chore: move adoption to DOMWorld (#8760)

This commit is contained in:
jrandolf 2022-08-09 14:55:18 +02:00 committed by GitHub
parent 6934b94f23
commit 932a053d02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 54 additions and 48 deletions

View File

@ -95,7 +95,9 @@ const queryOne = async (
if (!res[0] || !res[0].backendDOMNodeId) { if (!res[0] || !res[0].backendDOMNodeId) {
return null; return null;
} }
return exeCtx._adoptBackendNodeId(res[0].backendDOMNodeId); return (await exeCtx._world!.adoptBackendNode(
res[0].backendDOMNodeId
)) as ElementHandle<Node>;
}; };
const waitFor = async ( const waitFor = async (
@ -132,11 +134,12 @@ const queryAll = async (
const exeCtx = element.executionContext(); const exeCtx = element.executionContext();
const {name, role} = parseAriaSelector(selector); const {name, role} = parseAriaSelector(selector);
const res = await queryAXTree(exeCtx._client, element, name, role); const res = await queryAXTree(exeCtx._client, element, name, role);
return Promise.all( const world = exeCtx._world!;
return (await Promise.all(
res.map(axNode => { res.map(axNode => {
return exeCtx._adoptBackendNodeId(axNode.backendDOMNodeId); return world.adoptBackendNode(axNode.backendDOMNodeId);
}) })
); )) as Array<ElementHandle<Node>>;
}; };
const queryAllArray = async ( const queryAllArray = async (

View File

@ -29,6 +29,7 @@ import {TimeoutSettings} from './TimeoutSettings.js';
import {EvaluateFunc, HandleFor, NodeFor} from './types.js'; import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
import { import {
createDeferredPromise, createDeferredPromise,
createJSHandle,
debugError, debugError,
DeferredPromise, DeferredPromise,
importFS, importFS,
@ -739,6 +740,29 @@ export class DOMWorld {
return document.title; return document.title;
}); });
} }
async adoptBackendNode(
backendNodeId?: Protocol.DOM.BackendNodeId
): Promise<JSHandle<Node>> {
const executionContext = await this.executionContext();
const {object} = await this.#client.send('DOM.resolveNode', {
backendNodeId: backendNodeId,
executionContextId: executionContext._contextId,
});
return createJSHandle(executionContext, object) as JSHandle<Node>;
}
async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
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;
}
} }
/** /**

View File

@ -123,8 +123,7 @@ export class ElementHandle<
): Promise<ElementHandle<NodeFor<Selector>> | null> { ): Promise<ElementHandle<NodeFor<Selector>> | null> {
const frame = this._context.frame(); const frame = this._context.frame();
assert(frame); assert(frame);
const secondaryContext = await frame._secondaryWorld.executionContext(); const adoptedRoot = await frame._secondaryWorld.adoptHandle(this);
const adoptedRoot = await secondaryContext._adoptElementHandle(this);
const handle = await frame._secondaryWorld.waitForSelector(selector, { const handle = await frame._secondaryWorld.waitForSelector(selector, {
...options, ...options,
root: adoptedRoot, root: adoptedRoot,
@ -133,8 +132,9 @@ export class ElementHandle<
if (!handle) { if (!handle) {
return null; return null;
} }
const mainExecutionContext = await frame._mainWorld.executionContext(); const result = (await frame._mainWorld.adoptHandle(
const result = await mainExecutionContext._adoptElementHandle(handle); handle
)) as ElementHandle<NodeFor<Selector>>;
await handle.dispose(); await handle.dispose();
return result; return result;
} }

View File

@ -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 * This class represents a context for JavaScript execution. A [Page] might have
* many execution contexts: * many execution contexts:
* - each * - each {@link
* {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe | * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe | frame}
* frame } has "default" execution context that is always created after frame is * has "default" execution context that is always created after frame is
* attached to DOM. This context is returned by the * attached to DOM. This context is returned by the
* {@link Frame.executionContext} method. * {@link Frame.executionContext} method.
* - {@link https://developer.chrome.com/extensions | Extension}'s content scripts * - {@link https://developer.chrome.com/extensions | Extension}'s content
* create additional execution contexts. * scripts create additional execution contexts.
* *
* Besides pages, execution contexts can be found in * Besides pages, execution contexts can be found in {@link
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | * https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | workers}.
* workers }.
*
* @public
*/ */
export class ExecutionContext { export class ExecutionContext {
/** /**
@ -87,9 +84,9 @@ export class ExecutionContext {
/** /**
* @remarks * @remarks
* *
* Not every execution context is associated with a frame. For * Not every execution context is associated with a frame. For example,
* example, workers and extensions have execution contexts that are not * workers and extensions have execution contexts that are not associated with
* associated with frames. * frames.
* *
* @returns The frame associated with this execution context. * @returns The frame associated with this execution context.
*/ */
@ -422,21 +419,4 @@ export class ExecutionContext {
}); });
return createJSHandle(this, object) as ElementHandle<Node>; return createJSHandle(this, object) as ElementHandle<Node>;
} }
/**
* @internal
*/
async _adoptElementHandle<T extends ElementHandle<Node>>(
elementHandle: T
): Promise<T> {
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;
}
} }

View File

@ -1369,10 +1369,11 @@ export class Frame {
if (!handle) { if (!handle) {
return null; return null;
} }
const mainExecutionContext = await this._mainWorld.executionContext(); const mainHandle = (await this._mainWorld.adoptHandle(
const result = await mainExecutionContext._adoptElementHandle(handle); handle
)) as ElementHandle<NodeFor<Selector>>;
await handle.dispose(); await handle.dispose();
return result; return mainHandle;
} }
/** /**

View File

@ -644,15 +644,13 @@ export class Page extends EventEmitter {
} }
const frame = this.#frameManager.frame(event.frameId); const frame = this.#frameManager.frame(event.frameId);
assert(frame); assert(frame);
const context = await frame.executionContext(); // This is guaranteed to be an HTMLInputElement handle by the event.
const element = await context._adoptBackendNodeId(event.backendNodeId); const handle = (await frame._mainWorld.adoptBackendNode(
event.backendNodeId
)) as ElementHandle<HTMLInputElement>;
const interceptors = Array.from(this.#fileChooserInterceptors); const interceptors = Array.from(this.#fileChooserInterceptors);
this.#fileChooserInterceptors.clear(); this.#fileChooserInterceptors.clear();
const fileChooser = new FileChooser( const fileChooser = new FileChooser(handle, event);
// This is guaranteed by the event.
element as ElementHandle<HTMLInputElement>,
event
);
for (const interceptor of interceptors) { for (const interceptor of interceptors) {
interceptor.call(undefined, fileChooser); interceptor.call(undefined, fileChooser);
} }