diff --git a/src/common/AriaQueryHandler.ts b/src/common/AriaQueryHandler.ts index 45400dd2c5de4..d01eadc439253 100644 --- a/src/common/AriaQueryHandler.ts +++ b/src/common/AriaQueryHandler.ts @@ -18,11 +18,7 @@ import {Protocol} from 'devtools-protocol'; import {assert} from '../util/assert.js'; import {CDPSession} from './Connection.js'; import {ElementHandle} from './ElementHandle.js'; -import { - IsolatedWorld, - PageBinding, - WaitForSelectorOptions, -} from './IsolatedWorld.js'; +import {MAIN_WORLD, PageBinding, PUPPETEER_WORLD} from './IsolatedWorld.js'; import {InternalQueryHandler} from './QueryHandler.js'; async function queryAXTree( @@ -89,10 +85,10 @@ function parseAriaSelector(selector: string): ARIAQueryOption { return queryOptions; } -const queryOne = async ( - element: ElementHandle, - selector: string -): Promise | null> => { +const queryOne: InternalQueryHandler['queryOne'] = async ( + element, + selector +) => { const exeCtx = element.executionContext(); const {name, role} = parseAriaSelector(selector); const res = await queryAXTree(exeCtx._client, element, name, role); @@ -104,20 +100,22 @@ const queryOne = async ( )) as ElementHandle; }; -const waitFor = async ( - isolatedWorld: IsolatedWorld, - selector: string, - options: WaitForSelectorOptions -): Promise | null> => { +const waitFor: InternalQueryHandler['waitFor'] = async ( + element, + selector, + options +) => { + element = await element.frame.worlds[PUPPETEER_WORLD].adoptHandle(element); const binding: PageBinding = { name: 'ariaQuerySelector', pptrFunction: async (selector: string) => { - const root = options.root || (await isolatedWorld.document()); - const element = await queryOne(root, selector); - return element; + const result = await queryOne(element, selector); + return result; }, }; - return (await isolatedWorld._waitForSelectorInPage( + const result = await element.frame.worlds[ + PUPPETEER_WORLD + ]._waitForSelectorInPage( (_: Element, selector: string) => { return ( globalThis as unknown as { @@ -125,16 +123,26 @@ const waitFor = async ( } ).ariaQuerySelector(selector); }, + element, selector, options, binding - )) as ElementHandle | null; + ); + await element.dispose(); + if (!result) { + return null; + } + if (!(result instanceof ElementHandle)) { + await result.dispose(); + return null; + } + return element.frame.worlds[MAIN_WORLD].transferHandle(result); }; -const queryAll = async ( - element: ElementHandle, - selector: string -): Promise>> => { +const queryAll: InternalQueryHandler['queryAll'] = async ( + element, + selector +) => { const exeCtx = element.executionContext(); const {name, role} = parseAriaSelector(selector); const res = await queryAXTree(exeCtx._client, element, name, role); diff --git a/src/common/ElementHandle.ts b/src/common/ElementHandle.ts index bdb686ddaacdb..ed80b69bd0449 100644 --- a/src/common/ElementHandle.ts +++ b/src/common/ElementHandle.ts @@ -3,11 +3,7 @@ import {assert} from '../util/assert.js'; import {ExecutionContext} from './ExecutionContext.js'; import {Frame} from './Frame.js'; import {FrameManager} from './FrameManager.js'; -import { - MAIN_WORLD, - PUPPETEER_WORLD, - WaitForSelectorOptions, -} from './IsolatedWorld.js'; +import {WaitForSelectorOptions} from './IsolatedWorld.js'; import { BoundingBox, BoxModel, @@ -310,26 +306,16 @@ export class ElementHandle< */ async waitForSelector( selector: Selector, - options: Exclude = {} + options: WaitForSelectorOptions = {} ): Promise> | null> { - const frame = this.#frame; - const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this); - const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector( - selector, - { - ...options, - root: adoptedRoot, - } - ); - await adoptedRoot.dispose(); - if (!handle) { - return null; - } - const result = (await frame.worlds[MAIN_WORLD].adoptHandle( - handle - )) as ElementHandle>; - await handle.dispose(); - return result; + const {updatedSelector, queryHandler} = + getQueryHandlerAndSelector(selector); + assert(queryHandler.waitFor, 'Query handler does not support waiting'); + return (await queryHandler.waitFor( + this, + updatedSelector, + options + )) as ElementHandle> | null; } /** diff --git a/src/common/Frame.ts b/src/common/Frame.ts index e48719aa1d08d..4aaf977cdf960 100644 --- a/src/common/Frame.ts +++ b/src/common/Frame.ts @@ -579,18 +579,8 @@ export class Frame { selector: Selector, options: WaitForSelectorOptions = {} ): Promise> | null> { - const handle = await this.worlds[PUPPETEER_WORLD].waitForSelector( - selector, - options - ); - if (!handle) { - return null; - } - const mainHandle = (await this.worlds[MAIN_WORLD].adoptHandle( - handle - )) as ElementHandle>; - await handle.dispose(); - return mainHandle; + const document = await this.worlds[MAIN_WORLD].document(); + return document.waitForSelector(selector, options); } /** diff --git a/src/common/IsolatedWorld.ts b/src/common/IsolatedWorld.ts index 66a79b56471e9..4347294dc2b84 100644 --- a/src/common/IsolatedWorld.ts +++ b/src/common/IsolatedWorld.ts @@ -16,16 +16,19 @@ import {Protocol} from 'devtools-protocol'; import {assert} from '../util/assert.js'; +import { + createDeferredPromise, + DeferredPromise, +} from '../util/DeferredPromise.js'; import {CDPSession} from './Connection.js'; import {ElementHandle} from './ElementHandle.js'; import {TimeoutError} from './Errors.js'; import {ExecutionContext} from './ExecutionContext.js'; -import {FrameManager} from './FrameManager.js'; import {Frame} from './Frame.js'; +import {FrameManager} from './FrameManager.js'; import {MouseButton} from './Input.js'; import {JSHandle} from './JSHandle.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; -import {getQueryHandlerAndSelector} from './QueryHandler.js'; import {TimeoutSettings} from './TimeoutSettings.js'; import {EvaluateFunc, HandleFor, NodeFor} from './types.js'; import { @@ -37,10 +40,6 @@ import { makePredicateString, pageBindingInitString, } from './util.js'; -import { - createDeferredPromise, - DeferredPromise, -} from '../util/DeferredPromise.js'; // predicateQueryHandler and checkWaitForOptions are declared here so that // TypeScript knows about them when used in the predicate function below. @@ -80,10 +79,6 @@ export interface WaitForSelectorOptions { * @defaultValue `30000` (30 seconds) */ timeout?: number; - /** - * @deprecated Do not use. Use the {@link ElementHandle.waitForSelector} - */ - root?: ElementHandle; } /** @@ -294,20 +289,6 @@ export class IsolatedWorld { return document.$$eval(selector, pageFunction, ...args); } - async waitForSelector( - selector: Selector, - options: WaitForSelectorOptions - ): Promise> | null> { - const {updatedSelector, queryHandler} = - getQueryHandlerAndSelector(selector); - assert(queryHandler.waitFor, 'Query handler does not support waiting'); - return (await queryHandler.waitFor( - this, - updatedSelector, - options - )) as ElementHandle> | null; - } - async content(): Promise { return await this.evaluate(() => { let retVal = ''; @@ -707,10 +688,11 @@ export class IsolatedWorld { async _waitForSelectorInPage( queryOne: Function, + root: ElementHandle, selector: string, options: WaitForSelectorOptions, binding?: PageBinding - ): Promise | null> { + ): Promise | null> { const { visible: waitForVisible = false, hidden: waitForHidden = false, @@ -738,16 +720,10 @@ export class IsolatedWorld { timeout, args: [selector, waitForVisible, waitForHidden], binding, - root: options.root, + root, }; const waitTask = new WaitTask(waitTaskOptions); - const jsHandle = await waitTask.promise; - const elementHandle = jsHandle.asElement(); - if (!elementHandle) { - await jsHandle.dispose(); - return null; - } - return elementHandle; + return waitTask.promise; } waitForFunction( @@ -798,6 +774,12 @@ export class IsolatedWorld { }); return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T; } + + async transferHandle>(handle: T): Promise { + const result = this.adoptHandle(handle); + await handle.dispose(); + return result; + } } /** diff --git a/src/common/Page.ts b/src/common/Page.ts index e652e5a630b4e..f7e8a769efb41 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -3396,7 +3396,7 @@ export class Page extends EventEmitter { */ async waitForSelector( selector: Selector, - options: Exclude = {} + options: WaitForSelectorOptions = {} ): Promise> | null> { return await this.mainFrame().waitForSelector(selector, options); } diff --git a/src/common/QueryHandler.ts b/src/common/QueryHandler.ts index dc73558ec43b6..b8e888c17f7b3 100644 --- a/src/common/QueryHandler.ts +++ b/src/common/QueryHandler.ts @@ -16,7 +16,11 @@ import {ariaHandler} from './AriaQueryHandler.js'; import {ElementHandle} from './ElementHandle.js'; -import {IsolatedWorld, WaitForSelectorOptions} from './IsolatedWorld.js'; +import { + MAIN_WORLD, + PUPPETEER_WORLD, + WaitForSelectorOptions, +} from './IsolatedWorld.js'; /** * @public @@ -58,11 +62,9 @@ export interface InternalQueryHandler { /** * Waits until a single node appears for a given selector and * {@link ElementHandle}. - * - * Akin to {@link Window.prototype.querySelectorAll}. */ waitFor?: ( - isolatedWorld: IsolatedWorld, + element: ElementHandle, selector: string, options: WaitForSelectorOptions ) => Promise | null>; @@ -84,12 +86,22 @@ function internalizeCustomQueryHandler( await jsHandle.dispose(); return null; }; - internalHandler.waitFor = ( - domWorld: IsolatedWorld, - selector: string, - options: WaitForSelectorOptions - ) => { - return domWorld._waitForSelectorInPage(queryOne, selector, options); + internalHandler.waitFor = async (element, selector, options) => { + element = await element.frame.worlds[PUPPETEER_WORLD].adoptHandle( + element + ); + const result = await element.frame.worlds[ + PUPPETEER_WORLD + ]._waitForSelectorInPage(queryOne, element, selector, options); + await element.dispose(); + if (!result) { + return null; + } + if (!(result instanceof ElementHandle)) { + await result.dispose(); + return null; + } + return element.frame.worlds[MAIN_WORLD].transferHandle(result); }; }