inline factory

This commit is contained in:
Yury Semikhatsky 2025-02-21 14:53:27 -08:00
parent 1282981c45
commit aba2d31c64
12 changed files with 71 additions and 111 deletions

View file

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { assert } from '../../utils';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import * as dom from '../dom'; import * as dom from '../dom';
@ -26,7 +27,6 @@ import type { BidiSession } from './bidiConnection';
export class BidiExecutionContext implements js.ExecutionContextDelegate { export class BidiExecutionContext implements js.ExecutionContextDelegate {
private readonly _session: BidiSession; private readonly _session: BidiSession;
readonly _target: bidi.Script.Target; readonly _target: bidi.Script.Target;
private _handleFactory!: js.HandleFactory;
constructor(session: BidiSession, realmInfo: bidi.Script.RealmInfo) { constructor(session: BidiSession, realmInfo: bidi.Script.RealmInfo) {
this._session = session; this._session = session;
@ -43,10 +43,6 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
} }
} }
setHandleFactory(handleFactory: js.HandleFactory) {
this._handleFactory = handleFactory;
}
async rawEvaluateJSON(expression: string): Promise<any> { async rawEvaluateJSON(expression: string): Promise<any> {
const response = await this._session.send('script.evaluate', { const response = await this._session.send('script.evaluate', {
expression, expression,
@ -103,7 +99,7 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
if (response.type === 'success') { if (response.type === 'success') {
if (returnByValue) if (returnByValue)
return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result)); return parseEvaluationResultValue(BidiDeserializer.deserialize(response.result));
return this._createHandle(response.result); return createHandle(utilityScript._context, response.result);
} }
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)); throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
} }
@ -143,11 +139,11 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
}; };
} }
async remoteObjectForNodeId(nodeId: bidi.Script.SharedReference): Promise<js.JSHandle> { async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise<js.JSHandle> {
const result = await this._remoteValueForReference(nodeId); const result = await this._remoteValueForReference(nodeId);
if (!('handle' in result)) if (!('handle' in result))
throw new Error('Can\'t get remote object for nodeId'); throw new Error('Can\'t get remote object for nodeId');
return this._createHandle(result); return createHandle(context, result);
} }
async contentFrameIdForFrame(handle: dom.ElementHandle) { async contentFrameIdForFrame(handle: dom.ElementHandle) {
@ -186,13 +182,6 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
return response.result; return response.result;
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response)); throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
} }
_createHandle(remoteObject: bidi.Script.RemoteValue): js.JSHandle {
if (remoteObject.type === 'node')
return this._handleFactory.createElementHandle(remoteObject.handle!);
const objectId = 'handle' in remoteObject ? remoteObject.handle : undefined;
return this._handleFactory.createJSHandle(remoteObject.type, renderPreview(remoteObject), objectId, remoteObjectValue(remoteObject));
}
} }
function renderPreview(remoteObject: bidi.Script.RemoteValue): string | undefined { function renderPreview(remoteObject: bidi.Script.RemoteValue): string | undefined {
@ -216,3 +205,12 @@ function remoteObjectValue(remoteObject: bidi.Script.RemoteValue): any {
return remoteObject.value; return remoteObject.value;
return undefined; return undefined;
} }
export function createHandle(context: js.ExecutionContext, remoteObject: bidi.Script.RemoteValue): js.JSHandle {
if (remoteObject.type === 'node') {
assert(context instanceof dom.FrameExecutionContext);
return new dom.ElementHandle(context, remoteObject.handle!);
}
const objectId = 'handle' in remoteObject ? remoteObject.handle : undefined;
return new js.JSHandle(context, remoteObject.type, renderPreview(remoteObject), objectId, remoteObjectValue(remoteObject));
}

View file

@ -20,7 +20,7 @@ import { BrowserContext } from '../browserContext';
import * as dialog from '../dialog'; import * as dialog from '../dialog';
import * as dom from '../dom'; import * as dom from '../dom';
import { Page } from '../page'; import { Page } from '../page';
import { BidiExecutionContext } from './bidiExecutionContext'; import { BidiExecutionContext, createHandle } from './bidiExecutionContext';
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './bidiInput'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './bidiInput';
import { BidiNetworkManager } from './bidiNetworkManager'; import { BidiNetworkManager } from './bidiNetworkManager';
import { BidiPDF } from './bidiPdf'; import { BidiPDF } from './bidiPdf';
@ -242,7 +242,7 @@ export class BidiPage implements PageDelegate {
return; return;
const callFrame = params.stackTrace?.callFrames[0]; const callFrame = params.stackTrace?.callFrames[0];
const location = callFrame ?? { url: '', lineNumber: 1, columnNumber: 1 }; const location = callFrame ?? { url: '', lineNumber: 1, columnNumber: 1 };
this._page._addConsoleMessage(entry.method, entry.args.map(arg => toBidiExecutionContext(context)._createHandle(arg)), location, params.text || undefined); this._page._addConsoleMessage(entry.method, entry.args.map(arg => createHandle(context, arg)), location, params.text || undefined);
} }
async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> { async navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult> {
@ -528,7 +528,7 @@ export class BidiPage implements PageDelegate {
const fromContext = toBidiExecutionContext(handle._context); const fromContext = toBidiExecutionContext(handle._context);
const nodeId = await fromContext.nodeIdForElementHandle(handle); const nodeId = await fromContext.nodeIdForElementHandle(handle);
const executionContext = toBidiExecutionContext(to); const executionContext = toBidiExecutionContext(to);
return await executionContext.remoteObjectForNodeId(nodeId) as dom.ElementHandle<T>; return await executionContext.remoteObjectForNodeId(to, nodeId) as dom.ElementHandle<T>;
} }
async getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> { async getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> {

View file

@ -15,10 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { assert } from '../../utils/isomorphic/assert';
import { getExceptionMessage, releaseObject } from './crProtocolHelper'; import { getExceptionMessage, releaseObject } from './crProtocolHelper';
import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace'; import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import * as dom from '../dom';
import { isSessionClosedError } from '../protocolError'; import { isSessionClosedError } from '../protocolError';
import type { CRSession } from './crConnection'; import type { CRSession } from './crConnection';
@ -27,17 +29,12 @@ import type { Protocol } from './protocol';
export class CRExecutionContext implements js.ExecutionContextDelegate { export class CRExecutionContext implements js.ExecutionContextDelegate {
_client: CRSession; _client: CRSession;
_contextId: number; _contextId: number;
private _handleFactory!: js.HandleFactory;
constructor(client: CRSession, contextPayload: Protocol.Runtime.ExecutionContextDescription) { constructor(client: CRSession, contextPayload: Protocol.Runtime.ExecutionContextDescription) {
this._client = client; this._client = client;
this._contextId = contextPayload.id; this._contextId = contextPayload.id;
} }
setHandleFactory(handleFactory: js.HandleFactory) {
this._handleFactory = handleFactory;
}
async rawEvaluateJSON(expression: string): Promise<any> { async rawEvaluateJSON(expression: string): Promise<any> {
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', { const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', {
expression, expression,
@ -74,7 +71,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
}).catch(rewriteError); }).catch(rewriteError);
if (exceptionDetails) if (exceptionDetails)
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails)); throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
return returnByValue ? parseEvaluationResultValue(remoteObject.value) : this._createHandle(remoteObject); return returnByValue ? parseEvaluationResultValue(remoteObject.value) : createHandle(utilityScript._context, remoteObject);
} }
async getProperties(context: js.ExecutionContext, object: js.JSHandle): Promise<Map<string, js.JSHandle>> { async getProperties(context: js.ExecutionContext, object: js.JSHandle): Promise<Map<string, js.JSHandle>> {
@ -86,17 +83,11 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
for (const property of response.result) { for (const property of response.result) {
if (!property.enumerable || !property.value) if (!property.enumerable || !property.value)
continue; continue;
result.set(property.name, this._createHandle(property.value)); result.set(property.name, createHandle(object._context, property.value));
} }
return result; return result;
} }
_createHandle(remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
if (remoteObject.subtype === 'node')
return this._handleFactory.createElementHandle(remoteObject.objectId!);
return this._handleFactory.createJSHandle(remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
}
async releaseHandle(objectId: js.ObjectId): Promise<void> { async releaseHandle(objectId: js.ObjectId): Promise<void> {
await releaseObject(this._client, objectId); await releaseObject(this._client, objectId);
} }
@ -139,3 +130,11 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
return js.sparseArrayToString(object.preview.properties); return js.sparseArrayToString(object.preview.properties);
return object.description; return object.description;
} }
export function createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
if (remoteObject.subtype === 'node') {
assert(context instanceof dom.FrameExecutionContext);
return new dom.ElementHandle(context, remoteObject.objectId!);
}
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
}

