chore: reuse ExecutionContext between browsers (#102)

This commit is contained in:
Dmitry Gozman 2019-11-27 12:39:53 -08:00 committed by GitHub
parent dfc5592910
commit 06ba0f7a7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 185 additions and 222 deletions

View file

@ -16,45 +16,28 @@
*/ */
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { Frame } from './FrameManager';
import { helper } from '../helper'; import { helper } from '../helper';
import { valueFromRemoteObject, getExceptionMessage } from './protocolHelper'; import { valueFromRemoteObject, getExceptionMessage } from './protocolHelper';
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle'; import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as injectedSource from '../generated/injectedSource'; import { Response } from './NetworkManager';
import * as cssSelectorEngineSource from '../generated/cssSelectorEngineSource'; import * as js from '../javascript';
import * as xpathSelectorEngineSource from '../generated/xpathSelectorEngineSource';
import * as types from '../types';
export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__'; export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
export class ExecutionContext { export type ExecutionContext = js.ExecutionContext<JSHandle, ElementHandle, Response>;
_client: CDPSession;
private _frame: Frame;
private _injectedPromise: Promise<JSHandle> | null = null;
private _documentPromise: Promise<ElementHandle> | null = null;
private _contextId: number;
constructor(client: CDPSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, frame: Frame | null) { export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSHandle, ElementHandle, Response> {
_client: CDPSession;
_contextId: number;
constructor(client: CDPSession, contextPayload: Protocol.Runtime.ExecutionContextDescription) {
this._client = client; this._client = client;
this._frame = frame;
this._contextId = contextPayload.id; this._contextId = contextPayload.id;
} }
frame(): Frame | null { async evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
return this._frame;
}
evaluate: types.Evaluate<JSHandle> = (pageFunction, ...args) => {
return this._evaluateInternal(true /* returnByValue */, pageFunction, ...args);
}
evaluateHandle: types.EvaluateHandle<JSHandle> = (pageFunction, ...args) => {
return this._evaluateInternal(false /* returnByValue */, pageFunction, ...args);
}
async _evaluateInternal(returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`; const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
if (helper.isString(pageFunction)) { if (helper.isString(pageFunction)) {
@ -70,7 +53,7 @@ export class ExecutionContext {
}).catch(rewriteError); }).catch(rewriteError);
if (exceptionDetails) if (exceptionDetails)
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails)); throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
return returnByValue ? valueFromRemoteObject(remoteObject) : createJSHandle(this, remoteObject); return returnByValue ? valueFromRemoteObject(remoteObject) : createJSHandle(context, remoteObject);
} }
if (typeof pageFunction !== 'function') if (typeof pageFunction !== 'function')
@ -111,7 +94,7 @@ export class ExecutionContext {
const { exceptionDetails, result: remoteObject } = await callFunctionOnPromise.catch(rewriteError); const { exceptionDetails, result: remoteObject } = await callFunctionOnPromise.catch(rewriteError);
if (exceptionDetails) if (exceptionDetails)
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails)); throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
return returnByValue ? valueFromRemoteObject(remoteObject) : createJSHandle(this, remoteObject); return returnByValue ? valueFromRemoteObject(remoteObject) : createJSHandle(context, remoteObject);
function convertArgument(arg: any): any { function convertArgument(arg: any): any {
if (typeof arg === 'bigint') // eslint-disable-line valid-typeof if (typeof arg === 'bigint') // eslint-disable-line valid-typeof
@ -126,7 +109,7 @@ export class ExecutionContext {
return { unserializableValue: 'NaN' }; return { unserializableValue: 'NaN' };
const objectHandle = arg && (arg instanceof JSHandle) ? arg : null; const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
if (objectHandle) { if (objectHandle) {
if (objectHandle._context !== this) if (objectHandle._context !== context)
throw new Error('JSHandles can be evaluated only in the context they were created!'); throw new Error('JSHandles can be evaluated only in the context they were created!');
if (objectHandle._disposed) if (objectHandle._disposed)
throw new Error('JSHandle is disposed!'); throw new Error('JSHandle is disposed!');
@ -151,30 +134,11 @@ export class ExecutionContext {
} }
} }
async _adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId): Promise<ElementHandle> { async adoptBackendNodeId(context: ExecutionContext, backendNodeId: Protocol.DOM.BackendNodeId) {
const {object} = await this._client.send('DOM.resolveNode', { const {object} = await this._client.send('DOM.resolveNode', {
backendNodeId, backendNodeId,
executionContextId: this._contextId, executionContextId: this._contextId,
}); });
return createJSHandle(this, object) as ElementHandle; return createJSHandle(context, object) as ElementHandle;
}
_injected(): Promise<JSHandle> {
if (!this._injectedPromise) {
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
const source = `
new (${injectedSource.source})([
${engineSources.join(',\n')}
])
`;
this._injectedPromise = this.evaluateHandle(source);
}
return this._injectedPromise;
}
_document(): Promise<ElementHandle> {
if (!this._documentPromise)
this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement()!);
return this._documentPromise;
} }
} }

View file

@ -19,13 +19,14 @@ import { EventEmitter } from 'events';
import { assert, debugError } from '../helper'; import { assert, debugError } from '../helper';
import { TimeoutSettings } from '../TimeoutSettings'; import { TimeoutSettings } from '../TimeoutSettings';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { EVALUATION_SCRIPT_URL, ExecutionContext } from './ExecutionContext'; import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate, ExecutionContext } from './ExecutionContext';
import * as frames from '../frames'; import * as frames from '../frames';
import * as js from '../javascript';
import { LifecycleWatcher } from './LifecycleWatcher'; import { LifecycleWatcher } from './LifecycleWatcher';
import { NetworkManager, Response } from './NetworkManager'; import { NetworkManager, Response } from './NetworkManager';
import { Page } from './Page'; import { Page } from './Page';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { ElementHandle, JSHandle } from './JSHandle'; import { ElementHandle, JSHandle, createJSHandle } from './JSHandle';
const UTILITY_WORLD_NAME = '__playwright_utility_world__'; const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -44,9 +45,9 @@ type FrameData = {
lifecycleEvents: Set<string>, lifecycleEvents: Set<string>,
}; };
export type Frame = frames.Frame<JSHandle, ElementHandle, ExecutionContext, Response>; export type Frame = frames.Frame<JSHandle, ElementHandle, Response>;
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response> { export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, Response> {
_client: CDPSession; _client: CDPSession;
private _page: Page; private _page: Page;
private _networkManager: NetworkManager; private _networkManager: NetworkManager;
@ -186,7 +187,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
const nodeInfo = await this._client.send('DOM.describeNode', { const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: elementHandle._remoteObject.objectId, objectId: elementHandle._remoteObject.objectId,
}); });
return context._adoptBackendNodeId(nodeInfo.node.backendNodeId); return (context._delegate as ExecutionContextDelegate).adoptBackendNodeId(context, nodeInfo.node.backendNodeId);
} }
_onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload) { _onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload) {
@ -328,7 +329,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
const frame = this._frames.get(frameId) || null; const frame = this._frames.get(frameId) || null;
if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated') if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
this._isolatedWorlds.add(contextPayload.name); this._isolatedWorlds.add(contextPayload.name);
const context: ExecutionContext = new ExecutionContext(this._client, contextPayload, frame); const context: ExecutionContext = new js.ExecutionContext(new ExecutionContextDelegate(this._client, contextPayload), frame);
if (frame) { if (frame) {
if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) if (contextPayload.auxData && !!contextPayload.auxData['isDefault'])
frame._contextCreated('main', context); frame._contextCreated('main', context);

View file

@ -20,12 +20,12 @@ import Injected from '../injected/injected';
import * as input from '../input'; import * as input from '../input';
import * as types from '../types'; import * as types from '../types';
import { CDPSession } from './Connection'; import { CDPSession } from './Connection';
import { ExecutionContext } from './ExecutionContext';
import { Frame } from './FrameManager'; import { Frame } from './FrameManager';
import { FrameManager } from './FrameManager'; import { FrameManager } from './FrameManager';
import { Page } from './Page'; import { Page } from './Page';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { releaseObject, valueFromRemoteObject } from './protocolHelper'; import { releaseObject, valueFromRemoteObject } from './protocolHelper';
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
type SelectorRoot = Element | ShadowRoot | Document; type SelectorRoot = Element | ShadowRoot | Document;
@ -35,12 +35,13 @@ type Point = {
}; };
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): JSHandle { export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): JSHandle {
const delegate = context._delegate as ExecutionContextDelegate;
const frame = context.frame(); const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) { if (remoteObject.subtype === 'node' && frame) {
const frameManager = frame._delegate as FrameManager; const frameManager = frame._delegate as FrameManager;
return new ElementHandle(context, context._client, remoteObject, frameManager.page(), frameManager); return new ElementHandle(context, delegate._client, remoteObject, frameManager.page(), frameManager);
} }
return new JSHandle(context, context._client, remoteObject); return new JSHandle(context, delegate._client, remoteObject);
} }
export class JSHandle { export class JSHandle {

View file

@ -44,6 +44,7 @@ import { Target } from './Target';
import { TaskQueue } from './TaskQueue'; import { TaskQueue } from './TaskQueue';
import * as input from '../input'; import * as input from '../input';
import * as types from '../types'; import * as types from '../types';
import { ExecutionContextDelegate } from './ExecutionContext';
const writeFileAsync = helper.promisify(fs.writeFile); const writeFileAsync = helper.promisify(fs.writeFile);
@ -155,7 +156,7 @@ export class Page extends EventEmitter {
return; return;
const frame = this._frameManager.frame(event.frameId); const frame = this._frameManager.frame(event.frameId);
const context = await frame._utilityContext(); const context = await frame._utilityContext();
const handle = await context._adoptBackendNodeId(event.backendNodeId); const handle = await (context._delegate as ExecutionContextDelegate).adoptBackendNodeId(context, event.backendNodeId);
const interceptors = Array.from(this._fileChooserInterceptors); const interceptors = Array.from(this._fileChooserInterceptors);
this._fileChooserInterceptors.clear(); this._fileChooserInterceptors.clear();
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple); const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);

View file

@ -8,7 +8,7 @@ export { BrowserFetcher } from './BrowserFetcher';
export { Chromium } from './features/chromium'; export { Chromium } from './features/chromium';
export { CDPSession } from './Connection'; export { CDPSession } from './Connection';
export { Dialog } from './Dialog'; export { Dialog } from './Dialog';
export { ExecutionContext } from './ExecutionContext'; export { ExecutionContext } from '../javascript';
export { Accessibility } from './features/accessibility'; export { Accessibility } from './features/accessibility';
export { Coverage } from './features/coverage'; export { Coverage } from './features/coverage';
export { Overrides } from './features/overrides'; export { Overrides } from './features/overrides';

View file

@ -16,12 +16,13 @@
*/ */
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { CDPSession, Connection } from '../Connection'; import { CDPSession, Connection } from '../Connection';
import { ExecutionContext } from '../ExecutionContext';
import { debugError } from '../../helper'; import { debugError } from '../../helper';
import { JSHandle } from '../JSHandle'; import { JSHandle } from '../JSHandle';
import { Protocol } from '../protocol'; import { Protocol } from '../protocol';
import { Events } from '../events'; import { Events } from '../events';
import * as types from '../../types'; import * as types from '../../types';
import * as js from '../../javascript';
import { ExecutionContext, ExecutionContextDelegate } from '../ExecutionContext';
type AddToConsoleCallback = (type: string, args: JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void; type AddToConsoleCallback = (type: string, args: JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void;
type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void; type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void;
@ -68,7 +69,7 @@ export class Worker extends EventEmitter {
let jsHandleFactory: (o: Protocol.Runtime.RemoteObject) => JSHandle; let jsHandleFactory: (o: Protocol.Runtime.RemoteObject) => JSHandle;
this._client.once('Runtime.executionContextCreated', async event => { this._client.once('Runtime.executionContextCreated', async event => {
jsHandleFactory = remoteObject => new JSHandle(executionContext, client, remoteObject); jsHandleFactory = remoteObject => new JSHandle(executionContext, client, remoteObject);
const executionContext = new ExecutionContext(client, event.context, null); const executionContext = new js.ExecutionContext(new ExecutionContextDelegate(client, event.context), null);
this._executionContextCallback(executionContext); this._executionContextCallback(executionContext);
}); });
// This might fail if the target is closed before we recieve all execution contexts. // This might fail if the target is closed before we recieve all execution contexts.

View file

@ -16,33 +16,42 @@
*/ */
import {helper} from '../helper'; import {helper} from '../helper';
import {JSHandle, createHandle, ElementHandle} from './JSHandle'; import { JSHandle, createHandle, ElementHandle } from './JSHandle';
import { Frame } from './FrameManager'; import { Response } from './NetworkManager';
import * as injectedSource from '../generated/injectedSource'; import * as js from '../javascript';
import * as cssSelectorEngineSource from '../generated/cssSelectorEngineSource'; import { JugglerSession } from './Connection';
import * as xpathSelectorEngineSource from '../generated/xpathSelectorEngineSource';
import * as types from '../types';
export class ExecutionContext { export type ExecutionContext = js.ExecutionContext<JSHandle, ElementHandle, Response>;
_session: any;
_frame: Frame; export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSHandle, ElementHandle, Response> {
_session: JugglerSession;
_executionContextId: string; _executionContextId: string;
private _injectedPromise: Promise<JSHandle> | null = null;
private _documentPromise: Promise<ElementHandle> | null = null;
constructor(session: any, frame: Frame | null, executionContextId: string) { constructor(session: JugglerSession, executionContextId: string) {
this._session = session; this._session = session;
this._frame = frame;
this._executionContextId = executionContextId; this._executionContextId = executionContextId;
} }
evaluateHandle: types.EvaluateHandle<JSHandle> = async (pageFunction, ...args) => { async evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
if (returnByValue) {
try {
const handle = await this.evaluate(context, false /* returnByValue */, pageFunction, ...args as any);
const result = await handle.jsonValue();
await handle.dispose();
return result;
} catch (e) {
if (e.message.includes('cyclic object value') || e.message.includes('Object is not serializable'))
return undefined;
throw e;
}
}
if (helper.isString(pageFunction)) { if (helper.isString(pageFunction)) {
const payload = await this._session.send('Runtime.evaluate', { const payload = await this._session.send('Runtime.evaluate', {
expression: pageFunction.trim(), expression: pageFunction.trim(),
executionContextId: this._executionContextId, executionContextId: this._executionContextId,
}).catch(rewriteError); }).catch(rewriteError);
return createHandle(this, payload.result, payload.exceptionDetails); return createHandle(context, payload.result, payload.exceptionDetails);
} }
if (typeof pageFunction !== 'function') if (typeof pageFunction !== 'function')
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`); throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
@ -66,7 +75,7 @@ export class ExecutionContext {
} }
const protocolArgs = args.map(arg => { const protocolArgs = args.map(arg => {
if (arg instanceof JSHandle) { if (arg instanceof JSHandle) {
if (arg._context !== this) if (arg._context !== context)
throw new Error('JSHandles can be evaluated only in the context they were created!'); throw new Error('JSHandles can be evaluated only in the context they were created!');
if (arg._disposed) if (arg._disposed)
throw new Error('JSHandle is disposed!'); throw new Error('JSHandle is disposed!');
@ -95,7 +104,7 @@ export class ExecutionContext {
throw err; throw err;
} }
const payload = await callFunctionPromise.catch(rewriteError); const payload = await callFunctionPromise.catch(rewriteError);
return createHandle(this, payload.result, payload.exceptionDetails); return createHandle(context, payload.result, payload.exceptionDetails);
function rewriteError(error) { function rewriteError(error) {
if (error.message.includes('Failed to find execution context with id')) if (error.message.includes('Failed to find execution context with id'))
@ -103,40 +112,4 @@ export class ExecutionContext {
throw error; throw error;
} }
} }
frame() {
return this._frame;
}
evaluate: types.Evaluate<JSHandle> = async (pageFunction, ...args) => {
try {
const handle = await this.evaluateHandle(pageFunction, ...args as any);
const result = await handle.jsonValue();
await handle.dispose();
return result;
} catch (e) {
if (e.message.includes('cyclic object value') || e.message.includes('Object is not serializable'))
return undefined;
throw e;
}
}
_injected(): Promise<JSHandle> {
if (!this._injectedPromise) {
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
const source = `
new (${injectedSource.source})([
${engineSources.join(',\n')}
])
`;
this._injectedPromise = this.evaluateHandle(source);
}
return this._injectedPromise;
}
_document(): Promise<ElementHandle> {
if (!this._documentPromise)
this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement()!);
return this._documentPromise;
}
} }

View file

@ -20,11 +20,13 @@ import { Page } from './Page';
import {RegisteredListener, helper, assert} from '../helper'; import {RegisteredListener, helper, assert} from '../helper';
import {TimeoutError} from '../Errors'; import {TimeoutError} from '../Errors';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import {ExecutionContext} from './ExecutionContext'; import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
import {NavigationWatchdog, NextNavigationWatchdog} from './NavigationWatchdog'; import {NavigationWatchdog, NextNavigationWatchdog} from './NavigationWatchdog';
import { JSHandle, ElementHandle } from './JSHandle'; import { JSHandle, ElementHandle } from './JSHandle';
import { TimeoutSettings } from '../TimeoutSettings'; import { TimeoutSettings } from '../TimeoutSettings';
import { Response } from './NetworkManager';
import * as frames from '../frames'; import * as frames from '../frames';
import * as js from '../javascript';
export const FrameManagerEvents = { export const FrameManagerEvents = {
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'), FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
@ -41,9 +43,9 @@ type FrameData = {
firedEvents: Set<string>, firedEvents: Set<string>,
}; };
export type Frame = frames.Frame<JSHandle, ElementHandle, ExecutionContext, Response>; export type Frame = frames.Frame<JSHandle, ElementHandle, Response>;
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response> { export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, Response> {
_session: JugglerSession; _session: JugglerSession;
_page: Page; _page: Page;
_networkManager: any; _networkManager: any;
@ -80,7 +82,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
_onExecutionContextCreated({executionContextId, auxData}) { _onExecutionContextCreated({executionContextId, auxData}) {
const frameId = auxData ? auxData.frameId : null; const frameId = auxData ? auxData.frameId : null;
const frame = this._frames.get(frameId) || null; const frame = this._frames.get(frameId) || null;
const context = new ExecutionContext(this._session, frame, executionContextId); const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId), frame);
if (frame) { if (frame) {
frame._contextCreated('main', context); frame._contextCreated('main', context);
frame._contextCreated('utility', context); frame._contextCreated('utility', context);

View file

@ -20,9 +20,9 @@ import Injected from '../injected/injected';
import * as input from '../input'; import * as input from '../input';
import * as types from '../types'; import * as types from '../types';
import { JugglerSession } from './Connection'; import { JugglerSession } from './Connection';
import { ExecutionContext } from './ExecutionContext';
import { Frame, FrameManager } from './FrameManager'; import { Frame, FrameManager } from './FrameManager';
import { Page } from './Page'; import { Page } from './Page';
import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
type SelectorRoot = Element | ShadowRoot | Document; type SelectorRoot = Element | ShadowRoot | Document;
@ -38,8 +38,9 @@ export class JSHandle {
constructor(context: ExecutionContext, payload: any) { constructor(context: ExecutionContext, payload: any) {
this._context = context; this._context = context;
this._session = this._context._session; const delegate = context._delegate as ExecutionContextDelegate;
this._executionContextId = this._context._executionContextId; this._session = delegate._session;
this._executionContextId = delegate._executionContextId;
this._objectId = payload.objectId; this._objectId = payload.objectId;
this._type = payload.type; this._type = payload.type;
this._subtype = payload.subtype; this._subtype = payload.subtype;

View file

@ -6,7 +6,7 @@ export { Keyboard, Mouse } from '../input';
export { Browser, BrowserContext, Target } from './Browser'; export { Browser, BrowserContext, Target } from './Browser';
export { BrowserFetcher } from './BrowserFetcher'; export { BrowserFetcher } from './BrowserFetcher';
export { Dialog } from './Dialog'; export { Dialog } from './Dialog';
export { ExecutionContext } from './ExecutionContext'; export { ExecutionContext } from '../javascript';
export { Accessibility } from './features/accessibility'; export { Accessibility } from './features/accessibility';
export { Interception } from './features/interception'; export { Interception } from './features/interception';
export { Permissions } from './features/permissions'; export { Permissions } from './features/permissions';

View file

@ -17,6 +17,7 @@
import * as types from './types'; import * as types from './types';
import * as fs from 'fs'; import * as fs from 'fs';
import * as js from './javascript';
import { helper, assert } from './helper'; import { helper, assert } from './helper';
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from './input'; import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from './input';
import { waitForSelectorOrXPath, WaitTaskParams, WaitTask } from './waitTask'; import { waitForSelectorOrXPath, WaitTaskParams, WaitTask } from './waitTask';
@ -25,11 +26,11 @@ import { TimeoutSettings } from './TimeoutSettings';
const readFileAsync = helper.promisify(fs.readFile); const readFileAsync = helper.promisify(fs.readFile);
type WorldType = 'main' | 'utility'; type WorldType = 'main' | 'utility';
type World<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>, ExecutionContext> = { type World<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> = {
contextPromise: Promise<ExecutionContext>; contextPromise: Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>>;
contextResolveCallback: (c: ExecutionContext) => void; contextResolveCallback: (c: js.ExecutionContext<JSHandle, ElementHandle, Response>) => void;
context: ExecutionContext | null; context: js.ExecutionContext<JSHandle, ElementHandle, Response> | null;
waitTasks: Set<WaitTask<JSHandle, ElementHandle>>; waitTasks: Set<WaitTask<JSHandle, ElementHandle, Response>>;
}; };
export type NavigateOptions = { export type NavigateOptions = {
@ -41,24 +42,24 @@ export type GotoOptions = NavigateOptions & {
referer?: string, referer?: string,
}; };
export interface FrameDelegate<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>, ExecutionContext extends types.ExecutionContext<JSHandle, ElementHandle>, Response> { export interface FrameDelegate<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
timeoutSettings(): TimeoutSettings; timeoutSettings(): TimeoutSettings;
navigateFrame(frame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>, url: string, options?: GotoOptions): Promise<Response | null>; navigateFrame(frame: Frame<JSHandle, ElementHandle, Response>, url: string, options?: GotoOptions): Promise<Response | null>;
waitForFrameNavigation(frame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>, options?: NavigateOptions): Promise<Response | null>; waitForFrameNavigation(frame: Frame<JSHandle, ElementHandle, Response>, options?: NavigateOptions): Promise<Response | null>;
setFrameContent(frame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>, html: string, options?: NavigateOptions): Promise<void>; setFrameContent(frame: Frame<JSHandle, ElementHandle, Response>, html: string, options?: NavigateOptions): Promise<void>;
adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext): Promise<ElementHandle>; adoptElementHandle(elementHandle: ElementHandle, context: js.ExecutionContext<JSHandle, ElementHandle, Response>): Promise<ElementHandle>;
} }
export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>, ExecutionContext extends types.ExecutionContext<JSHandle, ElementHandle>, Response> { export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
_delegate: FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response>; _delegate: FrameDelegate<JSHandle, ElementHandle, Response>;
private _parentFrame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>; private _parentFrame: Frame<JSHandle, ElementHandle, Response>;
private _url = ''; private _url = '';
private _detached = false; private _detached = false;
private _worlds = new Map<WorldType, World<JSHandle, ElementHandle, ExecutionContext>>(); private _worlds = new Map<WorldType, World<JSHandle, ElementHandle, Response>>();
private _childFrames = new Set<Frame<JSHandle, ElementHandle, ExecutionContext, Response>>(); private _childFrames = new Set<Frame<JSHandle, ElementHandle, Response>>();
private _name: string; private _name: string;
constructor(delegate: FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response>, parentFrame: Frame<JSHandle, ElementHandle, ExecutionContext, Response> | null) { constructor(delegate: FrameDelegate<JSHandle, ElementHandle, Response>, parentFrame: Frame<JSHandle, ElementHandle, Response> | null) {
this._delegate = delegate; this._delegate = delegate;
this._parentFrame = parentFrame; this._parentFrame = parentFrame;
@ -79,19 +80,19 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, Ele
return this._delegate.waitForFrameNavigation(this, options); return this._delegate.waitForFrameNavigation(this, options);
} }
_mainContext(): Promise<ExecutionContext> { _mainContext(): Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>> {
if (this._detached) if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`); throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
return this._worlds.get('main').contextPromise; return this._worlds.get('main').contextPromise;
} }
_utilityContext(): Promise<ExecutionContext> { _utilityContext(): Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>> {
if (this._detached) if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`); throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
return this._worlds.get('utility').contextPromise; return this._worlds.get('utility').contextPromise;
} }
executionContext(): Promise<ExecutionContext> { executionContext(): Promise<js.ExecutionContext<JSHandle, ElementHandle, Response>> {
return this._mainContext(); return this._mainContext();
} }
@ -159,11 +160,11 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, Ele
return this._url; return this._url;
} }
parentFrame(): Frame<JSHandle, ElementHandle, ExecutionContext, Response> | null { parentFrame(): Frame<JSHandle, ElementHandle, Response> | null {
return this._parentFrame; return this._parentFrame;
} }
childFrames(): Frame<JSHandle, ElementHandle, ExecutionContext, Response>[] { childFrames(): Frame<JSHandle, ElementHandle, Response>[] {
return Array.from(this._childFrames); return Array.from(this._childFrames);
} }
@ -450,7 +451,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, Ele
this._parentFrame = null; this._parentFrame = null;
} }
private _scheduleWaitTask(params: WaitTaskParams, world: World<JSHandle, ElementHandle, ExecutionContext>): Promise<JSHandle> { private _scheduleWaitTask(params: WaitTaskParams, world: World<JSHandle, ElementHandle, Response>): Promise<JSHandle> {
const task = new WaitTask(params, () => world.waitTasks.delete(task)); const task = new WaitTask(params, () => world.waitTasks.delete(task));
world.waitTasks.add(task); world.waitTasks.add(task);
if (world.context) if (world.context)
@ -458,7 +459,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, Ele
return task.promise; return task.promise;
} }
private _setContext(worldType: WorldType, context: ExecutionContext | null) { private _setContext(worldType: WorldType, context: js.ExecutionContext<JSHandle, ElementHandle, Response> | null) {
const world = this._worlds.get(worldType); const world = this._worlds.get(worldType);
world.context = context; world.context = context;
if (context) { if (context) {
@ -472,7 +473,7 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, Ele
} }
} }
_contextCreated(worldType: WorldType, context: ExecutionContext) { _contextCreated(worldType: WorldType, context: js.ExecutionContext<JSHandle, ElementHandle, Response>) {
const world = this._worlds.get(worldType); const world = this._worlds.get(worldType);
// In case of multiple sessions to the same target, there's a race between // In case of multiple sessions to the same target, there's a race between
// connections so we might end up creating multiple isolated worlds. // connections so we might end up creating multiple isolated worlds.
@ -481,14 +482,14 @@ export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, Ele
this._setContext(worldType, context); this._setContext(worldType, context);
} }
_contextDestroyed(context: ExecutionContext) { _contextDestroyed(context: js.ExecutionContext<JSHandle, ElementHandle, Response>) {
for (const [worldType, world] of this._worlds) { for (const [worldType, world] of this._worlds) {
if (world.context === context) if (world.context === context)
this._setContext(worldType, null); this._setContext(worldType, null);
} }
} }
private async _adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext, dispose: boolean): Promise<ElementHandle> { private async _adoptElementHandle(elementHandle: ElementHandle, context: js.ExecutionContext<JSHandle, ElementHandle, Response>, dispose: boolean): Promise<ElementHandle> {
if (elementHandle.executionContext() === context) if (elementHandle.executionContext() === context)
return elementHandle; return elementHandle;
const handle = this._delegate.adoptElementHandle(elementHandle, context); const handle = this._delegate.adoptElementHandle(elementHandle, context);

56
src/javascript.ts Normal file
View file

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import * as frames from './frames';
import * as types from './types';
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 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;
private _documentPromise: Promise<ElementHandle> | null = null;
constructor(delegate: ExecutionContextDelegate<JSHandle, ElementHandle, Response>, frame: frames.Frame<JSHandle, ElementHandle, Response> | null) {
this._delegate = delegate;
this._frame = frame;
}
frame(): frames.Frame<JSHandle, ElementHandle, Response> | null {
return this._frame;
}
evaluate: types.Evaluate<JSHandle> = (pageFunction, ...args) => {
return this._delegate.evaluate(this, true /* returnByValue */, pageFunction, ...args);
}
evaluateHandle: types.EvaluateHandle<JSHandle> = (pageFunction, ...args) => {
return this._delegate.evaluate(this, false /* returnByValue */, pageFunction, ...args);
}
_injected(): Promise<JSHandle> {
if (!this._injectedPromise) {
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
const source = `
new (${injectedSource.source})([
${engineSources.join(',\n')}
])
`;
this._injectedPromise = this.evaluateHandle(source);
}
return this._injectedPromise;
}
_document(): Promise<ElementHandle> {
if (!this._documentPromise)
this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement()!);
return this._documentPromise;
}
}

View file

@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
import * as input from './input'; import * as input from './input';
import * as js from './javascript';
type Boxed<Args extends any[], Handle> = { [Index in keyof Args]: Args[Index] | Handle }; type Boxed<Args extends any[], Handle> = { [Index in keyof Args]: Args[Index] | Handle };
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>); type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
@ -14,19 +15,13 @@ 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 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 type EvaluateHandleOn<Handle> = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args, Handle>) => Promise<Handle>;
export interface ExecutionContext<Handle extends JSHandle<Handle, EHandle>, EHandle extends ElementHandle<Handle, EHandle>> { export interface JSHandle<Handle extends JSHandle<Handle, EHandle, Response>, EHandle extends ElementHandle<Handle, EHandle, Response>, Response> {
evaluate: Evaluate<Handle>; executionContext(): js.ExecutionContext<Handle, EHandle, Response>;
evaluateHandle: EvaluateHandle<Handle>;
_document(): Promise<EHandle>;
}
export interface JSHandle<Handle extends JSHandle<Handle, EHandle>, EHandle extends ElementHandle<Handle, EHandle>> {
executionContext(): ExecutionContext<Handle, EHandle>;
dispose(): Promise<void>; dispose(): Promise<void>;
asElement(): EHandle | null; asElement(): EHandle | null;
} }
export interface ElementHandle<Handle extends JSHandle<Handle, EHandle>, EHandle extends ElementHandle<Handle, EHandle>> extends JSHandle<Handle, EHandle> { export interface ElementHandle<Handle extends JSHandle<Handle, EHandle, Response>, EHandle extends ElementHandle<Handle, EHandle, Response>, Response> extends JSHandle<Handle, EHandle, Response> {
$(selector: string): Promise<EHandle | null>; $(selector: string): Promise<EHandle | null>;
$x(expression: string): Promise<EHandle[]>; $x(expression: string): Promise<EHandle[]>;
$$(selector: string): Promise<EHandle[]>; $$(selector: string): Promise<EHandle[]>;

View file

@ -3,6 +3,7 @@
import { assert, helper } from './helper'; import { assert, helper } from './helper';
import * as types from './types'; import * as types from './types';
import * as js from './javascript';
import { TimeoutError } from './Errors'; import { TimeoutError } from './Errors';
export type WaitTaskParams = { export type WaitTaskParams = {
@ -14,7 +15,7 @@ export type WaitTaskParams = {
args: any[]; args: any[];
}; };
export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>> { export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle, Response>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle, Response>, Response> {
readonly promise: Promise<JSHandle>; readonly promise: Promise<JSHandle>;
private _cleanup: () => void; private _cleanup: () => void;
private _params: WaitTaskParams & { predicateBody: string }; private _params: WaitTaskParams & { predicateBody: string };
@ -56,7 +57,7 @@ export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle>,
this._doCleanup(); this._doCleanup();
} }
async rerun(context: types.ExecutionContext<JSHandle, ElementHandle>) { async rerun(context: js.ExecutionContext<JSHandle, ElementHandle, Response>) {
const runCount = ++this._runCount; const runCount = ++this._runCount;
let success: JSHandle | null = null; let success: JSHandle | null = null;
let error = null; let error = null;

View file

@ -16,32 +16,27 @@
*/ */
import { TargetSession } from './Connection'; import { TargetSession } from './Connection';
import { Frame } from './FrameManager';
import { helper } from '../helper'; import { helper } from '../helper';
import { valueFromRemoteObject } from './protocolHelper'; import { valueFromRemoteObject } from './protocolHelper';
import { createJSHandle, JSHandle, ElementHandle } from './JSHandle'; import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as injectedSource from '../generated/injectedSource'; import { Response } from './NetworkManager';
import * as cssSelectorEngineSource from '../generated/cssSelectorEngineSource'; import * as js from '../javascript';
import * as xpathSelectorEngineSource from '../generated/xpathSelectorEngineSource';
import * as types from '../types';
export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__'; export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
export class ExecutionContext { export type ExecutionContext = js.ExecutionContext<JSHandle, ElementHandle, Response>;
_globalObjectId?: string;
_session: TargetSession;
_frame: Frame;
_contextId: number;
private _contextDestroyedCallback: any;
private _executionContextDestroyedPromise: Promise<unknown>;
private _injectedPromise: Promise<JSHandle> | null = null;
private _documentPromise: Promise<ElementHandle> | null = null;
constructor(client: TargetSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, frame: Frame | null) { export class ExecutionContextDelegate implements js.ExecutionContextDelegate<JSHandle, ElementHandle, Response> {
private _globalObjectId?: string;
_session: TargetSession;
private _contextId: number;
private _contextDestroyedCallback: () => void;
private _executionContextDestroyedPromise: Promise<unknown>;
constructor(client: TargetSession, contextPayload: Protocol.Runtime.ExecutionContextDescription) {
this._session = client; this._session = client;
this._frame = frame;
this._contextId = contextPayload.id; this._contextId = contextPayload.id;
this._contextDestroyedCallback = null; this._contextDestroyedCallback = null;
this._executionContextDestroyedPromise = new Promise((resolve, reject) => { this._executionContextDestroyedPromise = new Promise((resolve, reject) => {
@ -53,19 +48,7 @@ export class ExecutionContext {
this._contextDestroyedCallback(); this._contextDestroyedCallback();
} }
frame(): Frame | null { async evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
return this._frame;
}
evaluate: types.Evaluate<JSHandle> = (pageFunction, ...args) => {
return this._evaluateInternal(true /* returnByValue */, pageFunction, ...args);
}
evaluateHandle: types.EvaluateHandle<JSHandle> = (pageFunction, ...args) => {
return this._evaluateInternal(false /* returnByValue */, pageFunction, ...args);
}
async _evaluateInternal(returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`; const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
if (helper.isString(pageFunction)) { if (helper.isString(pageFunction)) {
@ -98,7 +81,7 @@ export class ExecutionContext {
if (response.wasThrown) if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description); throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue) if (!returnByValue)
return createJSHandle(this, response.result); return createJSHandle(context, response.result);
if (response.result.objectId) { if (response.result.objectId) {
const serializeFunction = function() { const serializeFunction = function() {
try { try {
@ -204,7 +187,7 @@ export class ExecutionContext {
if (response.wasThrown) if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description); throw new Error('Evaluation failed: ' + response.result.description);
if (!returnByValue) if (!returnByValue)
return createJSHandle(this, response.result); return createJSHandle(context, response.result);
if (response.result.objectId) { if (response.result.objectId) {
const serializeFunction = function() { const serializeFunction = function() {
try { try {
@ -305,23 +288,4 @@ export class ExecutionContext {
} }
return this._globalObjectId; return this._globalObjectId;
} }
_injected(): Promise<JSHandle> {
if (!this._injectedPromise) {
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
const source = `
new (${injectedSource.source})([
${engineSources.join(',\n')}
])
`;
this._injectedPromise = this.evaluateHandle(source);
}
return this._injectedPromise;
}
_document(): Promise<ElementHandle> {
if (!this._documentPromise)
this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement()!);
return this._documentPromise;
}
} }

View file

@ -21,12 +21,13 @@ import { Events } from './events';
import { assert, debugError, helper, RegisteredListener } from '../helper'; import { assert, debugError, helper, RegisteredListener } from '../helper';
import { TimeoutSettings } from '../TimeoutSettings'; import { TimeoutSettings } from '../TimeoutSettings';
import { TargetSession } from './Connection'; import { TargetSession } from './Connection';
import { ExecutionContext } from './ExecutionContext'; import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
import { ElementHandle, JSHandle } from './JSHandle'; import { ElementHandle, JSHandle } from './JSHandle';
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager'; import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
import { Page } from './Page'; import { Page } from './Page';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as frames from '../frames'; import * as frames from '../frames';
import * as js from '../javascript';
export const FrameManagerEvents = { export const FrameManagerEvents = {
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'), FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
@ -41,9 +42,9 @@ type FrameData = {
id: string, id: string,
}; };
export type Frame = frames.Frame<JSHandle, ElementHandle, ExecutionContext, Response>; export type Frame = frames.Frame<JSHandle, ElementHandle, Response>;
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response> { export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, Response> {
_session: TargetSession; _session: TargetSession;
_page: Page; _page: Page;
_networkManager: NetworkManager; _networkManager: NetworkManager;
@ -102,7 +103,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
disconnectFromTarget() { disconnectFromTarget() {
for (const context of this._contextIdToContext.values()) { for (const context of this._contextIdToContext.values()) {
context._dispose(); (context._delegate as ExecutionContextDelegate)._dispose();
context.frame()._contextDestroyed(context); context.frame()._contextDestroyed(context);
} }
// this._mainFrame = null; // this._mainFrame = null;
@ -198,7 +199,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
frame._navigated(framePayload.url, framePayload.name); frame._navigated(framePayload.url, framePayload.name);
for (const context of this._contextIdToContext.values()) { for (const context of this._contextIdToContext.values()) {
if (context.frame() === frame) { if (context.frame() === frame) {
context._dispose(); (context._delegate as ExecutionContextDelegate)._dispose();
frame._contextDestroyed(context); frame._contextDestroyed(context);
} }
} }
@ -230,7 +231,7 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate<J
const frame = this._frames.get(frameId) || null; const frame = this._frames.get(frameId) || null;
if (!frame) if (!frame)
return; return;
const context: ExecutionContext = new ExecutionContext(this._session, contextPayload, frame); const context: ExecutionContext = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload), frame);
if (frame) { if (frame) {
frame._contextCreated('main', context); frame._contextCreated('main', context);
frame._contextCreated('utility', context); frame._contextCreated('utility', context);

View file

@ -19,7 +19,7 @@ import * as fs from 'fs';
import { assert, debugError, helper } from '../helper'; import { assert, debugError, helper } from '../helper';
import * as input from '../input'; import * as input from '../input';
import { TargetSession } from './Connection'; import { TargetSession } from './Connection';
import { ExecutionContext } from './ExecutionContext'; import { ExecutionContext, ExecutionContextDelegate } from './ExecutionContext';
import { FrameManager } from './FrameManager'; import { FrameManager } from './FrameManager';
import { Page } from './Page'; import { Page } from './Page';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
@ -32,12 +32,13 @@ type SelectorRoot = Element | ShadowRoot | Document;
const writeFileAsync = helper.promisify(fs.writeFile); const writeFileAsync = helper.promisify(fs.writeFile);
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) { export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
const delegate = context._delegate as ExecutionContextDelegate;
const frame = context.frame(); const frame = context.frame();
if (remoteObject.subtype === 'node' && frame) { if (remoteObject.subtype === 'node' && frame) {
const frameManager = frame._delegate as FrameManager; const frameManager = frame._delegate as FrameManager;
return new ElementHandle(context, context._session, remoteObject, frameManager.page(), frameManager); return new ElementHandle(context, delegate._session, remoteObject, frameManager.page(), frameManager);
} }
return new JSHandle(context, context._session, remoteObject); return new JSHandle(context, delegate._session, remoteObject);
} }
export class JSHandle { export class JSHandle {

View file

@ -4,7 +4,7 @@
export { TimeoutError } from '../Errors'; export { TimeoutError } from '../Errors';
export { Browser, BrowserContext } from './Browser'; export { Browser, BrowserContext } from './Browser';
export { BrowserFetcher } from './BrowserFetcher'; export { BrowserFetcher } from './BrowserFetcher';
export { ExecutionContext } from './ExecutionContext'; export { ExecutionContext } from '../javascript';
export { Frame } from './FrameManager'; export { Frame } from './FrameManager';
export { Mouse, Keyboard } from '../input'; export { Mouse, Keyboard } from '../input';
export { ElementHandle, JSHandle } from './JSHandle'; export { ElementHandle, JSHandle } from './JSHandle';