chore: reuse JSHandle between browsers (#103)
This commit is contained in:
parent
06ba0f7a7f
commit
490db5bc18
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
import { CDPSession } from './Connection';
|
||||
import { helper } from '../helper';
|
||||
import { valueFromRemoteObject, getExceptionMessage } from './protocolHelper';
|
||||
import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
|
||||
import { valueFromRemoteObject, getExceptionMessage, releaseObject } from './protocolHelper';
|
||||
import { createJSHandle, ElementHandle } from './JSHandle';
|
||||
import { Protocol } from './protocol';
|
||||
import { Response } from './NetworkManager';
|
||||
import * as js from '../javascript';
|
||||
|
|
@ -26,9 +26,10 @@ import * as js from '../javascript';
|
|||
export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
|
||||
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||
|
||||
export type ExecutionContext = js.ExecutionContext<JSHandle, ElementHandle, Response>;
|
||||
export type ExecutionContext = js.ExecutionContext<ElementHandle, Response>;
|
||||
export type JSHandle = js.JSHandle<ElementHandle, Response>;
|
||||
|
||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSHandle, ElementHandle, Response> {
|
||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate<ElementHandle, Response> {
|
||||
_client: CDPSession;
|
||||
_contextId: number;
|
||||
|
||||
|
|
@ -107,17 +108,18 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
return { unserializableValue: '-Infinity' };
|
||||
if (Object.is(arg, NaN))
|
||||
return { unserializableValue: 'NaN' };
|
||||
const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
|
||||
const objectHandle = arg && (arg instanceof js.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!');
|
||||
if (objectHandle._remoteObject.unserializableValue)
|
||||
return { unserializableValue: objectHandle._remoteObject.unserializableValue };
|
||||
if (!objectHandle._remoteObject.objectId)
|
||||
return { value: objectHandle._remoteObject.value };
|
||||
return { objectId: objectHandle._remoteObject.objectId };
|
||||
const remoteObject = toRemoteObject(objectHandle);
|
||||
if (remoteObject.unserializableValue)
|
||||
return { unserializableValue: remoteObject.unserializableValue };
|
||||
if (!remoteObject.objectId)
|
||||
return { value: remoteObject.value };
|
||||
return { objectId: remoteObject.objectId };
|
||||
}
|
||||
return { value: arg };
|
||||
}
|
||||
|
|
@ -141,4 +143,55 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
});
|
||||
return createJSHandle(context, object) as ElementHandle;
|
||||
}
|
||||
|
||||
async getProperties(handle: JSHandle): Promise<Map<string, JSHandle>> {
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId: toRemoteObject(handle).objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.result) {
|
||||
if (!property.enumerable)
|
||||
continue;
|
||||
result.set(property.name, createJSHandle(handle.executionContext(), property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async releaseHandle(handle: JSHandle): Promise<void> {
|
||||
await releaseObject(this._client, toRemoteObject(handle));
|
||||
}
|
||||
|
||||
async handleJSONValue(handle: JSHandle): Promise<any> {
|
||||
const remoteObject = toRemoteObject(handle);
|
||||
if (remoteObject.objectId) {
|
||||
const response = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: 'function() { return this; }',
|
||||
objectId: remoteObject.objectId,
|
||||
returnByValue: true,
|
||||
awaitPromise: true,
|
||||
});
|
||||
return valueFromRemoteObject(response.result);
|
||||
}
|
||||
return valueFromRemoteObject(remoteObject);
|
||||
}
|
||||
|
||||
handleToString(handle: JSHandle): string {
|
||||
const object = toRemoteObject(handle);
|
||||
if (object.objectId) {
|
||||
const type = object.subtype || object.type;
|
||||
return 'JSHandle@' + type;
|
||||
}
|
||||
return 'JSHandle:' + valueFromRemoteObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteObjectSymbol = Symbol('RemoteObject');
|
||||
|
||||
export function toRemoteObject(handle: JSHandle): Protocol.Runtime.RemoteObject {
|
||||
return (handle as any)[remoteObjectSymbol];
|
||||
}
|
||||
|
||||
export function markJSHandle(handle: JSHandle, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||
(handle as any)[remoteObjectSymbol] = remoteObject;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ import { EventEmitter } from 'events';
|
|||
import { assert, debugError } from '../helper';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { CDPSession } from './Connection';
|
||||
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate, ExecutionContext } from './ExecutionContext';
|
||||
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate, ExecutionContext, JSHandle, toRemoteObject } from './ExecutionContext';
|
||||
import * as frames from '../frames';
|
||||
import * as js from '../javascript';
|
||||
import { LifecycleWatcher } from './LifecycleWatcher';
|
||||
import { NetworkManager, Response } from './NetworkManager';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { ElementHandle, JSHandle, createJSHandle } from './JSHandle';
|
||||
import { ElementHandle, createJSHandle } from './JSHandle';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
|
|
@ -45,9 +45,9 @@ type FrameData = {
|
|||
lifecycleEvents: Set<string>,
|
||||
};
|
||||
|
||||
export type Frame = frames.Frame<JSHandle, ElementHandle, Response>;
|
||||
export type Frame = frames.Frame<ElementHandle, Response>;
|
||||
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, Response> {
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate<ElementHandle, Response> {
|
||||
_client: CDPSession;
|
||||
private _page: Page;
|
||||
private _networkManager: NetworkManager;
|
||||
|
|
@ -185,7 +185,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
|
|||
|
||||
async adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext): Promise<ElementHandle> {
|
||||
const nodeInfo = await this._client.send('DOM.describeNode', {
|
||||
objectId: elementHandle._remoteObject.objectId,
|
||||
objectId: toRemoteObject(elementHandle).objectId,
|
||||
});
|
||||
return (context._delegate as ExecutionContextDelegate).adoptBackendNodeId(context, nodeInfo.node.backendNodeId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ import { assert, debugError, helper } from '../helper';
|
|||
import Injected from '../injected/injected';
|
||||
import * as input from '../input';
|
||||
import * as types from '../types';
|
||||
import * as js from '../javascript';
|
||||
import { CDPSession } from './Connection';
|
||||
import { Frame } from './FrameManager';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { JSHandle, ExecutionContext, ExecutionContextDelegate, markJSHandle } from './ExecutionContext';
|
||||
import { Response } from './NetworkManager';
|
||||
|
||||
type SelectorRoot = Element | ShadowRoot | Document;
|
||||
|
||||
|
|
@ -41,102 +42,24 @@ export function createJSHandle(context: ExecutionContext, remoteObject: Protocol
|
|||
const frameManager = frame._delegate as FrameManager;
|
||||
return new ElementHandle(context, delegate._client, remoteObject, frameManager.page(), frameManager);
|
||||
}
|
||||
return new JSHandle(context, delegate._client, remoteObject);
|
||||
const handle = new js.JSHandle(context);
|
||||
markJSHandle(handle, remoteObject);
|
||||
return handle;
|
||||
}
|
||||
|
||||
export class JSHandle {
|
||||
_context: ExecutionContext;
|
||||
protected _client: CDPSession;
|
||||
_remoteObject: Protocol.Runtime.RemoteObject;
|
||||
_disposed = false;
|
||||
|
||||
constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||
this._context = context;
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
executionContext(): ExecutionContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
evaluate: types.EvaluateOn<JSHandle> = (pageFunction, ...args) => {
|
||||
return this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandleOn<JSHandle> = (pageFunction, ...args) => {
|
||||
return this.executionContext().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
async getProperty(propertyName: string): Promise<JSHandle | null> {
|
||||
const objectHandle = await this.evaluateHandle((object, propertyName) => {
|
||||
const result = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
}, propertyName);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName) || null;
|
||||
await objectHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId: this._remoteObject.objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.result) {
|
||||
if (!property.enumerable)
|
||||
continue;
|
||||
result.set(property.name, createJSHandle(this._context, property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async jsonValue(): Promise<object | null> {
|
||||
if (this._remoteObject.objectId) {
|
||||
const response = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: 'function() { return this; }',
|
||||
objectId: this._remoteObject.objectId,
|
||||
returnByValue: true,
|
||||
awaitPromise: true,
|
||||
});
|
||||
return valueFromRemoteObject(response.result);
|
||||
}
|
||||
return valueFromRemoteObject(this._remoteObject);
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this._disposed)
|
||||
return;
|
||||
this._disposed = true;
|
||||
await releaseObject(this._client, this._remoteObject);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
if (this._remoteObject.objectId) {
|
||||
const type = this._remoteObject.subtype || this._remoteObject.type;
|
||||
return 'JSHandle@' + type;
|
||||
}
|
||||
return 'JSHandle:' + valueFromRemoteObject(this._remoteObject);
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementHandle extends JSHandle {
|
||||
export class ElementHandle extends js.JSHandle<ElementHandle, Response> {
|
||||
private _client: CDPSession;
|
||||
private _remoteObject: Protocol.Runtime.RemoteObject;
|
||||
private _page: Page;
|
||||
private _frameManager: FrameManager;
|
||||
|
||||
constructor(context: ExecutionContext, client: CDPSession, remoteObject: Protocol.Runtime.RemoteObject, page: Page, frameManager: FrameManager) {
|
||||
super(context, client, remoteObject);
|
||||
super(context);
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
this._page = page;
|
||||
this._frameManager = frameManager;
|
||||
markJSHandle(this, remoteObject);
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ import { Workers } from './features/workers';
|
|||
import { Frame } from './FrameManager';
|
||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||
import { createJSHandle, ElementHandle } from './JSHandle';
|
||||
import { JSHandle, toRemoteObject } from './ExecutionContext';
|
||||
import { NetworkManagerEvents, Response } from './NetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||
|
|
@ -359,7 +360,7 @@ export class Page extends EventEmitter {
|
|||
}
|
||||
const textTokens = [];
|
||||
for (const arg of args) {
|
||||
const remoteObject = arg._remoteObject;
|
||||
const remoteObject = toRemoteObject(arg);
|
||||
if (remoteObject.objectId)
|
||||
textTokens.push(arg.toString());
|
||||
else
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export { BrowserFetcher } from './BrowserFetcher';
|
|||
export { Chromium } from './features/chromium';
|
||||
export { CDPSession } from './Connection';
|
||||
export { Dialog } from './Dialog';
|
||||
export { ExecutionContext } from '../javascript';
|
||||
export { ExecutionContext, JSHandle } from '../javascript';
|
||||
export { Accessibility } from './features/accessibility';
|
||||
export { Coverage } from './features/coverage';
|
||||
export { Overrides } from './features/overrides';
|
||||
|
|
@ -18,7 +18,7 @@ export { Permissions } from './features/permissions';
|
|||
export { Worker, Workers } from './features/workers';
|
||||
export { Frame } from '../frames';
|
||||
export { Keyboard, Mouse } from '../input';
|
||||
export { ElementHandle, JSHandle } from './JSHandle';
|
||||
export { ElementHandle } from './JSHandle';
|
||||
export { Request, Response } from './NetworkManager';
|
||||
export { ConsoleMessage, FileChooser, Page } from './Page';
|
||||
export { Playwright } from './Playwright';
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
import { CDPSession } from '../Connection';
|
||||
import { ElementHandle } from '../JSHandle';
|
||||
import { Protocol } from '../protocol';
|
||||
import { toRemoteObject } from '../ExecutionContext';
|
||||
|
||||
type SerializedAXNode = {
|
||||
role: string,
|
||||
|
|
@ -72,7 +73,7 @@ export class Accessibility {
|
|||
const {nodes} = await this._client.send('Accessibility.getFullAXTree');
|
||||
let backendNodeId = null;
|
||||
if (root) {
|
||||
const {node} = await this._client.send('DOM.describeNode', {objectId: root._remoteObject.objectId});
|
||||
const {node} = await this._client.send('DOM.describeNode', {objectId: toRemoteObject(root).objectId});
|
||||
backendNodeId = node.backendNodeId;
|
||||
}
|
||||
const defaultRoot = AXNode.createTree(nodes);
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@
|
|||
import { EventEmitter } from 'events';
|
||||
import { CDPSession, Connection } from '../Connection';
|
||||
import { debugError } from '../../helper';
|
||||
import { JSHandle } from '../JSHandle';
|
||||
import { Protocol } from '../protocol';
|
||||
import { Events } from '../events';
|
||||
import * as types from '../../types';
|
||||
import * as js from '../../javascript';
|
||||
import { ExecutionContext, ExecutionContextDelegate } from '../ExecutionContext';
|
||||
import { JSHandle, ExecutionContext, ExecutionContextDelegate } from '../ExecutionContext';
|
||||
import { createJSHandle } from '../JSHandle';
|
||||
|
||||
type AddToConsoleCallback = (type: string, args: JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void;
|
||||
type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void;
|
||||
|
|
@ -68,7 +68,7 @@ export class Worker extends EventEmitter {
|
|||
this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
|
||||
let jsHandleFactory: (o: Protocol.Runtime.RemoteObject) => JSHandle;
|
||||
this._client.once('Runtime.executionContextCreated', async event => {
|
||||
jsHandleFactory = remoteObject => new JSHandle(executionContext, client, remoteObject);
|
||||
jsHandleFactory = remoteObject => createJSHandle(executionContext, remoteObject);
|
||||
const executionContext = new js.ExecutionContext(new ExecutionContextDelegate(client, event.context), null);
|
||||
this._executionContextCallback(executionContext);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {helper} from '../helper';
|
||||
import { JSHandle, createHandle, ElementHandle } from './JSHandle';
|
||||
import {helper, debugError} from '../helper';
|
||||
import { createHandle, ElementHandle } from './JSHandle';
|
||||
import { Response } from './NetworkManager';
|
||||
import * as js from '../javascript';
|
||||
import { JugglerSession } from './Connection';
|
||||
|
||||
export type ExecutionContext = js.ExecutionContext<JSHandle, ElementHandle, Response>;
|
||||
export type ExecutionContext = js.ExecutionContext<ElementHandle, Response>;
|
||||
export type JSHandle = js.JSHandle<ElementHandle, Response>;
|
||||
|
||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSHandle, ElementHandle, Response> {
|
||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate<ElementHandle, Response> {
|
||||
_session: JugglerSession;
|
||||
_executionContextId: string;
|
||||
|
||||
|
|
@ -74,12 +75,12 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
}
|
||||
}
|
||||
const protocolArgs = args.map(arg => {
|
||||
if (arg instanceof JSHandle) {
|
||||
if (arg instanceof js.JSHandle) {
|
||||
if (arg._context !== context)
|
||||
throw new Error('JSHandles can be evaluated only in the context they were created!');
|
||||
if (arg._disposed)
|
||||
throw new Error('JSHandle is disposed!');
|
||||
return arg._protocolValue;
|
||||
return this._toProtocolValue(toPayload(arg));
|
||||
}
|
||||
if (Object.is(arg, Infinity))
|
||||
return {unserializableValue: 'Infinity'};
|
||||
|
|
@ -112,4 +113,72 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getProperties(handle: JSHandle): Promise<Map<string, JSHandle>> {
|
||||
const response = await this._session.send('Runtime.getObjectProperties', {
|
||||
executionContextId: this._executionContextId,
|
||||
objectId: toPayload(handle).objectId,
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.properties)
|
||||
result.set(property.name, createHandle(handle.executionContext(), property.value, null));
|
||||
return result;
|
||||
}
|
||||
|
||||
async releaseHandle(handle: JSHandle): Promise<void> {
|
||||
await this._session.send('Runtime.disposeObject', {
|
||||
executionContextId: this._executionContextId,
|
||||
objectId: toPayload(handle).objectId,
|
||||
}).catch(error => {
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
async handleJSONValue(handle: JSHandle): Promise<any> {
|
||||
const payload = toPayload(handle);
|
||||
if (!payload.objectId)
|
||||
return deserializeValue(payload);
|
||||
const simpleValue = await this._session.send('Runtime.callFunction', {
|
||||
executionContextId: this._executionContextId,
|
||||
returnByValue: true,
|
||||
functionDeclaration: (e => e).toString(),
|
||||
args: [this._toProtocolValue(payload)],
|
||||
});
|
||||
return deserializeValue(simpleValue.result);
|
||||
}
|
||||
|
||||
handleToString(handle: JSHandle): string {
|
||||
const payload = toPayload(handle);
|
||||
if (payload.objectId)
|
||||
return 'JSHandle@' + (payload.subtype || payload.type);
|
||||
return 'JSHandle:' + deserializeValue(payload);
|
||||
}
|
||||
|
||||
private _toProtocolValue(payload: any): any {
|
||||
return { value: payload.value, unserializableValue: payload.unserializableValue, objectId: payload.objectId };
|
||||
}
|
||||
}
|
||||
|
||||
const payloadSymbol = Symbol('payload');
|
||||
|
||||
export function toPayload(handle: JSHandle): any {
|
||||
return (handle as any)[payloadSymbol];
|
||||
}
|
||||
|
||||
export function markJSHandle(handle: JSHandle, payload: any) {
|
||||
(handle as any)[payloadSymbol] = payload;
|
||||
}
|
||||
|
||||
export function deserializeValue({unserializableValue, value}) {
|
||||
if (unserializableValue === 'Infinity')
|
||||
return Infinity;
|
||||
if (unserializableValue === '-Infinity')
|
||||
return -Infinity;
|
||||
if (unserializableValue === '-0')
|
||||
return -0;
|
||||
if (unserializableValue === 'NaN')
|
||||
return NaN;
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import { Page } from './Page';
|
|||
import {RegisteredListener, helper, assert} from '../helper';
|
||||
import {TimeoutError} from '../Errors';
|
||||
import {EventEmitter} from 'events';
|
||||
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { JSHandle, ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import {NavigationWatchdog, NextNavigationWatchdog} from './NavigationWatchdog';
|
||||
import { JSHandle, ElementHandle } from './JSHandle';
|
||||
import { ElementHandle } from './JSHandle';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { Response } from './NetworkManager';
|
||||
import * as frames from '../frames';
|
||||
|
|
@ -43,9 +43,9 @@ type FrameData = {
|
|||
firedEvents: Set<string>,
|
||||
};
|
||||
|
||||
export type Frame = frames.Frame<JSHandle, ElementHandle, Response>;
|
||||
export type Frame = frames.Frame<ElementHandle, Response>;
|
||||
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, Response> {
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate<ElementHandle, Response> {
|
||||
_session: JugglerSession;
|
||||
_page: Page;
|
||||
_networkManager: any;
|
||||
|
|
|
|||
|
|
@ -19,134 +19,31 @@ import { assert, debugError, helper } from '../helper';
|
|||
import Injected from '../injected/injected';
|
||||
import * as input from '../input';
|
||||
import * as types from '../types';
|
||||
import * as js from '../javascript';
|
||||
import { JugglerSession } from './Connection';
|
||||
import { Frame, FrameManager } from './FrameManager';
|
||||
import { Page } from './Page';
|
||||
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { JSHandle, ExecutionContext, markJSHandle, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { Response } from './NetworkManager';
|
||||
|
||||
type SelectorRoot = Element | ShadowRoot | Document;
|
||||
|
||||
export class JSHandle {
|
||||
_context: ExecutionContext;
|
||||
protected _session: JugglerSession;
|
||||
private _executionContextId: string;
|
||||
protected _objectId: string;
|
||||
private _type: string;
|
||||
private _subtype: string;
|
||||
_disposed: boolean;
|
||||
_protocolValue: { unserializableValue: any; value: any; objectId: any; };
|
||||
|
||||
constructor(context: ExecutionContext, payload: any) {
|
||||
this._context = context;
|
||||
const delegate = context._delegate as ExecutionContextDelegate;
|
||||
this._session = delegate._session;
|
||||
this._executionContextId = delegate._executionContextId;
|
||||
this._objectId = payload.objectId;
|
||||
this._type = payload.type;
|
||||
this._subtype = payload.subtype;
|
||||
this._disposed = false;
|
||||
this._protocolValue = {
|
||||
unserializableValue: payload.unserializableValue,
|
||||
value: payload.value,
|
||||
objectId: payload.objectId,
|
||||
};
|
||||
}
|
||||
|
||||
executionContext(): ExecutionContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
evaluate: types.EvaluateOn<JSHandle> = (pageFunction, ...args) => {
|
||||
return this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandleOn<JSHandle> = (pageFunction, ...args) => {
|
||||
return this.executionContext().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
if (this._objectId)
|
||||
return 'JSHandle@' + (this._subtype || this._type);
|
||||
return 'JSHandle:' + this._deserializeValue(this._protocolValue);
|
||||
}
|
||||
|
||||
async getProperty(propertyName: string): Promise<JSHandle | null> {
|
||||
const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
|
||||
const result = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
}, this, propertyName);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName) || null;
|
||||
await objectHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
const response = await this._session.send('Runtime.getObjectProperties', {
|
||||
executionContextId: this._executionContextId,
|
||||
objectId: this._objectId,
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.properties)
|
||||
result.set(property.name, createHandle(this._context, property.value, null));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_deserializeValue({unserializableValue, value}) {
|
||||
if (unserializableValue === 'Infinity')
|
||||
return Infinity;
|
||||
if (unserializableValue === '-Infinity')
|
||||
return -Infinity;
|
||||
if (unserializableValue === '-0')
|
||||
return -0;
|
||||
if (unserializableValue === 'NaN')
|
||||
return NaN;
|
||||
return value;
|
||||
}
|
||||
|
||||
async jsonValue() {
|
||||
if (!this._objectId)
|
||||
return this._deserializeValue(this._protocolValue);
|
||||
const simpleValue = await this._session.send('Runtime.callFunction', {
|
||||
executionContextId: this._executionContextId,
|
||||
returnByValue: true,
|
||||
functionDeclaration: (e => e).toString(),
|
||||
args: [this._protocolValue],
|
||||
});
|
||||
return this._deserializeValue(simpleValue.result);
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (!this._objectId)
|
||||
return;
|
||||
this._disposed = true;
|
||||
await this._session.send('Runtime.disposeObject', {
|
||||
executionContextId: this._executionContextId,
|
||||
objectId: this._objectId,
|
||||
}).catch(error => {
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementHandle extends JSHandle {
|
||||
export class ElementHandle extends js.JSHandle<ElementHandle, Response> {
|
||||
_frame: Frame;
|
||||
_frameId: string;
|
||||
_page: Page;
|
||||
_context: ExecutionContext;
|
||||
protected _session: JugglerSession;
|
||||
protected _objectId: string;
|
||||
|
||||
constructor(frame: Frame, frameId: string, page: Page, context: ExecutionContext, payload: any) {
|
||||
super(context, payload);
|
||||
constructor(frame: Frame, frameId: string, page: Page, session: JugglerSession, context: ExecutionContext, payload: any) {
|
||||
super(context);
|
||||
this._frame = frame;
|
||||
this._frameId = frameId;
|
||||
this._page = page;
|
||||
this._session = session;
|
||||
this._objectId = payload.objectId;
|
||||
markJSHandle(this, payload);
|
||||
}
|
||||
|
||||
async contentFrame(): Promise<Frame | null> {
|
||||
|
|
@ -392,9 +289,12 @@ export function createHandle(context: ExecutionContext, result: any, exceptionDe
|
|||
const frameManager = frame._delegate as FrameManager;
|
||||
const frameId = frameManager._frameData(frame).frameId;
|
||||
const page = frameManager._page;
|
||||
return new ElementHandle(frame, frameId, page, context, result);
|
||||
const session = (context._delegate as ExecutionContextDelegate)._session;
|
||||
return new ElementHandle(frame, frameId, page, session, context, result);
|
||||
}
|
||||
return new JSHandle(context, result);
|
||||
const handle = new js.JSHandle(context);
|
||||
markJSHandle(handle, result);
|
||||
return handle;
|
||||
}
|
||||
|
||||
function computeQuadArea(quad) {
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ import { Accessibility } from './features/accessibility';
|
|||
import { Interception } from './features/interception';
|
||||
import { FrameManager, FrameManagerEvents, normalizeWaitUntil, Frame } from './FrameManager';
|
||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||
import { createHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||
import { createHandle, ElementHandle } from './JSHandle';
|
||||
import { NavigationWatchdog } from './NavigationWatchdog';
|
||||
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
|
||||
import * as input from '../input';
|
||||
import * as types from '../types';
|
||||
import { JSHandle, toPayload, deserializeValue } from './ExecutionContext';
|
||||
|
||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||
|
||||
|
|
@ -559,8 +560,9 @@ export class Page extends EventEmitter {
|
|||
|
||||
export class ConsoleMessage {
|
||||
private _type: string;
|
||||
private _args: any[];
|
||||
private _args: JSHandle[];
|
||||
private _location: any;
|
||||
|
||||
constructor(type: string, args: Array<JSHandle>, location) {
|
||||
this._type = type;
|
||||
this._args = args;
|
||||
|
|
@ -581,9 +583,10 @@ export class ConsoleMessage {
|
|||
|
||||
text(): string {
|
||||
return this._args.map(arg => {
|
||||
if (arg._objectId)
|
||||
const payload = toPayload(arg);
|
||||
if (payload.objectId)
|
||||
return arg.toString();
|
||||
return arg._deserializeValue(arg._protocolValue);
|
||||
return deserializeValue(payload);
|
||||
}).join(' ');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ export { Keyboard, Mouse } from '../input';
|
|||
export { Browser, BrowserContext, Target } from './Browser';
|
||||
export { BrowserFetcher } from './BrowserFetcher';
|
||||
export { Dialog } from './Dialog';
|
||||
export { ExecutionContext } from '../javascript';
|
||||
export { ExecutionContext, JSHandle } from '../javascript';
|
||||
export { Accessibility } from './features/accessibility';
|
||||
export { Interception } from './features/interception';
|
||||
export { Permissions } from './features/permissions';
|
||||
export { Frame } from './FrameManager';
|
||||
export { ElementHandle, JSHandle } from './JSHandle';
|
||||
export { ElementHandle } from './JSHandle';
|
||||
export { Request, Response } from './NetworkManager';
|
||||
export { ConsoleMessage, FileChooser, Page } from './Page';
|
||||
export { Playwright } from './Playwright';
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ import { TimeoutSettings } from './TimeoutSettings';
|
|||
const readFileAsync = helper.promisify(fs.readFile);
|
||||
|
||||
type WorldType = 'main' | 'utility';
|
||||
type World<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> = {
|
||||
contextPromise: Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>>;
|
||||
contextResolveCallback: (c: js.ExecutionContext<JSHandle, ElementHandle, Response>) => void;
|
||||
context: js.ExecutionContext<JSHandle, ElementHandle, Response> | null;
|
||||
waitTasks: Set<WaitTask<JSHandle, ElementHandle, Response>>;
|
||||
type World<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> = {
|
||||
contextPromise: Promise<js.ExecutionContext<ElementHandle, Response>>;
|
||||
contextResolveCallback: (c: js.ExecutionContext<ElementHandle, Response>) => void;
|
||||
context: js.ExecutionContext<ElementHandle, Response> | null;
|
||||
waitTasks: Set<WaitTask<ElementHandle, Response>>;
|
||||
};
|
||||
|
||||
export type NavigateOptions = {
|
||||
|
|
@ -42,24 +42,24 @@ export type GotoOptions = NavigateOptions & {
|
|||
referer?: string,
|
||||
};
|
||||
|
||||
export interface FrameDelegate<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
|
||||
export interface FrameDelegate<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> {
|
||||
timeoutSettings(): TimeoutSettings;
|
||||
navigateFrame(frame: Frame<JSHandle, ElementHandle, Response>, url: string, options?: GotoOptions): Promise<Response | null>;
|
||||
waitForFrameNavigation(frame: Frame<JSHandle, ElementHandle, Response>, options?: NavigateOptions): Promise<Response | null>;
|
||||
setFrameContent(frame: Frame<JSHandle, ElementHandle, Response>, html: string, options?: NavigateOptions): Promise<void>;
|
||||
adoptElementHandle(elementHandle: ElementHandle, context: js.ExecutionContext<JSHandle, ElementHandle, Response>): Promise<ElementHandle>;
|
||||
navigateFrame(frame: Frame<ElementHandle, Response>, url: string, options?: GotoOptions): Promise<Response | null>;
|
||||
waitForFrameNavigation(frame: Frame<ElementHandle, Response>, options?: NavigateOptions): Promise<Response | null>;
|
||||
setFrameContent(frame: Frame<ElementHandle, Response>, html: string, options?: NavigateOptions): Promise<void>;
|
||||
adoptElementHandle(elementHandle: ElementHandle, context: js.ExecutionContext<ElementHandle, Response>): Promise<ElementHandle>;
|
||||
}
|
||||
|
||||
export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
|
||||
_delegate: FrameDelegate<JSHandle, ElementHandle, Response>;
|
||||
private _parentFrame: Frame<JSHandle, ElementHandle, Response>;
|
||||
export class Frame<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> {
|
||||
_delegate: FrameDelegate<ElementHandle, Response>;
|
||||
private _parentFrame: Frame<ElementHandle, Response>;
|
||||
private _url = '';
|
||||
private _detached = false;
|
||||
private _worlds = new Map<WorldType, World<JSHandle, ElementHandle, Response>>();
|
||||
private _childFrames = new Set<Frame<JSHandle, ElementHandle, Response>>();
|
||||
private _worlds = new Map<WorldType, World<ElementHandle, Response>>();
|
||||
private _childFrames = new Set<Frame<ElementHandle, Response>>();
|
||||
private _name: string;
|
||||
|
||||
constructor(delegate: FrameDelegate<JSHandle, ElementHandle, Response>, parentFrame: Frame<JSHandle, ElementHandle, Response> | null) {
|
||||
constructor(delegate: FrameDelegate<ElementHandle, Response>, parentFrame: Frame<ElementHandle, Response> | null) {
|
||||
this._delegate = delegate;
|
||||
this._parentFrame = parentFrame;
|
||||
|
||||
|
|
@ -80,28 +80,28 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
return this._delegate.waitForFrameNavigation(this, options);
|
||||
}
|
||||
|
||||
_mainContext(): Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>> {
|
||||
_mainContext(): Promise<js.ExecutionContext<ElementHandle, Response>> {
|
||||
if (this._detached)
|
||||
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
||||
return this._worlds.get('main').contextPromise;
|
||||
}
|
||||
|
||||
_utilityContext(): Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>> {
|
||||
_utilityContext(): Promise<js.ExecutionContext<ElementHandle, Response>> {
|
||||
if (this._detached)
|
||||
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
||||
return this._worlds.get('utility').contextPromise;
|
||||
}
|
||||
|
||||
executionContext(): Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>> {
|
||||
executionContext(): Promise<js.ExecutionContext<ElementHandle, Response>> {
|
||||
return this._mainContext();
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandle<JSHandle> = async (pageFunction, ...args) => {
|
||||
evaluateHandle: types.EvaluateHandle<js.JSHandle<ElementHandle, Response>> = async (pageFunction, ...args) => {
|
||||
const context = await this._mainContext();
|
||||
return context.evaluateHandle(pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
evaluate: types.Evaluate<JSHandle> = async (pageFunction, ...args) => {
|
||||
evaluate: types.Evaluate<js.JSHandle<ElementHandle, Response>> = async (pageFunction, ...args) => {
|
||||
const context = await this._mainContext();
|
||||
return context.evaluate(pageFunction, ...args as any);
|
||||
}
|
||||
|
|
@ -118,13 +118,13 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
return document.$x(expression);
|
||||
}
|
||||
|
||||
$eval: types.$Eval<JSHandle> = async (selector, pageFunction, ...args) => {
|
||||
$eval: types.$Eval<js.JSHandle<ElementHandle, Response>> = async (selector, pageFunction, ...args) => {
|
||||
const context = await this._mainContext();
|
||||
const document = await context._document();
|
||||
return document.$eval(selector, pageFunction, ...args as any);
|
||||
}
|
||||
|
||||
$$eval: types.$$Eval<JSHandle> = async (selector, pageFunction, ...args) => {
|
||||
$$eval: types.$$Eval<js.JSHandle<ElementHandle, Response>> = async (selector, pageFunction, ...args) => {
|
||||
const context = await this._mainContext();
|
||||
const document = await context._document();
|
||||
return document.$$eval(selector, pageFunction, ...args as any);
|
||||
|
|
@ -160,11 +160,11 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
return this._url;
|
||||
}
|
||||
|
||||
parentFrame(): Frame<JSHandle, ElementHandle, Response> | null {
|
||||
parentFrame(): Frame<ElementHandle, Response> | null {
|
||||
return this._parentFrame;
|
||||
}
|
||||
|
||||
childFrames(): Frame<JSHandle, ElementHandle, Response>[] {
|
||||
childFrames(): Frame<ElementHandle, Response>[] {
|
||||
return Array.from(this._childFrames);
|
||||
}
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
await handle.dispose();
|
||||
}
|
||||
|
||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: any = {}, ...args: any[]): Promise<JSHandle | null> {
|
||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: any = {}, ...args: any[]): Promise<js.JSHandle<ElementHandle, Response> | null> {
|
||||
const xPathPattern = '//';
|
||||
|
||||
if (helper.isString(selectorOrFunctionOrTimeout)) {
|
||||
|
|
@ -415,7 +415,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
waitForFunction(
|
||||
pageFunction: Function | string,
|
||||
options: { polling?: string | number; timeout?: number; } = {},
|
||||
...args): Promise<JSHandle> {
|
||||
...args): Promise<js.JSHandle<ElementHandle, Response>> {
|
||||
const {
|
||||
polling = 'raf',
|
||||
timeout = this._delegate.timeoutSettings().timeout(),
|
||||
|
|
@ -451,7 +451,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
this._parentFrame = null;
|
||||
}
|
||||
|
||||
private _scheduleWaitTask(params: WaitTaskParams, world: World<JSHandle, ElementHandle, Response>): Promise<JSHandle> {
|
||||
private _scheduleWaitTask(params: WaitTaskParams, world: World<ElementHandle, Response>): Promise<js.JSHandle<ElementHandle, Response>> {
|
||||
const task = new WaitTask(params, () => world.waitTasks.delete(task));
|
||||
world.waitTasks.add(task);
|
||||
if (world.context)
|
||||
|
|
@ -459,7 +459,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
return task.promise;
|
||||
}
|
||||
|
||||
private _setContext(worldType: WorldType, context: js.ExecutionContext<JSHandle, ElementHandle, Response> | null) {
|
||||
private _setContext(worldType: WorldType, context: js.ExecutionContext<ElementHandle, Response> | null) {
|
||||
const world = this._worlds.get(worldType);
|
||||
world.context = context;
|
||||
if (context) {
|
||||
|
|
@ -473,7 +473,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
}
|
||||
}
|
||||
|
||||
_contextCreated(worldType: WorldType, context: js.ExecutionContext<JSHandle, ElementHandle, Response>) {
|
||||
_contextCreated(worldType: WorldType, context: js.ExecutionContext<ElementHandle, Response>) {
|
||||
const world = this._worlds.get(worldType);
|
||||
// In case of multiple sessions to the same target, there's a race between
|
||||
// connections so we might end up creating multiple isolated worlds.
|
||||
|
|
@ -482,14 +482,14 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Resp
|
|||
this._setContext(worldType, context);
|
||||
}
|
||||
|
||||
_contextDestroyed(context: js.ExecutionContext<JSHandle, ElementHandle, Response>) {
|
||||
_contextDestroyed(context: js.ExecutionContext<ElementHandle, Response>) {
|
||||
for (const [worldType, world] of this._worlds) {
|
||||
if (world.context === context)
|
||||
this._setContext(worldType, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async _adoptElementHandle(elementHandle: ElementHandle, context: js.ExecutionContext<JSHandle, ElementHandle, Response>, dispose: boolean): Promise<ElementHandle> {
|
||||
private async _adoptElementHandle(elementHandle: ElementHandle, context: js.ExecutionContext<ElementHandle, Response>, dispose: boolean): Promise<ElementHandle> {
|
||||
if (elementHandle.executionContext() === context)
|
||||
return elementHandle;
|
||||
const handle = this._delegate.adoptElementHandle(elementHandle, context);
|
||||
|
|
|
|||
|
|
@ -7,34 +7,38 @@ import * as injectedSource from './generated/injectedSource';
|
|||
import * as cssSelectorEngineSource from './generated/cssSelectorEngineSource';
|
||||
import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource';
|
||||
|
||||
export interface ExecutionContextDelegate<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
|
||||
evaluate(context: ExecutionContext<JSHandle, ElementHandle, Response>, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;
|
||||
export interface ExecutionContextDelegate<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> {
|
||||
evaluate(context: ExecutionContext<ElementHandle, Response>, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;
|
||||
getProperties(handle: JSHandle<ElementHandle, Response>): Promise<Map<string, JSHandle<ElementHandle, Response>>>;
|
||||
releaseHandle(handle: JSHandle<ElementHandle, Response>): Promise<void>;
|
||||
handleToString(handle: JSHandle<ElementHandle, Response>): string;
|
||||
handleJSONValue(handle: JSHandle<ElementHandle, Response>): Promise<any>;
|
||||
}
|
||||
|
||||
export class ExecutionContext<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
|
||||
_delegate: ExecutionContextDelegate<JSHandle, ElementHandle, Response>;
|
||||
private _frame: frames.Frame<JSHandle, ElementHandle, Response>;
|
||||
private _injectedPromise: Promise<JSHandle> | null = null;
|
||||
export class ExecutionContext<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> {
|
||||
_delegate: ExecutionContextDelegate<ElementHandle, Response>;
|
||||
private _frame: frames.Frame<ElementHandle, Response>;
|
||||
private _injectedPromise: Promise<JSHandle<ElementHandle, Response>> | null = null;
|
||||
private _documentPromise: Promise<ElementHandle> | null = null;
|
||||
|
||||
constructor(delegate: ExecutionContextDelegate<JSHandle, ElementHandle, Response>, frame: frames.Frame<JSHandle, ElementHandle, Response> | null) {
|
||||
constructor(delegate: ExecutionContextDelegate<ElementHandle, Response>, frame: frames.Frame<ElementHandle, Response> | null) {
|
||||
this._delegate = delegate;
|
||||
this._frame = frame;
|
||||
}
|
||||
|
||||
frame(): frames.Frame<JSHandle, ElementHandle, Response> | null {
|
||||
frame(): frames.Frame<ElementHandle, Response> | null {
|
||||
return this._frame;
|
||||
}
|
||||
|
||||
evaluate: types.Evaluate<JSHandle> = (pageFunction, ...args) => {
|
||||
evaluate: types.Evaluate<JSHandle<ElementHandle, Response>> = (pageFunction, ...args) => {
|
||||
return this._delegate.evaluate(this, true /* returnByValue */, pageFunction, ...args);
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandle<JSHandle> = (pageFunction, ...args) => {
|
||||
evaluateHandle: types.EvaluateHandle<JSHandle<ElementHandle, Response>> = (pageFunction, ...args) => {
|
||||
return this._delegate.evaluate(this, false /* returnByValue */, pageFunction, ...args);
|
||||
}
|
||||
|
||||
_injected(): Promise<JSHandle> {
|
||||
_injected(): Promise<JSHandle<ElementHandle, Response>> {
|
||||
if (!this._injectedPromise) {
|
||||
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
|
||||
const source = `
|
||||
|
|
@ -54,3 +58,58 @@ export class ExecutionContext<JSHandle extends types.JSHandle<JSHandle, ElementH
|
|||
}
|
||||
}
|
||||
|
||||
export class JSHandle<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> {
|
||||
_context: ExecutionContext<ElementHandle, Response>;
|
||||
_disposed = false;
|
||||
|
||||
constructor(context: ExecutionContext<ElementHandle, Response>) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
executionContext(): ExecutionContext<ElementHandle, Response> {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
evaluate: types.EvaluateOn<JSHandle<ElementHandle, Response>> = (pageFunction, ...args) => {
|
||||
return this._context.evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandleOn<JSHandle<ElementHandle, Response>> = (pageFunction, ...args) => {
|
||||
return this._context.evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
async getProperty(propertyName: string): Promise<JSHandle<ElementHandle, Response> | null> {
|
||||
const objectHandle = await this.evaluateHandle((object, propertyName) => {
|
||||
const result = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
}, propertyName);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName) || null;
|
||||
await objectHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
getProperties(): Promise<Map<string, JSHandle<ElementHandle, Response>>> {
|
||||
return this._context._delegate.getProperties(this);
|
||||
}
|
||||
|
||||
jsonValue(): Promise<any> {
|
||||
return this._context._delegate.handleJSONValue(this);
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this._disposed)
|
||||
return;
|
||||
this._disposed = true;
|
||||
await this._context._delegate.releaseHandle(this);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this._context._delegate.handleToString(this);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
src/types.ts
12
src/types.ts
|
|
@ -15,18 +15,12 @@ export type $$Eval<Handle> = <Args extends any[], R>(selector: string, pageFunct
|
|||
export type EvaluateOn<Handle> = <Args extends any[], R>(pageFunction: PageFunctionOn<any, Args, R>, ...args: Boxed<Args, Handle>) => Promise<R>;
|
||||
export type EvaluateHandleOn<Handle> = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args, Handle>) => Promise<Handle>;
|
||||
|
||||
export interface JSHandle<Handle extends JSHandle<Handle, EHandle, Response>, EHandle extends ElementHandle<Handle, EHandle, Response>, Response> {
|
||||
executionContext(): js.ExecutionContext<Handle, EHandle, Response>;
|
||||
dispose(): Promise<void>;
|
||||
asElement(): EHandle | null;
|
||||
}
|
||||
|
||||
export interface ElementHandle<Handle extends JSHandle<Handle, EHandle, Response>, EHandle extends ElementHandle<Handle, EHandle, Response>, Response> extends JSHandle<Handle, EHandle, Response> {
|
||||
export interface ElementHandle<EHandle extends ElementHandle<EHandle, Response>, Response> extends js.JSHandle<EHandle, Response> {
|
||||
$(selector: string): Promise<EHandle | null>;
|
||||
$x(expression: string): Promise<EHandle[]>;
|
||||
$$(selector: string): Promise<EHandle[]>;
|
||||
$eval: $Eval<Handle>;
|
||||
$$eval: $$Eval<Handle>;
|
||||
$eval: $Eval<js.JSHandle<EHandle, Response>>;
|
||||
$$eval: $$Eval<js.JSHandle<EHandle, Response>>;
|
||||
click(options?: input.ClickOptions): Promise<void>;
|
||||
dblclick(options?: input.MultiClickOptions): Promise<void>;
|
||||
tripleclick(options?: input.MultiClickOptions): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ export type WaitTaskParams = {
|
|||
args: any[];
|
||||
};
|
||||
|
||||
export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
|
||||
readonly promise: Promise<JSHandle>;
|
||||
export class WaitTask<ElementHandle extends types.ElementHandle<ElementHandle, Response>, Response> {
|
||||
readonly promise: Promise<js.JSHandle<ElementHandle, Response>>;
|
||||
private _cleanup: () => void;
|
||||
private _params: WaitTaskParams & { predicateBody: string };
|
||||
private _runCount: number;
|
||||
private _resolve: (result: JSHandle) => void;
|
||||
private _resolve: (result: js.JSHandle<ElementHandle, Response>) => void;
|
||||
private _reject: (reason: Error) => void;
|
||||
private _timeoutTimer: NodeJS.Timer;
|
||||
private _terminated: boolean;
|
||||
|
|
@ -39,7 +39,7 @@ export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle, R
|
|||
};
|
||||
this._cleanup = cleanup;
|
||||
this._runCount = 0;
|
||||
this.promise = new Promise<JSHandle>((resolve, reject) => {
|
||||
this.promise = new Promise<js.JSHandle<ElementHandle, Response>>((resolve, reject) => {
|
||||
this._resolve = resolve;
|
||||
this._reject = reject;
|
||||
});
|
||||
|
|
@ -57,9 +57,9 @@ export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle, R
|
|||
this._doCleanup();
|
||||
}
|
||||
|
||||
async rerun(context: js.ExecutionContext<JSHandle, ElementHandle, Response>) {
|
||||
async rerun(context: js.ExecutionContext<ElementHandle, Response>) {
|
||||
const runCount = ++this._runCount;
|
||||
let success: JSHandle | null = null;
|
||||
let success: js.JSHandle<ElementHandle, Response> | null = null;
|
||||
let error = null;
|
||||
try {
|
||||
success = await context.evaluateHandle(waitForPredicatePageFunction, this._params.predicateBody, this._params.polling, this._params.timeout, ...this._params.args);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
import { TargetSession } from './Connection';
|
||||
import { helper } from '../helper';
|
||||
import { valueFromRemoteObject } from './protocolHelper';
|
||||
import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
|
||||
import { valueFromRemoteObject, releaseObject } from './protocolHelper';
|
||||
import { createJSHandle, ElementHandle } from './JSHandle';
|
||||
import { Protocol } from './protocol';
|
||||
import { Response } from './NetworkManager';
|
||||
import * as js from '../javascript';
|
||||
|
|
@ -26,9 +26,10 @@ import * as js from '../javascript';
|
|||
export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
|
||||
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||
|
||||
export type ExecutionContext = js.ExecutionContext<JSHandle, ElementHandle, Response>;
|
||||
export type ExecutionContext = js.ExecutionContext<ElementHandle, Response>;
|
||||
export type JSHandle = js.JSHandle<ElementHandle, Response>;
|
||||
|
||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSHandle, ElementHandle, Response> {
|
||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate<ElementHandle, Response> {
|
||||
private _globalObjectId?: string;
|
||||
_session: TargetSession;
|
||||
private _contextId: number;
|
||||
|
|
@ -218,15 +219,16 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
}).catch(rewriteError);
|
||||
|
||||
function convertArgument(this: ExecutionContext, arg: JSHandle | any) : Protocol.Runtime.CallArgument{
|
||||
const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
|
||||
const objectHandle = arg && (arg instanceof js.JSHandle) ? arg : null;
|
||||
if (objectHandle) {
|
||||
if (objectHandle._context !== this)
|
||||
throw new Error('JSHandles can be evaluated only in the context they were created!');
|
||||
if (objectHandle._disposed)
|
||||
throw new Error('JSHandle is disposed!');
|
||||
if (!objectHandle._remoteObject.objectId)
|
||||
return { value: valueFromRemoteObject(objectHandle._remoteObject) };
|
||||
return { objectId: objectHandle._remoteObject.objectId };
|
||||
const remoteObject = toRemoteObject(arg);
|
||||
if (!remoteObject.objectId)
|
||||
return { value: valueFromRemoteObject(remoteObject) };
|
||||
return { objectId: remoteObject.objectId };
|
||||
}
|
||||
return { value: arg };
|
||||
}
|
||||
|
|
@ -240,8 +242,8 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
return '-Infinity';
|
||||
if (Object.is(arg, NaN))
|
||||
return 'NaN';
|
||||
if (arg instanceof JSHandle) {
|
||||
const remoteObj = arg._remoteObject;
|
||||
if (arg instanceof js.JSHandle) {
|
||||
const remoteObj = toRemoteObject(arg);
|
||||
if (!remoteObj.objectId)
|
||||
return valueFromRemoteObject(remoteObj);
|
||||
}
|
||||
|
|
@ -259,8 +261,8 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
return true;
|
||||
if (Object.is(arg, NaN))
|
||||
return true;
|
||||
if (arg instanceof JSHandle) {
|
||||
const remoteObj = arg._remoteObject;
|
||||
if (arg instanceof js.JSHandle) {
|
||||
const remoteObj = toRemoteObject(arg);
|
||||
if (!remoteObj.objectId)
|
||||
return !Object.is(valueFromRemoteObject(remoteObj), remoteObj.value);
|
||||
}
|
||||
|
|
@ -288,4 +290,57 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSH
|
|||
}
|
||||
return this._globalObjectId;
|
||||
}
|
||||
|
||||
async getProperties(handle: JSHandle): Promise<Map<string, JSHandle>> {
|
||||
const response = await this._session.send('Runtime.getProperties', {
|
||||
objectId: toRemoteObject(handle).objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.properties) {
|
||||
if (!property.enumerable)
|
||||
continue;
|
||||
result.set(property.name, createJSHandle(handle.executionContext(), property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async releaseHandle(handle: JSHandle): Promise<void> {
|
||||
await releaseObject(this._session, toRemoteObject(handle));
|
||||
}
|
||||
|
||||
async handleJSONValue(handle: JSHandle): Promise<any> {
|
||||
const remoteObject = toRemoteObject(handle);
|
||||
if (remoteObject.objectId) {
|
||||
const response = await this._session.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: 'function() { return this; }',
|
||||
objectId: remoteObject.objectId,
|
||||
returnByValue: true
|
||||
});
|
||||
return valueFromRemoteObject(response.result);
|
||||
}
|
||||
return valueFromRemoteObject(remoteObject);
|
||||
}
|
||||
|
||||
handleToString(handle: JSHandle): string {
|
||||
const object = toRemoteObject(handle);
|
||||
if (object.objectId) {
|
||||
let type: string = object.subtype || object.type;
|
||||
// FIXME: promise doesn't have special subtype in WebKit.
|
||||
if (object.className === 'Promise')
|
||||
type = 'promise';
|
||||
return 'JSHandle@' + type;
|
||||
}
|
||||
return 'JSHandle:' + valueFromRemoteObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteObjectSymbol = Symbol('RemoteObject');
|
||||
|
||||
export function toRemoteObject(handle: JSHandle): Protocol.Runtime.RemoteObject {
|
||||
return (handle as any)[remoteObjectSymbol];
|
||||
}
|
||||
|
||||
export function markJSHandle(handle: JSHandle, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||
(handle as any)[remoteObjectSymbol] = remoteObject;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import { Events } from './events';
|
|||
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||
import { TimeoutSettings } from '../TimeoutSettings';
|
||||
import { TargetSession } from './Connection';
|
||||
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { ElementHandle, JSHandle } from './JSHandle';
|
||||
import { JSHandle, ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { ElementHandle } from './JSHandle';
|
||||
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
|
|
@ -42,9 +42,9 @@ type FrameData = {
|
|||
id: string,
|
||||
};
|
||||
|
||||
export type Frame = frames.Frame<JSHandle, ElementHandle, Response>;
|
||||
export type Frame = frames.Frame<ElementHandle, Response>;
|
||||
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, Response> {
|
||||
export class FrameManager extends EventEmitter implements frames.FrameDelegate<ElementHandle, Response> {
|
||||
_session: TargetSession;
|
||||
_page: Page;
|
||||
_networkManager: NetworkManager;
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ import * as fs from 'fs';
|
|||
import { assert, debugError, helper } from '../helper';
|
||||
import * as input from '../input';
|
||||
import { TargetSession } from './Connection';
|
||||
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
|
||||
import { JSHandle, ExecutionContext, ExecutionContextDelegate, markJSHandle } from './ExecutionContext';
|
||||
import { Response } from './NetworkManager';
|
||||
import { FrameManager } from './FrameManager';
|
||||
import { Page } from './Page';
|
||||
import { Protocol } from './protocol';
|
||||
import { releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||
import Injected from '../injected/injected';
|
||||
import * as types from '../types';
|
||||
import * as js from '../javascript';
|
||||
|
||||
type SelectorRoot = Element | ShadowRoot | Document;
|
||||
|
||||
|
|
@ -38,104 +39,24 @@ export function createJSHandle(context: ExecutionContext, remoteObject: Protocol
|
|||
const frameManager = frame._delegate as FrameManager;
|
||||
return new ElementHandle(context, delegate._session, remoteObject, frameManager.page(), frameManager);
|
||||
}
|
||||
return new JSHandle(context, delegate._session, remoteObject);
|
||||
const handle = new js.JSHandle(context);
|
||||
markJSHandle(handle, remoteObject);
|
||||
return handle;
|
||||
}
|
||||
|
||||
export class JSHandle {
|
||||
_context: ExecutionContext;
|
||||
protected _client: TargetSession;
|
||||
_remoteObject: Protocol.Runtime.RemoteObject;
|
||||
_disposed = false;
|
||||
|
||||
constructor(context: ExecutionContext, client: TargetSession, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||
this._context = context;
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
}
|
||||
|
||||
executionContext(): ExecutionContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
evaluate: types.EvaluateOn<JSHandle> = (pageFunction, ...args) => {
|
||||
return this.executionContext().evaluate(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
evaluateHandle: types.EvaluateHandleOn<JSHandle> = (pageFunction, ...args) => {
|
||||
return this.executionContext().evaluateHandle(pageFunction, this, ...args);
|
||||
}
|
||||
|
||||
async getProperty(propertyName: string): Promise<JSHandle | null> {
|
||||
const objectHandle = await this.evaluateHandle((object, propertyName) => {
|
||||
const result = {__proto__: null};
|
||||
result[propertyName] = object[propertyName];
|
||||
return result;
|
||||
}, propertyName);
|
||||
const properties = await objectHandle.getProperties();
|
||||
const result = properties.get(propertyName) || null;
|
||||
await objectHandle.dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
async getProperties(): Promise<Map<string, JSHandle>> {
|
||||
const response = await this._client.send('Runtime.getProperties', {
|
||||
objectId: this._remoteObject.objectId,
|
||||
ownProperties: true
|
||||
});
|
||||
const result = new Map();
|
||||
for (const property of response.properties) {
|
||||
if (!property.enumerable)
|
||||
continue;
|
||||
result.set(property.name, createJSHandle(this._context, property.value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async jsonValue(): Promise<object | null> {
|
||||
if (this._remoteObject.objectId) {
|
||||
const response = await this._client.send('Runtime.callFunctionOn', {
|
||||
functionDeclaration: 'function() { return this; }',
|
||||
objectId: this._remoteObject.objectId,
|
||||
returnByValue: true
|
||||
});
|
||||
return valueFromRemoteObject(response.result);
|
||||
}
|
||||
return valueFromRemoteObject(this._remoteObject);
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
if (this._disposed)
|
||||
return;
|
||||
this._disposed = true;
|
||||
await releaseObject(this._client, this._remoteObject);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
if (this._remoteObject.objectId) {
|
||||
let type: string = this._remoteObject.subtype || this._remoteObject.type;
|
||||
// FIXME: promise doesn't have special subtype in WebKit.
|
||||
if (this._remoteObject.className === 'Promise')
|
||||
type = 'promise';
|
||||
return 'JSHandle@' + type;
|
||||
}
|
||||
return 'JSHandle:' + valueFromRemoteObject(this._remoteObject);
|
||||
}
|
||||
}
|
||||
|
||||
export class ElementHandle extends JSHandle {
|
||||
export class ElementHandle extends js.JSHandle<ElementHandle, Response> {
|
||||
private _client: TargetSession;
|
||||
private _remoteObject: Protocol.Runtime.RemoteObject;
|
||||
private _page: Page;
|
||||
private _frameManager: FrameManager;
|
||||
|
||||
constructor(context: ExecutionContext, client: TargetSession, remoteObject: Protocol.Runtime.RemoteObject, page: Page, frameManager: FrameManager) {
|
||||
super(context, client, remoteObject);
|
||||
super(context);
|
||||
this._client = client;
|
||||
this._remoteObject = remoteObject;
|
||||
this._page = page;
|
||||
this._frameManager = frameManager;
|
||||
markJSHandle(this, remoteObject);
|
||||
}
|
||||
|
||||
asElement(): ElementHandle | null {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ import { TargetSession, TargetSessionEvents } from './Connection';
|
|||
import { Events } from './events';
|
||||
import { Frame, FrameManager, FrameManagerEvents } from './FrameManager';
|
||||
import { RawKeyboardImpl, RawMouseImpl } from './Input';
|
||||
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||
import { createJSHandle, ElementHandle } from './JSHandle';
|
||||
import { JSHandle, toRemoteObject } from './ExecutionContext';
|
||||
import { NetworkManagerEvents, Response } from './NetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { valueFromRemoteObject } from './protocolHelper';
|
||||
|
|
@ -179,7 +180,7 @@ export class Page extends EventEmitter {
|
|||
});
|
||||
const textTokens = [];
|
||||
for (const handle of handles) {
|
||||
const remoteObject = handle._remoteObject;
|
||||
const remoteObject = toRemoteObject(handle);
|
||||
if (remoteObject.objectId)
|
||||
textTokens.push(handle.toString());
|
||||
else
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
export { TimeoutError } from '../Errors';
|
||||
export { Browser, BrowserContext } from './Browser';
|
||||
export { BrowserFetcher } from './BrowserFetcher';
|
||||
export { ExecutionContext } from '../javascript';
|
||||
export { ExecutionContext, JSHandle } from '../javascript';
|
||||
export { Frame } from './FrameManager';
|
||||
export { Mouse, Keyboard } from '../input';
|
||||
export { ElementHandle, JSHandle } from './JSHandle';
|
||||
export { ElementHandle } from './JSHandle';
|
||||
export { Request, Response } from './NetworkManager';
|
||||
export { ConsoleMessage, Page } from './Page';
|
||||
export { Playwright } from './Playwright';
|
||||
|
|
|
|||
Loading…
Reference in a new issue