View file

@ -33,7 +33,7 @@ import { getAccessibilityTree } from './crAccessibility';
import { CRBrowserContext } from './crBrowser'; import { CRBrowserContext } from './crBrowser';
import { CRCoverage } from './crCoverage'; import { CRCoverage } from './crCoverage';
import { DragManager } from './crDragDrop'; import { DragManager } from './crDragDrop';
import { CRExecutionContext } from './crExecutionContext'; import { createHandle, CRExecutionContext } from './crExecutionContext';
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './crInput'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './crInput';
import { CRNetworkManager } from './crNetworkManager'; import { CRNetworkManager } from './crNetworkManager';
import { CRPDF } from './crPdf'; import { CRPDF } from './crPdf';
@ -736,7 +736,7 @@ class FrameSession {
session.on('Target.attachedToTarget', event => this._onAttachedToTarget(event)); session.on('Target.attachedToTarget', event => this._onAttachedToTarget(event));
session.on('Target.detachedFromTarget', event => this._onDetachedFromTarget(event)); session.on('Target.detachedFromTarget', event => this._onDetachedFromTarget(event));
session.on('Runtime.consoleAPICalled', event => { session.on('Runtime.consoleAPICalled', event => {
const args = event.args.map(o => toCRExecutionContext(worker._existingExecutionContext!)._createHandle(o)); const args = event.args.map(o => createHandle(worker._existingExecutionContext!, o));
this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace)); this._page._addConsoleMessage(event.type, args, toConsoleMessageLocation(event.stackTrace));
}); });
session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails), this._page)); session.on('Runtime.exceptionThrown', exception => this._page.emitOnContextOnceInitialized(BrowserContext.Events.PageError, exceptionToError(exception.exceptionDetails), this._page));
@ -799,7 +799,7 @@ class FrameSession {
const context = this._contextIdToContext.get(event.executionContextId); const context = this._contextIdToContext.get(event.executionContextId);
if (!context) if (!context)
return; return;
const values = event.args.map(arg => toCRExecutionContext(context)._createHandle(arg)); const values = event.args.map(arg => createHandle(context, arg));
this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace)); this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace));
} }
@ -1168,7 +1168,7 @@ class FrameSession {
}); });
if (!result || result.object.subtype === 'null') if (!result || result.object.subtype === 'null')
throw new Error(dom.kUnableToAdoptErrorMessage); throw new Error(dom.kUnableToAdoptErrorMessage);
return toCRExecutionContext(to)._createHandle(result.object).asElement()!; return createHandle(to, result.object).asElement()!;
} }
} }
@ -1243,7 +1243,3 @@ function calculateUserAgentMetadata(options: types.BrowserContextOptions) {
metadata.architecture = 'arm'; metadata.architecture = 'arm';
return metadata; return metadata;
} }
function toCRExecutionContext(executionContext: js.ExecutionContext): CRExecutionContext {
return executionContext._delegate as CRExecutionContext;
}

