mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: Implement JSHandle for BiDi (#9660)
This commit is contained in:
parent
56f99f7b10
commit
0c85c0611c
@ -35,7 +35,7 @@ const windowHandle = await page.evaluateHandle(() => window);
|
||||
## Methods
|
||||
|
||||
| Method | Modifiers | Description |
|
||||
| ---------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ---------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [asElement()](./puppeteer.jshandle.aselement.md) | | |
|
||||
| [dispose()](./puppeteer.jshandle.dispose.md) | | Releases the object referenced by the handle for garbage collection. |
|
||||
| [evaluate(pageFunction, args)](./puppeteer.jshandle.evaluate.md) | | Evaluates the given function with the current handle as its first argument. |
|
||||
@ -45,5 +45,5 @@ const windowHandle = await page.evaluateHandle(() => window);
|
||||
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_1.md) | | |
|
||||
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_2.md) | | |
|
||||
| [jsonValue()](./puppeteer.jshandle.jsonvalue.md) | | |
|
||||
| [remoteObject()](./puppeteer.jshandle.remoteobject.md) | | Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) |
|
||||
| [remoteObject()](./puppeteer.jshandle.remoteobject.md) | | Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this handle. |
|
||||
| [toString()](./puppeteer.jshandle.tostring.md) | | Returns a string representation of the JSHandle. |
|
||||
|
@ -4,7 +4,7 @@ sidebar_label: JSHandle.remoteObject
|
||||
|
||||
# JSHandle.remoteObject() method
|
||||
|
||||
Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject)
|
||||
Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this handle.
|
||||
|
||||
#### Signature:
|
||||
|
||||
|
@ -177,9 +177,17 @@ export class JSHandle<T = unknown> {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get id(): string | undefined {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the
|
||||
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject)
|
||||
* backing this handle.
|
||||
*/
|
||||
remoteObject(): Protocol.Runtime.RemoteObject {
|
||||
throw new Error('Not implemented');
|
||||
|
@ -186,7 +186,7 @@ export class Accessibility {
|
||||
let backendNodeId: number | undefined;
|
||||
if (root) {
|
||||
const {node} = await this.#client.send('DOM.describeNode', {
|
||||
objectId: root.remoteObject().objectId,
|
||||
objectId: root.id,
|
||||
});
|
||||
backendNodeId = node.backendNodeId;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ const queryAXTree = async (
|
||||
role?: string
|
||||
): Promise<Protocol.Accessibility.AXNode[]> => {
|
||||
const {nodes} = await client.send('Accessibility.queryAXTree', {
|
||||
objectId: element.remoteObject().objectId,
|
||||
objectId: element.id,
|
||||
accessibleName,
|
||||
role,
|
||||
});
|
||||
|
@ -43,12 +43,8 @@ import {
|
||||
NodeFor,
|
||||
} from './types.js';
|
||||
import {KeyInput} from './USKeyboardLayout.js';
|
||||
import {
|
||||
debugError,
|
||||
isString,
|
||||
releaseObject,
|
||||
valueFromRemoteObject,
|
||||
} from './util.js';
|
||||
import {debugError, isString} from './util.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
|
||||
const applyOffsetsToQuad = (
|
||||
quad: Point[],
|
||||
@ -70,10 +66,8 @@ const applyOffsetsToQuad = (
|
||||
export class CDPElementHandle<
|
||||
ElementType extends Node = Element
|
||||
> extends ElementHandle<ElementType> {
|
||||
#disposed = false;
|
||||
#frame: Frame;
|
||||
#context: ExecutionContext;
|
||||
#remoteObject: Protocol.Runtime.RemoteObject;
|
||||
#jsHandle: CDPJSHandle<ElementType>;
|
||||
|
||||
constructor(
|
||||
context: ExecutionContext,
|
||||
@ -81,8 +75,7 @@ export class CDPElementHandle<
|
||||
frame: Frame
|
||||
) {
|
||||
super();
|
||||
this.#context = context;
|
||||
this.#remoteObject = remoteObject;
|
||||
this.#jsHandle = new CDPJSHandle(context, remoteObject);
|
||||
this.#frame = frame;
|
||||
}
|
||||
|
||||
@ -90,18 +83,22 @@ export class CDPElementHandle<
|
||||
* @internal
|
||||
*/
|
||||
override executionContext(): ExecutionContext {
|
||||
return this.#context;
|
||||
return this.#jsHandle.executionContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
override get client(): CDPSession {
|
||||
return this.#context._client;
|
||||
return this.#jsHandle.client;
|
||||
}
|
||||
|
||||
override get id(): string | undefined {
|
||||
return this.#jsHandle.id;
|
||||
}
|
||||
|
||||
override remoteObject(): Protocol.Runtime.RemoteObject {
|
||||
return this.#remoteObject;
|
||||
return this.#jsHandle.remoteObject();
|
||||
}
|
||||
|
||||
override async evaluate<
|
||||
@ -143,7 +140,7 @@ export class CDPElementHandle<
|
||||
}
|
||||
|
||||
override get disposed(): boolean {
|
||||
return this.#disposed;
|
||||
return this.#jsHandle.disposed;
|
||||
}
|
||||
|
||||
override async getProperty<K extends keyof ElementType>(
|
||||
@ -153,30 +150,27 @@ export class CDPElementHandle<
|
||||
override async getProperty<K extends keyof ElementType>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<ElementType[K]>> {
|
||||
return this.evaluateHandle((object, propertyName) => {
|
||||
return object[propertyName as K];
|
||||
}, propertyName);
|
||||
return this.#jsHandle.getProperty(propertyName);
|
||||
}
|
||||
|
||||
override async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
return this.#jsHandle.getProperties();
|
||||
}
|
||||
|
||||
override asElement(): CDPElementHandle<ElementType> | null {
|
||||
return this;
|
||||
}
|
||||
|
||||
override async jsonValue(): Promise<ElementType> {
|
||||
if (!this.#remoteObject.objectId) {
|
||||
return valueFromRemoteObject(this.#remoteObject);
|
||||
}
|
||||
const value = await this.evaluate(object => {
|
||||
return object;
|
||||
});
|
||||
if (value === undefined) {
|
||||
throw new Error('Could not serialize referenced object');
|
||||
}
|
||||
return value;
|
||||
return this.#jsHandle.jsonValue();
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
if (!this.#remoteObject.objectId) {
|
||||
return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject);
|
||||
return this.#jsHandle.toString();
|
||||
}
|
||||
const type = this.#remoteObject.subtype || this.#remoteObject.type;
|
||||
return 'JSHandle@' + type;
|
||||
|
||||
override async dispose(): Promise<void> {
|
||||
return await this.#jsHandle.dispose();
|
||||
}
|
||||
|
||||
override async $<Selector extends string>(
|
||||
@ -297,10 +291,6 @@ export class CDPElementHandle<
|
||||
return this as unknown as HandleFor<ElementFor<K>>;
|
||||
}
|
||||
|
||||
override asElement(): CDPElementHandle<ElementType> | null {
|
||||
return this;
|
||||
}
|
||||
|
||||
override async contentFrame(): Promise<Frame | null> {
|
||||
const nodeInfo = await this.client.send('DOM.describeNode', {
|
||||
objectId: this.remoteObject().objectId,
|
||||
@ -464,7 +454,7 @@ export class CDPElementHandle<
|
||||
|
||||
#getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
|
||||
const params: Protocol.DOM.GetBoxModelRequest = {
|
||||
objectId: this.remoteObject().objectId,
|
||||
objectId: this.id,
|
||||
};
|
||||
return this.client.send('DOM.getBoxModel', params).catch(error => {
|
||||
return debugError(error);
|
||||
@ -846,14 +836,6 @@ export class CDPElementHandle<
|
||||
return threshold === 1 ? visibleRatio === 1 : visibleRatio > threshold;
|
||||
}, threshold);
|
||||
}
|
||||
|
||||
override async dispose(): Promise<void> {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
this.#disposed = true;
|
||||
await releaseObject(this.client, this.#remoteObject);
|
||||
}
|
||||
}
|
||||
|
||||
function computeQuadArea(quad: Point[]): number {
|
||||
|
@ -29,6 +29,8 @@ import {
|
||||
stringifyFunction,
|
||||
valueFromRemoteObject,
|
||||
} from './util.js';
|
||||
import {CDPJSHandle} from './JSHandle.js';
|
||||
import {CDPElementHandle} from './ElementHandle.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -321,7 +323,10 @@ export class ExecutionContext {
|
||||
if (Object.is(arg, NaN)) {
|
||||
return {unserializableValue: 'NaN'};
|
||||
}
|
||||
const objectHandle = arg && arg instanceof JSHandle ? arg : null;
|
||||
const objectHandle =
|
||||
arg && (arg instanceof CDPJSHandle || arg instanceof CDPElementHandle)
|
||||
? arg
|
||||
: null;
|
||||
if (objectHandle) {
|
||||
if (objectHandle.executionContext() !== this) {
|
||||
throw new Error(
|
||||
|
@ -539,7 +539,7 @@ export class IsolatedWorld {
|
||||
'Cannot adopt handle that already belongs to this execution context'
|
||||
);
|
||||
const nodeInfo = await this.#client.send('DOM.describeNode', {
|
||||
objectId: handle.remoteObject().objectId,
|
||||
objectId: handle.id,
|
||||
});
|
||||
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
||||
}
|
||||
|
@ -156,6 +156,10 @@ export class CDPJSHandle<T> extends JSHandle<T> {
|
||||
return 'JSHandle@' + type;
|
||||
}
|
||||
|
||||
override get id(): string | undefined {
|
||||
return this.#remoteObject.objectId;
|
||||
}
|
||||
|
||||
override remoteObject(): Protocol.Runtime.RemoteObject {
|
||||
return this.#remoteObject;
|
||||
}
|
||||
|
@ -525,13 +525,12 @@ export class CDPPage extends Page {
|
||||
): Promise<JSHandle<Prototype[]>> {
|
||||
const context = await this.mainFrame().executionContext();
|
||||
assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
|
||||
const remoteObject = prototypeHandle.remoteObject();
|
||||
assert(
|
||||
remoteObject.objectId,
|
||||
prototypeHandle.id,
|
||||
'Prototype JSHandle must not be referencing primitive value'
|
||||
);
|
||||
const response = await context._client.send('Runtime.queryObjects', {
|
||||
prototypeObjectId: remoteObject.objectId,
|
||||
prototypeObjectId: prototypeHandle.id,
|
||||
});
|
||||
return createJSHandle(context, response.objects) as HandleFor<Prototype[]>;
|
||||
}
|
||||
@ -820,7 +819,7 @@ export class CDPPage extends Page {
|
||||
}
|
||||
const textTokens = [];
|
||||
for (const arg of args) {
|
||||
const remoteObject = arg.remoteObject();
|
||||
const remoteObject = arg.remoteObject() as Protocol.Runtime.RemoteObject;
|
||||
if (remoteObject.objectId) {
|
||||
textTokens.push(arg.toString());
|
||||
} else {
|
||||
|
@ -36,6 +36,10 @@ interface Commands {
|
||||
params: Bidi.Script.CallFunctionParameters;
|
||||
returnType: Bidi.Script.CallFunctionResult;
|
||||
};
|
||||
'script.disown': {
|
||||
params: Bidi.Script.DisownParameters;
|
||||
returnType: Bidi.Script.DisownResult;
|
||||
};
|
||||
'browsingContext.create': {
|
||||
params: Bidi.BrowsingContext.CreateParameters;
|
||||
returnType: Bidi.BrowsingContext.CreateResult;
|
||||
|
146
packages/puppeteer-core/src/common/bidi/JSHandle.ts
Normal file
146
packages/puppeteer-core/src/common/bidi/JSHandle.ts
Normal file
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright 2023 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {ElementHandle} from '../../api/ElementHandle.js';
|
||||
import {EvaluateFuncWith, HandleFor, HandleOr} from '../../common/types.js';
|
||||
import {releaseReference} from './utils.js';
|
||||
import {Page} from './Page.js';
|
||||
import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
import {Connection} from './Connection.js';
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
|
||||
export class JSHandle<T = unknown> extends BaseJSHandle<T> {
|
||||
#disposed = false;
|
||||
#context;
|
||||
#remoteValue;
|
||||
|
||||
constructor(context: Page, remoteValue: Bidi.CommonDataTypes.RemoteValue) {
|
||||
super();
|
||||
this.#context = context;
|
||||
this.#remoteValue = remoteValue;
|
||||
}
|
||||
|
||||
context(): Page {
|
||||
return this.#context;
|
||||
}
|
||||
|
||||
get connecton(): Connection {
|
||||
return this.#context.connection;
|
||||
}
|
||||
|
||||
override get disposed(): boolean {
|
||||
return this.#disposed;
|
||||
}
|
||||
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return await this.context().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFuncWith<T, Params> = EvaluateFuncWith<T, Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return await this.context().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>>;
|
||||
override async getProperty(propertyName: string): Promise<HandleFor<unknown>>;
|
||||
override async getProperty<K extends keyof T>(
|
||||
propertyName: HandleOr<K>
|
||||
): Promise<HandleFor<T[K]>> {
|
||||
return await this.evaluateHandle((object, propertyName) => {
|
||||
return object[propertyName as K];
|
||||
}, propertyName);
|
||||
}
|
||||
|
||||
override async getProperties(): Promise<Map<string, BaseJSHandle>> {
|
||||
// TODO(lightning00blade): Either include return of depth Handles in RemoteValue
|
||||
// or new BiDi command that returns array of remote value
|
||||
const keys = await this.evaluate(object => {
|
||||
return Object.getOwnPropertyNames(object);
|
||||
});
|
||||
const map: Map<string, BaseJSHandle> = new Map();
|
||||
const results = await Promise.all(
|
||||
keys.map(key => {
|
||||
return this.getProperty(key);
|
||||
})
|
||||
);
|
||||
|
||||
for (const [key, value] of Object.entries(keys)) {
|
||||
const handle = results[key as any];
|
||||
if (handle) {
|
||||
map.set(value, handle);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
override async jsonValue(): Promise<T> {
|
||||
if (!('handle' in this.#remoteValue)) {
|
||||
return BidiSerializer.deserialize(this.#remoteValue);
|
||||
}
|
||||
const value = await this.evaluate(object => {
|
||||
return object;
|
||||
});
|
||||
if (value === undefined) {
|
||||
throw new Error('Could not serialize referenced object');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
override asElement(): ElementHandle<Node> | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
override async dispose(): Promise<void> {
|
||||
if (this.#disposed) {
|
||||
return;
|
||||
}
|
||||
this.#disposed = true;
|
||||
if ('handle' in this.#remoteValue) {
|
||||
await releaseReference(this.connecton, this.#remoteValue);
|
||||
}
|
||||
}
|
||||
|
||||
override toString(): string {
|
||||
if (!('handle' in this.#remoteValue)) {
|
||||
return 'JSHandle:' + BidiSerializer.deserialize(this.#remoteValue);
|
||||
}
|
||||
|
||||
return 'JSHandle@' + this.#remoteValue.type;
|
||||
}
|
||||
|
||||
override get id(): string | undefined {
|
||||
return 'handle' in this.#remoteValue ? this.#remoteValue.handle : undefined;
|
||||
}
|
||||
|
||||
bidiObject(): Bidi.CommonDataTypes.RemoteValue {
|
||||
return this.#remoteValue;
|
||||
}
|
||||
}
|
@ -16,28 +16,45 @@
|
||||
|
||||
import {Page as PageBase} from '../../api/Page.js';
|
||||
import {Connection} from './Connection.js';
|
||||
import type {EvaluateFunc} from '../types.js';
|
||||
import type {EvaluateFunc, HandleFor} from '../types.js';
|
||||
import {isString, stringifyFunction} from '../util.js';
|
||||
import {BidiSerializer} from './Serializer.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {Reference} from './types.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class Page extends PageBase {
|
||||
#connection: Connection;
|
||||
#contextId: string;
|
||||
_contextId: string;
|
||||
|
||||
constructor(connection: Connection, contextId: string) {
|
||||
super();
|
||||
this.#connection = connection;
|
||||
this.#contextId = contextId;
|
||||
this._contextId = contextId;
|
||||
}
|
||||
|
||||
override async close(): Promise<void> {
|
||||
await this.#connection.send('browsingContext.close', {
|
||||
context: this.#contextId,
|
||||
context: this._contextId,
|
||||
});
|
||||
}
|
||||
|
||||
get connection(): Connection {
|
||||
return this.#connection;
|
||||
}
|
||||
|
||||
override async evaluateHandle<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
||||
return this.#evaluate(false, pageFunction, ...args);
|
||||
}
|
||||
|
||||
override async evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
@ -45,18 +62,52 @@ export class Page extends PageBase {
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>> {
|
||||
return this.#evaluate(true, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async #evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
returnByValue: true,
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<Awaited<ReturnType<Func>>>;
|
||||
async #evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
returnByValue: false,
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
||||
async #evaluate<
|
||||
Params extends unknown[],
|
||||
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>
|
||||
>(
|
||||
returnByValue: boolean,
|
||||
pageFunction: Func | string,
|
||||
...args: Params
|
||||
): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
|
||||
let responsePromise;
|
||||
const resultOwnership = returnByValue ? 'none' : 'root';
|
||||
if (isString(pageFunction)) {
|
||||
responsePromise = this.#connection.send('script.evaluate', {
|
||||
expression: pageFunction,
|
||||
target: {context: this.#contextId},
|
||||
target: {context: this._contextId},
|
||||
resultOwnership,
|
||||
awaitPromise: true,
|
||||
});
|
||||
} else {
|
||||
responsePromise = this.#connection.send('script.callFunction', {
|
||||
functionDeclaration: stringifyFunction(pageFunction),
|
||||
arguments: await Promise.all(args.map(BidiSerializer.serialize)),
|
||||
target: {context: this.#contextId},
|
||||
arguments: await Promise.all(
|
||||
args.map(arg => {
|
||||
return BidiSerializer.serialize(arg, this);
|
||||
})
|
||||
),
|
||||
target: {context: this._contextId},
|
||||
resultOwnership,
|
||||
awaitPromise: true,
|
||||
});
|
||||
}
|
||||
@ -67,8 +118,22 @@ export class Page extends PageBase {
|
||||
throw new Error(result.exceptionDetails.text);
|
||||
}
|
||||
|
||||
return BidiSerializer.deserialize(result.result) as Awaited<
|
||||
ReturnType<Func>
|
||||
>;
|
||||
return returnByValue
|
||||
? BidiSerializer.deserialize(result.result)
|
||||
: getBidiHandle(this, result.result as Reference);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function getBidiHandle(context: Page, result: Reference): JSHandle {
|
||||
// TODO: | ElementHandle<Node>
|
||||
if (
|
||||
(result.type === 'node' || result.type === 'window') &&
|
||||
context._contextId
|
||||
) {
|
||||
throw new Error('ElementHandle not implemented');
|
||||
}
|
||||
return new JSHandle(context, result);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
import {debugError, isDate, isPlainObject, isRegExp} from '../util.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {Page} from './Page.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -46,6 +48,18 @@ export class BidiSerializer {
|
||||
value: parsedArray,
|
||||
};
|
||||
} else if (isPlainObject(arg)) {
|
||||
try {
|
||||
JSON.stringify(arg);
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof TypeError &&
|
||||
error.message.startsWith('Converting circular structure to JSON')
|
||||
) {
|
||||
error.message += ' Recursive objects are not allowed.';
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const parsedObject: Bidi.CommonDataTypes.MappingLocalValue = [];
|
||||
for (const key in arg) {
|
||||
parsedObject.push([
|
||||
@ -112,8 +126,24 @@ export class BidiSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
static serialize(arg: unknown): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||
static serialize(
|
||||
arg: unknown,
|
||||
context: Page
|
||||
): Bidi.CommonDataTypes.LocalOrRemoteValue {
|
||||
// TODO: See use case of LazyArgs
|
||||
const objectHandle = arg && arg instanceof JSHandle ? arg : null;
|
||||
if (objectHandle) {
|
||||
if (objectHandle.context() !== context) {
|
||||
throw new Error(
|
||||
'JSHandles can be evaluated only in the context they were created!'
|
||||
);
|
||||
}
|
||||
if (objectHandle.disposed) {
|
||||
throw new Error('JSHandle is disposed!');
|
||||
}
|
||||
return objectHandle.bidiObject();
|
||||
}
|
||||
|
||||
return BidiSerializer.serializeRemoveValue(arg);
|
||||
}
|
||||
|
||||
@ -146,8 +176,8 @@ export class BidiSerializer {
|
||||
});
|
||||
case 'set':
|
||||
// TODO: Check expected output when value is undefined
|
||||
return result.value.reduce((acc: Set<unknown>, value: unknown) => {
|
||||
return acc.add(value);
|
||||
return result.value.reduce((acc: Set<unknown>, value) => {
|
||||
return acc.add(BidiSerializer.deserializeLocalValue(value));
|
||||
}, new Set());
|
||||
case 'object':
|
||||
if (result.value) {
|
||||
@ -202,7 +232,7 @@ export class BidiSerializer {
|
||||
return {key, value};
|
||||
}
|
||||
|
||||
static deserialize(result: Bidi.CommonDataTypes.RemoteValue): unknown {
|
||||
static deserialize(result: Bidi.CommonDataTypes.RemoteValue): any {
|
||||
if (!result) {
|
||||
debugError('Service did not produce a result.');
|
||||
return undefined;
|
||||
|
6
packages/puppeteer-core/src/common/bidi/types.ts
Normal file
6
packages/puppeteer-core/src/common/bidi/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
|
||||
export type Reference = Extract<
|
||||
Bidi.CommonDataTypes.RemoteValue,
|
||||
Bidi.CommonDataTypes.RemoteReference
|
||||
>;
|
45
packages/puppeteer-core/src/common/bidi/utils.ts
Normal file
45
packages/puppeteer-core/src/common/bidi/utils.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2023 Google Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {debug} from '../Debug.js';
|
||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||
import {Connection} from './Connection.js';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const debugError = debug('puppeteer:error');
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function releaseReference(
|
||||
client: Connection,
|
||||
remoteReference: Bidi.CommonDataTypes.RemoteReference
|
||||
): Promise<void> {
|
||||
if (!remoteReference.handle) {
|
||||
return;
|
||||
}
|
||||
await client
|
||||
.send('script.disown', {
|
||||
target: {realm: '', context: ''}, // TODO: Populate
|
||||
handles: [remoteReference.handle],
|
||||
})
|
||||
.catch((error: any) => {
|
||||
// Exceptions might happen in case of a page been navigated or closed.
|
||||
// Swallow these since they are harmless and we don't leak anything in this case.
|
||||
debugError(error);
|
||||
});
|
||||
}
|
@ -414,8 +414,8 @@
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.click should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should return Point data",
|
||||
"platforms": ["darwin", "win32", "linux"],
|
||||
"parameters": ["firefox"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
@ -1756,5 +1756,59 @@
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["SKIP"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec]",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["PASS"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should return the RemoteObject",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle Page.evaluateHandle should use the same JS wrappers",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.jsonValue should not work with dates",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.asElement should work",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.asElement should return ElementHandle for TextNodes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work for complicated objects",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
},
|
||||
{
|
||||
"testIdPattern": "[jshandle.spec] JSHandle JSHandle.toString should work with different subtypes",
|
||||
"platforms": ["darwin", "linux", "win32"],
|
||||
"parameters": ["webDriverBiDi"],
|
||||
"expectations": ["FAIL"]
|
||||
}
|
||||
]
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
getTestState,
|
||||
setupTestBrowserHooks,
|
||||
setupTestPageAndContextHooks,
|
||||
shortWaitForArrayToHaveAtLeastNElements,
|
||||
} from './mocha-utils.js';
|
||||
|
||||
import {Puppeteer} from 'puppeteer';
|
||||
@ -173,7 +174,6 @@ describe('ElementHandle specs', function () {
|
||||
});
|
||||
|
||||
describe('ElementHandle.click', function () {
|
||||
// See https://github.com/puppeteer/puppeteer/issues/7175
|
||||
it('should work', async () => {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
@ -186,6 +186,40 @@ describe('ElementHandle specs', function () {
|
||||
})
|
||||
).toBe('Clicked');
|
||||
});
|
||||
it('should return Point data', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
const clicks: Array<[x: number, y: number]> = [];
|
||||
|
||||
await page.exposeFunction('reportClick', (x: number, y: number): void => {
|
||||
clicks.push([x, y]);
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.body.style.padding = '0';
|
||||
document.body.style.margin = '0';
|
||||
document.body.innerHTML = `
|
||||
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
||||
`;
|
||||
document.body.addEventListener('click', e => {
|
||||
(window as any).reportClick(e.clientX, e.clientY);
|
||||
});
|
||||
});
|
||||
|
||||
const divHandle = (await page.$('div'))!;
|
||||
await divHandle.click();
|
||||
await divHandle.click({
|
||||
offset: {
|
||||
x: 10,
|
||||
y: 15,
|
||||
},
|
||||
});
|
||||
await shortWaitForArrayToHaveAtLeastNElements(clicks, 2);
|
||||
expect(clicks).toEqual([
|
||||
[45 + 60, 45 + 30], // margin + middle point offset
|
||||
[30 + 10, 30 + 15], // margin + offset
|
||||
]);
|
||||
});
|
||||
it('should work for Shadow DOM v1', async () => {
|
||||
const {page, server} = getTestState();
|
||||
|
||||
@ -273,6 +307,70 @@ describe('ElementHandle specs', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ElementHandle.clickablePoint', function () {
|
||||
it('should work', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.body.style.padding = '0';
|
||||
document.body.style.margin = '0';
|
||||
document.body.innerHTML = `
|
||||
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
||||
`;
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
return new Promise(resolve => {
|
||||
return window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
const divHandle = (await page.$('div'))!;
|
||||
expect(await divHandle.clickablePoint()).toEqual({
|
||||
x: 45 + 60, // margin + middle point offset
|
||||
y: 45 + 30, // margin + middle point offset
|
||||
});
|
||||
expect(
|
||||
await divHandle.clickablePoint({
|
||||
x: 10,
|
||||
y: 15,
|
||||
})
|
||||
).toEqual({
|
||||
x: 30 + 10, // margin + offset
|
||||
y: 30 + 15, // margin + offset
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for iframes', async () => {
|
||||
const {page} = getTestState();
|
||||
await page.evaluate(() => {
|
||||
document.body.style.padding = '10px';
|
||||
document.body.style.margin = '10px';
|
||||
document.body.innerHTML = `
|
||||
<iframe style="border: none; margin: 0; padding: 0;" seamless sandbox srcdoc="<style>* { margin: 0; padding: 0;}</style><div style='cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;' />"></iframe>
|
||||
`;
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
return new Promise(resolve => {
|
||||
return window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
const frame = page.frames()[1]!;
|
||||
const divHandle = (await frame.$('div'))!;
|
||||
expect(await divHandle.clickablePoint()).toEqual({
|
||||
x: 20 + 45 + 60, // iframe pos + margin + middle point offset
|
||||
y: 20 + 45 + 30, // iframe pos + margin + middle point offset
|
||||
});
|
||||
expect(
|
||||
await divHandle.clickablePoint({
|
||||
x: 10,
|
||||
y: 15,
|
||||
})
|
||||
).toEqual({
|
||||
x: 20 + 30 + 10, // iframe pos + margin + offset
|
||||
y: 20 + 30 + 15, // iframe pos + margin + offset
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Element.waitForSelector', () => {
|
||||
it('should wait correctly with waitForSelector on an element', async () => {
|
||||
const {page} = getTestState();
|
||||
|
@ -19,7 +19,6 @@ import {
|
||||
getTestState,
|
||||
setupTestBrowserHooks,
|
||||
setupTestPageAndContextHooks,
|
||||
shortWaitForArrayToHaveAtLeastNElements,
|
||||
} from './mocha-utils.js';
|
||||
|
||||
describe('JSHandle', function () {
|
||||
@ -336,105 +335,4 @@ describe('JSHandle', function () {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSHandle.clickablePoint', function () {
|
||||
it('should work', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.body.style.padding = '0';
|
||||
document.body.style.margin = '0';
|
||||
document.body.innerHTML = `
|
||||
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
||||
`;
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
return new Promise(resolve => {
|
||||
return window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
const divHandle = (await page.$('div'))!;
|
||||
expect(await divHandle.clickablePoint()).toEqual({
|
||||
x: 45 + 60, // margin + middle point offset
|
||||
y: 45 + 30, // margin + middle point offset
|
||||
});
|
||||
expect(
|
||||
await divHandle.clickablePoint({
|
||||
x: 10,
|
||||
y: 15,
|
||||
})
|
||||
).toEqual({
|
||||
x: 30 + 10, // margin + offset
|
||||
y: 30 + 15, // margin + offset
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for iframes', async () => {
|
||||
const {page} = getTestState();
|
||||
await page.evaluate(() => {
|
||||
document.body.style.padding = '10px';
|
||||
document.body.style.margin = '10px';
|
||||
document.body.innerHTML = `
|
||||
<iframe style="border: none; margin: 0; padding: 0;" seamless sandbox srcdoc="<style>* { margin: 0; padding: 0;}</style><div style='cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;' />"></iframe>
|
||||
`;
|
||||
});
|
||||
await page.evaluate(async () => {
|
||||
return new Promise(resolve => {
|
||||
return window.requestAnimationFrame(resolve);
|
||||
});
|
||||
});
|
||||
const frame = page.frames()[1]!;
|
||||
const divHandle = (await frame.$('div'))!;
|
||||
expect(await divHandle.clickablePoint()).toEqual({
|
||||
x: 20 + 45 + 60, // iframe pos + margin + middle point offset
|
||||
y: 20 + 45 + 30, // iframe pos + margin + middle point offset
|
||||
});
|
||||
expect(
|
||||
await divHandle.clickablePoint({
|
||||
x: 10,
|
||||
y: 15,
|
||||
})
|
||||
).toEqual({
|
||||
x: 20 + 30 + 10, // iframe pos + margin + offset
|
||||
y: 20 + 30 + 15, // iframe pos + margin + offset
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSHandle.click', function () {
|
||||
it('should work', async () => {
|
||||
const {page} = getTestState();
|
||||
|
||||
const clicks: Array<[x: number, y: number]> = [];
|
||||
|
||||
await page.exposeFunction('reportClick', (x: number, y: number): void => {
|
||||
clicks.push([x, y]);
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.body.style.padding = '0';
|
||||
document.body.style.margin = '0';
|
||||
document.body.innerHTML = `
|
||||
<div style="cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;"></div>
|
||||
`;
|
||||
document.body.addEventListener('click', e => {
|
||||
(window as any).reportClick(e.clientX, e.clientY);
|
||||
});
|
||||
});
|
||||
|
||||
const divHandle = (await page.$('div'))!;
|
||||
await divHandle.click();
|
||||
await divHandle.click({
|
||||
offset: {
|
||||
x: 10,
|
||||
y: 15,
|
||||
},
|
||||
});
|
||||
await shortWaitForArrayToHaveAtLeastNElements(clicks, 2);
|
||||
expect(clicks).toEqual([
|
||||
[45 + 60, 45 + 30], // margin + middle point offset
|
||||
[30 + 10, 30 + 15], // margin + offset
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user