From 4c089f36ec0ee20184cb0542b53273f23b77ebcf Mon Sep 17 00:00:00 2001 From: Randolf J Date: Thu, 25 Aug 2022 15:30:48 +0200 Subject: [PATCH] chore: make execution context frame-independent --- src/common/ElementHandle.ts | 10 +++- src/common/ExecutionContext.ts | 83 ++++++------------------------- src/common/util.ts | 6 +-- test/src/ariaqueryhandler.spec.ts | 4 +- test/src/frame.spec.ts | 4 +- test/src/waittask.spec.ts | 6 +-- 6 files changed, 33 insertions(+), 80 deletions(-) diff --git a/src/common/ElementHandle.ts b/src/common/ElementHandle.ts index baef532ec4b1d..cb9b5524ecfda 100644 --- a/src/common/ElementHandle.ts +++ b/src/common/ElementHandle.ts @@ -92,6 +92,13 @@ export class ElementHandle< return this.#frame.page(); } + /** + * @internal + */ + get frame(): Frame { + return this.#frame; + } + /** * Queries the current element for an element matching the given selector. * @@ -294,8 +301,7 @@ export class ElementHandle< selector: Selector, options: Exclude = {} ): Promise> | null> { - const frame = this.executionContext().frame(); - assert(frame); + const frame = this.#frame; const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this); const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector( selector, diff --git a/src/common/ExecutionContext.ts b/src/common/ExecutionContext.ts index c4a1ef5136a82..13dfbc51223c6 100644 --- a/src/common/ExecutionContext.ts +++ b/src/common/ExecutionContext.ts @@ -15,9 +15,7 @@ */ import {Protocol} from 'devtools-protocol'; -import {assert} from '../util/assert.js'; import {CDPSession} from './Connection.js'; -import {Frame} from './Frame.js'; import {IsolatedWorld} from './IsolatedWorld.js'; import {JSHandle} from './JSHandle.js'; import {EvaluateFunc, HandleFor} from './types.js'; @@ -88,18 +86,6 @@ export class ExecutionContext { this._contextName = contextPayload.name; } - /** - * @returns The frame associated with this execution context. - * - * @remarks - * Not every execution context is associated with a frame. For example, - * {@link WebWorker | workers} have execution contexts that are not associated - * with frames. - */ - frame(): Frame | null { - return this._world ? this._world.frame() : null; - } - /** * Evaluates the given function. * @@ -355,61 +341,24 @@ export class ExecutionContext { } return {value: arg}; } + } +} - function rewriteError(error: Error): Protocol.Runtime.EvaluateResponse { - if (error.message.includes('Object reference chain is too long')) { - return {result: {type: 'undefined'}}; - } - if (error.message.includes("Object couldn't be returned by value")) { - return {result: {type: 'undefined'}}; - } - - if ( - error.message.endsWith('Cannot find context with specified id') || - error.message.endsWith('Inspected target navigated or closed') - ) { - throw new Error( - 'Execution context was destroyed, most likely because of a navigation.' - ); - } - throw error; - } +const rewriteError = (error: Error): Protocol.Runtime.EvaluateResponse => { + if (error.message.includes('Object reference chain is too long')) { + return {result: {type: 'undefined'}}; + } + if (error.message.includes("Object couldn't be returned by value")) { + return {result: {type: 'undefined'}}; } - /** - * Iterates through the JavaScript heap and finds all the objects with the - * given prototype. - * - * @example - * - * ```ts - * // Create a Map object - * await page.evaluate(() => (window.map = new Map())); - * // Get a handle to the Map object prototype - * const mapPrototype = await page.evaluateHandle(() => Map.prototype); - * // Query all map instances into an array - * const mapInstances = await page.queryObjects(mapPrototype); - * // Count amount of map objects in heap - * const count = await page.evaluate(maps => maps.length, mapInstances); - * await mapInstances.dispose(); - * await mapPrototype.dispose(); - * ``` - * - * @param prototypeHandle - a handle to the object prototype - * @returns A handle to an array of objects with the given prototype. - */ - async queryObjects( - prototypeHandle: JSHandle - ): Promise> { - assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!'); - const remoteObject = prototypeHandle.remoteObject(); - assert( - remoteObject.objectId, - 'Prototype JSHandle must not be referencing primitive value' + if ( + error.message.endsWith('Cannot find context with specified id') || + error.message.endsWith('Inspected target navigated or closed') + ) { + throw new Error( + 'Execution context was destroyed, most likely because of a navigation.' ); - const response = await this._client.send('Runtime.queryObjects', { - prototypeObjectId: remoteObject.objectId, - }); - return createJSHandle(this, response.objects) as HandleFor; } -} + throw error; +}; diff --git a/src/common/util.ts b/src/common/util.ts index 0317938c6c560..0b280292ee980 100644 --- a/src/common/util.ts +++ b/src/common/util.ts @@ -25,7 +25,6 @@ import {ElementHandle} from './ElementHandle.js'; import {TimeoutError} from './Errors.js'; import {CommonEventEmitter} from './EventEmitter.js'; import {ExecutionContext} from './ExecutionContext.js'; -import {Frame} from './Frame.js'; import {JSHandle} from './JSHandle.js'; /** @@ -218,9 +217,8 @@ export function createJSHandle( context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject ): JSHandle | ElementHandle { - const frame = context.frame(); - if (remoteObject.subtype === 'node' && frame instanceof Frame) { - return new ElementHandle(context, remoteObject, frame); + if (remoteObject.subtype === 'node' && context._world) { + return new ElementHandle(context, remoteObject, context._world.frame()); } return new JSHandle(context, remoteObject); } diff --git a/test/src/ariaqueryhandler.spec.ts b/test/src/ariaqueryhandler.spec.ts index 8de375992718e..b47018cdc9bb4 100644 --- a/test/src/ariaqueryhandler.spec.ts +++ b/test/src/ariaqueryhandler.spec.ts @@ -334,7 +334,7 @@ describeChromeOnly('AriaQueryHandler', () => { await otherFrame!.evaluate(addElement, 'button'); await page.evaluate(addElement, 'button'); const elementHandle = await watchdog; - expect(elementHandle!.executionContext().frame()).toBe(page.mainFrame()); + expect(elementHandle!.frame).toBe(page.mainFrame()); }); it('should run in specified frame', async () => { @@ -350,7 +350,7 @@ describeChromeOnly('AriaQueryHandler', () => { await frame1!.evaluate(addElement, 'button'); await frame2!.evaluate(addElement, 'button'); const elementHandle = await waitForSelectorPromise; - expect(elementHandle!.executionContext().frame()).toBe(frame2); + expect(elementHandle!.frame).toBe(frame2); }); it('should throw when frame is detached', async () => { diff --git a/test/src/frame.spec.ts b/test/src/frame.spec.ts index 92e61e9102e92..b07b72769ab85 100644 --- a/test/src/frame.spec.ts +++ b/test/src/frame.spec.ts @@ -42,8 +42,8 @@ describe('Frame specs', function () { expect(context1).toBeTruthy(); expect(context2).toBeTruthy(); expect(context1 !== context2).toBeTruthy(); - expect(context1.frame()).toBe(frame1); - expect(context2.frame()).toBe(frame2); + expect(context1._world?.frame()).toBe(frame1); + expect(context2._world?.frame()).toBe(frame2); await Promise.all([ context1.evaluate(() => { diff --git a/test/src/waittask.spec.ts b/test/src/waittask.spec.ts index d28afb5a76b81..3ac049d418ee2 100644 --- a/test/src/waittask.spec.ts +++ b/test/src/waittask.spec.ts @@ -477,7 +477,7 @@ describe('waittask specs', function () { await otherFrame.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div'); const eHandle = await watchdog; - expect(eHandle?.executionContext().frame()).toBe(page.mainFrame()); + expect(eHandle?.frame).toBe(page.mainFrame()); } ); @@ -492,7 +492,7 @@ describe('waittask specs', function () { await frame1.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div'); const eHandle = await waitForSelectorPromise; - expect(eHandle?.executionContext().frame()).toBe(frame2); + expect(eHandle?.frame).toBe(frame2); }); itFailsFirefox('should throw when frame is detached', async () => { @@ -749,7 +749,7 @@ describe('waittask specs', function () { await frame1.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div'); const eHandle = await waitForXPathPromise; - expect(eHandle?.executionContext().frame()).toBe(frame2); + expect(eHandle?.frame).toBe(frame2); }); itFailsFirefox('should throw when frame is detached', async () => { const {page, server} = getTestState();