View file

@ -109,10 +109,6 @@ export class FrameExecutionContext extends js.ExecutionContext {
} }
return this._injectedScriptPromise; return this._injectedScriptPromise;
} }
override createElementHandle(objectId: js.ObjectId): ElementHandle {
return new ElementHandle(this, objectId);
}
} }
export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> { export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {

View file

@ -27,7 +27,7 @@ import { eventsHelper } from '../utils/eventsHelper';
import { validateBrowserContextOptions } from '../browserContext'; import { validateBrowserContextOptions } from '../browserContext';
import { CRBrowser } from '../chromium/crBrowser'; import { CRBrowser } from '../chromium/crBrowser';
import { CRConnection } from '../chromium/crConnection'; import { CRConnection } from '../chromium/crConnection';
import { CRExecutionContext } from '../chromium/crExecutionContext'; import { createHandle, CRExecutionContext } from '../chromium/crExecutionContext';
import { toConsoleMessageLocation } from '../chromium/crProtocolHelper'; import { toConsoleMessageLocation } from '../chromium/crProtocolHelper';
import { ConsoleMessage } from '../console'; import { ConsoleMessage } from '../console';
import { helper } from '../helper'; import { helper } from '../helper';
@ -116,7 +116,7 @@ export class ElectronApplication extends SdkObject {
} }
if (!this._nodeExecutionContext) if (!this._nodeExecutionContext)
return; return;
const args = event.args.map(arg => toCRExecutionContext(this._nodeExecutionContext!)._createHandle(arg)); const args = event.args.map(arg => createHandle(this._nodeExecutionContext!, arg));
const message = new ConsoleMessage(null, event.type, undefined, args, toConsoleMessageLocation(event.stackTrace)); const message = new ConsoleMessage(null, event.type, undefined, args, toConsoleMessageLocation(event.stackTrace));
this.emit(ElectronApplication.Events.Console, message); this.emit(ElectronApplication.Events.Console, message);
} }
@ -151,10 +151,6 @@ export class ElectronApplication extends SdkObject {
} }
} }
function toCRExecutionContext(executionContext: js.ExecutionContext): CRExecutionContext {
return executionContext._delegate as CRExecutionContext;
}
export class Electron extends SdkObject { export class Electron extends SdkObject {
constructor(playwright: Playwright) { constructor(playwright: Playwright) {
super(playwright, 'electron'); super(playwright, 'electron');

View file

@ -15,9 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { assert } from '../../utils/isomorphic/assert';
import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace'; import { rewriteErrorMessage } from '../../utils/isomorphic/stackTrace';
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import * as dom from '../dom';
import { isSessionClosedError } from '../protocolError'; import { isSessionClosedError } from '../protocolError';
import type { FFSession } from './ffConnection'; import type { FFSession } from './ffConnection';
@ -26,17 +28,12 @@ import type { Protocol } from './protocol';
export class FFExecutionContext implements js.ExecutionContextDelegate { export class FFExecutionContext implements js.ExecutionContextDelegate {
_session: FFSession; _session: FFSession;
_executionContextId: string; _executionContextId: string;
private _handleFactory!: js.HandleFactory;
constructor(session: FFSession, executionContextId: string) { constructor(session: FFSession, executionContextId: string) {
this._session = session; this._session = session;
this._executionContextId = executionContextId; this._executionContextId = executionContextId;
} }
setHandleFactory(handleFactory: js.HandleFactory) {
this._handleFactory = handleFactory;
}
async rawEvaluateJSON(expression: string): Promise<any> { async rawEvaluateJSON(expression: string): Promise<any> {
const payload = await this._session.send('Runtime.evaluate', { const payload = await this._session.send('Runtime.evaluate', {
expression, expression,
@ -71,7 +68,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
checkException(payload.exceptionDetails); checkException(payload.exceptionDetails);
if (returnByValue) if (returnByValue)
return parseEvaluationResultValue(payload.result!.value); return parseEvaluationResultValue(payload.result!.value);
return toFFExecutionContext(utilityScript._context)._createHandle(payload.result!); return createHandle(utilityScript._context, payload.result!);
} }
async getProperties(context: js.ExecutionContext, object: js.JSHandle): Promise<Map<string, js.JSHandle>> { async getProperties(context: js.ExecutionContext, object: js.JSHandle): Promise<Map<string, js.JSHandle>> {
@ -81,16 +78,10 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
}); });
const result = new Map(); const result = new Map();
for (const property of response.properties) for (const property of response.properties)
result.set(property.name, toFFExecutionContext(context)._createHandle(property.value)); result.set(property.name, createHandle(context, property.value));
return result; return result;
} }
_createHandle(remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
if (remoteObject.subtype === 'node')
return this._handleFactory.createElementHandle(remoteObject.objectId!);
return this._handleFactory.createJSHandle(remoteObject.subtype || remoteObject.type || '', renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
}
async releaseHandle(objectId: js.ObjectId): Promise<void> { async releaseHandle(objectId: js.ObjectId): Promise<void> {
await this._session.send('Runtime.disposeObject', { await this._session.send('Runtime.disposeObject', {
executionContextId: this._executionContextId, executionContextId: this._executionContextId,
@ -143,6 +134,10 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
return String(object.value); return String(object.value);
} }
export function toFFExecutionContext(executionContext: js.ExecutionContext): FFExecutionContext { export function createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
return executionContext._delegate as FFExecutionContext; if (remoteObject.subtype === 'node') {
assert(context instanceof dom.FrameExecutionContext);
return new dom.ElementHandle(context, remoteObject.objectId!);
}
return new js.JSHandle(context, remoteObject.subtype || remoteObject.type || '', renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
} }

View file

@ -22,7 +22,7 @@ import { InitScript } from '../page';
import { Page, Worker } from '../page'; import { Page, Worker } from '../page';
import { getAccessibilityTree } from './ffAccessibility'; import { getAccessibilityTree } from './ffAccessibility';
import { FFSession } from './ffConnection'; import { FFSession } from './ffConnection';
import { FFExecutionContext, toFFExecutionContext } from './ffExecutionContext'; import { createHandle, FFExecutionContext } from './ffExecutionContext';
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './ffInput';
import { FFNetworkManager } from './ffNetworkManager'; import { FFNetworkManager } from './ffNetworkManager';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
@ -234,7 +234,7 @@ export class FFPage implements PageDelegate {
if (!context) if (!context)
return; return;
// Juggler reports 'warn' for some internal messages generated by the browser. // Juggler reports 'warn' for some internal messages generated by the browser.
this._page._addConsoleMessage(type === 'warn' ? 'warning' : type, args.map(arg => toFFExecutionContext(context)._createHandle(arg)), location); this._page._addConsoleMessage(type === 'warn' ? 'warning' : type, args.map(arg => createHandle(context, arg)), location);
} }
_onDialogOpened(params: Protocol.Page.dialogOpenedPayload) { _onDialogOpened(params: Protocol.Page.dialogOpenedPayload) {
@ -262,7 +262,7 @@ export class FFPage implements PageDelegate {
const context = this._contextIdToContext.get(executionContextId); const context = this._contextIdToContext.get(executionContextId);
if (!context) if (!context)
return; return;
const handle = toFFExecutionContext(context)._createHandle(element).asElement()!; const handle = createHandle(context, element).asElement()!;
await this._page._onFileChooserOpened(handle); await this._page._onFileChooserOpened(handle);
} }
@ -286,7 +286,7 @@ export class FFPage implements PageDelegate {
workerSession.on('Runtime.console', event => { workerSession.on('Runtime.console', event => {
const { type, args, location } = event; const { type, args, location } = event;
const context = worker._existingExecutionContext!; const context = worker._existingExecutionContext!;
this._page._addConsoleMessage(type, args.map(arg => toFFExecutionContext(context)._createHandle(arg)), location); this._page._addConsoleMessage(type, args.map(arg => createHandle(context, arg)), location);
}); });
// Note: we receive worker exceptions directly from the page. // Note: we receive worker exceptions directly from the page.
} }
@ -531,7 +531,7 @@ export class FFPage implements PageDelegate {
}); });
if (!result.remoteObject) if (!result.remoteObject)
throw new Error(dom.kUnableToAdoptErrorMessage); throw new Error(dom.kUnableToAdoptErrorMessage);
return toFFExecutionContext(to)._createHandle(result.remoteObject) as dom.ElementHandle<T>; return createHandle(to, result.remoteObject) as dom.ElementHandle<T>;
} }
async getAccessibilityTree(needle?: dom.ElementHandle) { async getAccessibilityTree(needle?: dom.ElementHandle) {
@ -560,7 +560,7 @@ export class FFPage implements PageDelegate {
}); });
if (!result.remoteObject) if (!result.remoteObject)
throw new Error('Frame has been detached.'); throw new Error('Frame has been detached.');
return toFFExecutionContext(context)._createHandle(result.remoteObject) as dom.ElementHandle; return createHandle(context, result.remoteObject) as dom.ElementHandle;
} }
shouldToggleStyleSheetToSyncAnimations(): boolean { shouldToggleStyleSheetToSyncAnimations(): boolean {

View file

@ -53,25 +53,18 @@ export interface ExecutionContextDelegate {
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any>; evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any>;
getProperties(context: ExecutionContext, object: JSHandle): Promise<Map<string, JSHandle>>; getProperties(context: ExecutionContext, object: JSHandle): Promise<Map<string, JSHandle>>;
releaseHandle(objectId: ObjectId): Promise<void>; releaseHandle(objectId: ObjectId): Promise<void>;
setHandleFactory(factory: HandleFactory): void;
} }
export interface HandleFactory { export class ExecutionContext extends SdkObject {
createElementHandle(objectId: ObjectId): dom.ElementHandle; private _delegate: ExecutionContextDelegate;
createJSHandle(type: string, preview: string | undefined, objectId?: ObjectId, value?: any): JSHandle;
}
export class ExecutionContext extends SdkObject implements HandleFactory {
_delegate: ExecutionContextDelegate;
private _utilityScriptPromise: Promise<JSHandle> | undefined; private _utilityScriptPromise: Promise<JSHandle> | undefined;
private _contextDestroyedScope = new LongStandingScope(); private _contextDestroyedScope = new LongStandingScope();
readonly worldNameForTest: string; readonly worldNameForTest: string;
constructor(parent: SdkObject, delegate: ExecutionContextDelegate, worldNameForTest: string, handleFactory?: HandleFactory) { constructor(parent: SdkObject, delegate: ExecutionContextDelegate, worldNameForTest: string) {
super(parent, 'execution-context'); super(parent, 'execution-context');
this.worldNameForTest = worldNameForTest; this.worldNameForTest = worldNameForTest;
this._delegate = delegate; this._delegate = delegate;
delegate.setHandleFactory(this);
} }
contextDestroyed(reason: string) { contextDestroyed(reason: string) {
@ -123,14 +116,6 @@ export class ExecutionContext extends SdkObject implements HandleFactory {
async doSlowMo() { async doSlowMo() {
// overridden in FrameExecutionContext // overridden in FrameExecutionContext
} }
createElementHandle(objectId: string): dom.ElementHandle {
throw new Error('Not supported');
}
createJSHandle(type: string, preview: string | undefined, objectId?: ObjectId, value?: any): JSHandle {
return new JSHandle(this, type, preview, objectId, value);
}
} }
export class JSHandle<T = any> extends SdkObject { export class JSHandle<T = any> extends SdkObject {

View file

@ -17,7 +17,9 @@
import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers'; import { parseEvaluationResultValue } from '../isomorphic/utilityScriptSerializers';
import * as js from '../javascript'; import * as js from '../javascript';
import * as dom from '../dom';
import { isSessionClosedError } from '../protocolError'; import { isSessionClosedError } from '../protocolError';
import { assert } from '../../utils/isomorphic/assert';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { WKSession } from './wkConnection'; import type { WKSession } from './wkConnection';
@ -25,17 +27,12 @@ import type { WKSession } from './wkConnection';
export class WKExecutionContext implements js.ExecutionContextDelegate { export class WKExecutionContext implements js.ExecutionContextDelegate {
private readonly _session: WKSession; private readonly _session: WKSession;
readonly _contextId: number | undefined; readonly _contextId: number | undefined;
private _handleFactory!: js.HandleFactory;
constructor(session: WKSession, contextId: number | undefined) { constructor(session: WKSession, contextId: number | undefined) {
this._session = session; this._session = session;
this._contextId = contextId; this._contextId = contextId;
} }
setHandleFactory(handleFactory: js.HandleFactory) {
this._handleFactory = handleFactory;
}
async rawEvaluateJSON(expression: string): Promise<any> { async rawEvaluateJSON(expression: string): Promise<any> {
try { try {
const response = await this._session.send('Runtime.evaluate', { const response = await this._session.send('Runtime.evaluate', {
@ -84,7 +81,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
throw new js.JavaScriptErrorInEvaluate(response.result.description); throw new js.JavaScriptErrorInEvaluate(response.result.description);
if (returnByValue) if (returnByValue)
return parseEvaluationResultValue(response.result.value); return parseEvaluationResultValue(response.result.value);
return toWKExecutionContext(utilityScript._context)._createHandle(response.result); return createHandle(utilityScript._context, response.result);
} catch (error) { } catch (error) {
throw rewriteError(error); throw rewriteError(error);
} }
@ -99,18 +96,11 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
for (const property of response.properties) { for (const property of response.properties) {
if (!property.enumerable || !property.value) if (!property.enumerable || !property.value)
continue; continue;
result.set(property.name, toWKExecutionContext(context)._createHandle(property.value)); result.set(property.name, createHandle(context, property.value));
} }
return result; return result;
} }
_createHandle(remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
if (remoteObject.subtype === 'node')
return this._handleFactory.createElementHandle(remoteObject.objectId!);
const isPromise = remoteObject.className === 'Promise';
return this._handleFactory.createJSHandle(isPromise ? 'promise' : remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
}
async releaseHandle(objectId: js.ObjectId): Promise<void> { async releaseHandle(objectId: js.ObjectId): Promise<void> {
await this._session.send('Runtime.releaseObject', { objectId }); await this._session.send('Runtime.releaseObject', { objectId });
} }
@ -147,6 +137,11 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
return object.description; return object.description;
} }
export function toWKExecutionContext(executionContext: js.ExecutionContext): WKExecutionContext { export function createHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
return executionContext._delegate as WKExecutionContext; if (remoteObject.subtype === 'node') {
assert(context instanceof dom.FrameExecutionContext);
return new dom.ElementHandle(context as dom.FrameExecutionContext, remoteObject.objectId!);
}
const isPromise = remoteObject.className === 'Promise';
return new js.JSHandle(context, isPromise ? 'promise' : remoteObject.subtype || remoteObject.type, renderPreview(remoteObject), remoteObject.objectId, potentiallyUnserializableValue(remoteObject));
} }

View file

@ -34,7 +34,7 @@ import { PageBinding } from '../page';
import { Page } from '../page'; import { Page } from '../page';
import { getAccessibilityTree } from './wkAccessibility'; import { getAccessibilityTree } from './wkAccessibility';
import { WKSession } from './wkConnection'; import { WKSession } from './wkConnection';
import { toWKExecutionContext, WKExecutionContext } from './wkExecutionContext'; import { createHandle, WKExecutionContext } from './wkExecutionContext';
import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './wkInput'; import { RawKeyboardImpl, RawMouseImpl, RawTouchscreenImpl } from './wkInput';
import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest'; import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest';
import { WKProvisionalPage } from './wkProvisionalPage'; import { WKProvisionalPage } from './wkProvisionalPage';
@ -562,7 +562,7 @@ export class WKPage implements PageDelegate {
} }
if (!context) if (!context)
return; return;
handles.push(toWKExecutionContext(context)._createHandle(p)); handles.push(createHandle(context, p));
} }
this._lastConsoleMessage = { this._lastConsoleMessage = {
derivedType, derivedType,
@ -611,7 +611,7 @@ export class WKPage implements PageDelegate {
let handle; let handle;
try { try {
const context = await this._page._frameManager.frame(event.frameId)!._mainContext(); const context = await this._page._frameManager.frame(event.frameId)!._mainContext();
handle = toWKExecutionContext(context)._createHandle(event.element).asElement()!; handle = createHandle(context, event.element).asElement()!;
} catch (e) { } catch (e) {
// During async processing, frame/context may go away. We should not throw. // During async processing, frame/context may go away. We should not throw.
return; return;
@ -958,7 +958,7 @@ export class WKPage implements PageDelegate {
}); });
if (!result || result.object.subtype === 'null') if (!result || result.object.subtype === 'null')
throw new Error(dom.kUnableToAdoptErrorMessage); throw new Error(dom.kUnableToAdoptErrorMessage);
return toWKExecutionContext(to)._createHandle(result.object) as dom.ElementHandle<T>; return createHandle(to, result.object) as dom.ElementHandle<T>;
} }
async getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> { async getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> {
@ -982,7 +982,7 @@ export class WKPage implements PageDelegate {
}); });
if (!result || result.object.subtype === 'null') if (!result || result.object.subtype === 'null')
throw new Error('Frame has been detached.'); throw new Error('Frame has been detached.');
return toWKExecutionContext(context)._createHandle(result.object) as dom.ElementHandle; return createHandle(context, result.object) as dom.ElementHandle;
} }
private _maybeCancelCoopNavigationRequest(provisionalPage: WKProvisionalPage) { private _maybeCancelCoopNavigationRequest(provisionalPage: WKProvisionalPage) {

View file

@ -17,7 +17,7 @@
import { eventsHelper } from '../utils/eventsHelper'; import { eventsHelper } from '../utils/eventsHelper';
import { Worker } from '../page'; import { Worker } from '../page';
import { WKSession } from './wkConnection'; import { WKSession } from './wkConnection';
import { toWKExecutionContext, WKExecutionContext } from './wkExecutionContext'; import { createHandle, WKExecutionContext } from './wkExecutionContext';
import type { Protocol } from './protocol'; import type { Protocol } from './protocol';
import type { RegisteredListener } from '../utils/eventsHelper'; import type { RegisteredListener } from '../utils/eventsHelper';
@ -95,7 +95,7 @@ export class WKWorkers {
derivedType = 'timeEnd'; derivedType = 'timeEnd';
const handles = (parameters || []).map(p => { const handles = (parameters || []).map(p => {
return toWKExecutionContext(worker._existingExecutionContext!)._createHandle(p); return createHandle(worker._existingExecutionContext!, p);
}); });
const location: types.ConsoleMessageLocation = { const location: types.ConsoleMessageLocation = {
url: url || '', url: url || '',