diff --git a/src/common/AriaQueryHandler.ts b/src/common/AriaQueryHandler.ts index a2f82ccc89c..97731ed5669 100644 --- a/src/common/AriaQueryHandler.ts +++ b/src/common/AriaQueryHandler.ts @@ -95,7 +95,9 @@ const queryOne = async ( if (!res[0] || !res[0].backendDOMNodeId) { return null; } - return exeCtx._adoptBackendNodeId(res[0].backendDOMNodeId); + return (await exeCtx._world!.adoptBackendNode( + res[0].backendDOMNodeId + )) as ElementHandle; }; const waitFor = async ( @@ -132,11 +134,12 @@ const queryAll = async ( const exeCtx = element.executionContext(); const {name, role} = parseAriaSelector(selector); const res = await queryAXTree(exeCtx._client, element, name, role); - return Promise.all( + const world = exeCtx._world!; + return (await Promise.all( res.map(axNode => { - return exeCtx._adoptBackendNodeId(axNode.backendDOMNodeId); + return world.adoptBackendNode(axNode.backendDOMNodeId); }) - ); + )) as Array>; }; const queryAllArray = async ( diff --git a/src/common/DOMWorld.ts b/src/common/DOMWorld.ts index c76cccdd2c4..59b88058cfc 100644 --- a/src/common/DOMWorld.ts +++ b/src/common/DOMWorld.ts @@ -29,6 +29,7 @@ import {TimeoutSettings} from './TimeoutSettings.js'; import {EvaluateFunc, HandleFor, NodeFor} from './types.js'; import { createDeferredPromise, + createJSHandle, debugError, DeferredPromise, importFS, @@ -739,6 +740,29 @@ export class DOMWorld { return document.title; }); } + + async adoptBackendNode( + backendNodeId?: Protocol.DOM.BackendNodeId + ): Promise> { + const executionContext = await this.executionContext(); + const {object} = await this.#client.send('DOM.resolveNode', { + backendNodeId: backendNodeId, + executionContextId: executionContext._contextId, + }); + return createJSHandle(executionContext, object) as JSHandle; + } + + async adoptHandle>(handle: T): Promise { + const executionContext = await this.executionContext(); + assert( + handle.executionContext() !== executionContext, + 'Cannot adopt handle that already belongs to this execution context' + ); + const nodeInfo = await this.#client.send('DOM.describeNode', { + objectId: handle._remoteObject.objectId, + }); + return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T; + } } /** diff --git a/src/common/ElementHandle.ts b/src/common/ElementHandle.ts index e40c0c0a60b..dea138d3c26 100644 --- a/src/common/ElementHandle.ts +++ b/src/common/ElementHandle.ts @@ -123,8 +123,7 @@ export class ElementHandle< ): Promise> | null> { const frame = this._context.frame(); assert(frame); - const secondaryContext = await frame._secondaryWorld.executionContext(); - const adoptedRoot = await secondaryContext._adoptElementHandle(this); + const adoptedRoot = await frame._secondaryWorld.adoptHandle(this); const handle = await frame._secondaryWorld.waitForSelector(selector, { ...options, root: adoptedRoot, @@ -133,8 +132,9 @@ export class ElementHandle< if (!handle) { return null; } - const mainExecutionContext = await frame._mainWorld.executionContext(); - const result = await mainExecutionContext._adoptElementHandle(handle); + const result = (await frame._mainWorld.adoptHandle( + handle + )) as ElementHandle>; await handle.dispose(); return result; } diff --git a/src/common/ExecutionContext.ts b/src/common/ExecutionContext.ts index b67b9d395ac..05d8941f3d2 100644 --- a/src/common/ExecutionContext.ts +++ b/src/common/ExecutionContext.ts @@ -38,19 +38,16 @@ const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; /** * This class represents a context for JavaScript execution. A [Page] might have * many execution contexts: - * - each - * {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe | - * frame } has "default" execution context that is always created after frame is + * - each {@link + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe | frame} + * has "default" execution context that is always created after frame is * attached to DOM. This context is returned by the * {@link Frame.executionContext} method. - * - {@link https://developer.chrome.com/extensions | Extension}'s content scripts - * create additional execution contexts. + * - {@link https://developer.chrome.com/extensions | Extension}'s content + * scripts create additional execution contexts. * - * Besides pages, execution contexts can be found in - * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | - * workers }. - * - * @public + * Besides pages, execution contexts can be found in {@link + * https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | workers}. */ export class ExecutionContext { /** @@ -87,9 +84,9 @@ export class ExecutionContext { /** * @remarks * - * Not every execution context is associated with a frame. For - * example, workers and extensions have execution contexts that are not - * associated with frames. + * Not every execution context is associated with a frame. For example, + * workers and extensions have execution contexts that are not associated with + * frames. * * @returns The frame associated with this execution context. */ @@ -422,21 +419,4 @@ export class ExecutionContext { }); return createJSHandle(this, object) as ElementHandle; } - - /** - * @internal - */ - async _adoptElementHandle>( - elementHandle: T - ): Promise { - assert( - elementHandle.executionContext() !== this, - 'Cannot adopt handle that already belongs to this execution context' - ); - assert(this._world, 'Cannot adopt handle without DOMWorld'); - const nodeInfo = await this._client.send('DOM.describeNode', { - objectId: elementHandle._remoteObject.objectId, - }); - return (await this._adoptBackendNodeId(nodeInfo.node.backendNodeId)) as T; - } } diff --git a/src/common/FrameManager.ts b/src/common/FrameManager.ts index df20ede6897..6d1efd82029 100644 --- a/src/common/FrameManager.ts +++ b/src/common/FrameManager.ts @@ -1369,10 +1369,11 @@ export class Frame { if (!handle) { return null; } - const mainExecutionContext = await this._mainWorld.executionContext(); - const result = await mainExecutionContext._adoptElementHandle(handle); + const mainHandle = (await this._mainWorld.adoptHandle( + handle + )) as ElementHandle>; await handle.dispose(); - return result; + return mainHandle; } /** diff --git a/src/common/Page.ts b/src/common/Page.ts index bacc47680bf..4983178d4fe 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -644,15 +644,13 @@ export class Page extends EventEmitter { } const frame = this.#frameManager.frame(event.frameId); assert(frame); - const context = await frame.executionContext(); - const element = await context._adoptBackendNodeId(event.backendNodeId); + // This is guaranteed to be an HTMLInputElement handle by the event. + const handle = (await frame._mainWorld.adoptBackendNode( + event.backendNodeId + )) as ElementHandle; const interceptors = Array.from(this.#fileChooserInterceptors); this.#fileChooserInterceptors.clear(); - const fileChooser = new FileChooser( - // This is guaranteed by the event. - element as ElementHandle, - event - ); + const fileChooser = new FileChooser(handle, event); for (const interceptor of interceptors) { interceptor.call(undefined, fileChooser); }