From 1bb019ac817852ca50125f0f47abb335831174ba Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 27 Dec 2022 09:22:22 -0800 Subject: [PATCH] chore: migrate most of rerunnable tasks to element callback (#19713) --- packages/playwright-core/src/server/frames.ts | 117 ++++++------------ tests/library/inspector/pause.spec.ts | 1 - 2 files changed, 39 insertions(+), 79 deletions(-) diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 4c82ec56ba..a3be598fd3 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -84,6 +84,7 @@ export type NavigationEvent = { export type SchedulableTask = (injectedScript: js.JSHandle) => Promise>>; export type DomTaskBody = (progress: InjectedScriptProgress, element: E, data: T, elements: Element[]) => R | symbol; +type ElementCallback = (injected: InjectedScript, element: Element, data: T) => R; export class NavigationAbortedError extends Error { readonly documentId?: string; @@ -823,8 +824,8 @@ export class Frame extends SdkObject { } async dispatchEvent(metadata: CallMetadata, selector: string, type: string, eventInit: Object = {}, options: types.QueryOnSelectorOptions = {}): Promise { - await this._scheduleRerunnableTask(metadata, selector, (progress, element, data) => { - progress.injectedScript.dispatchEvent(element, data.type, data.eventInit); + await this._callOnElementOnceMatches(metadata, selector, (injectedScript, element, data) => { + injectedScript.dispatchEvent(element, data.type, data.eventInit); }, { type, eventInit }, { mainWorld: true, ...options }); } @@ -1134,14 +1135,14 @@ export class Frame extends SdkObject { return false; } - private async _resolveInjectedForSelector(progress: Progress, selector: string, strict: boolean | undefined): Promise<{ injected: js.JSHandle, info: SelectorInfo } | undefined> { - const selectorInFrame = await this.resolveFrameForSelectorNoWait(selector, { strict }); + private async _resolveInjectedForSelector(progress: Progress, selector: string, options: { strict?: boolean, mainWorld?: boolean }): Promise<{ injected: js.JSHandle, info: SelectorInfo } | undefined> { + const selectorInFrame = await this.resolveFrameForSelectorNoWait(selector, options); if (!selectorInFrame) return; progress.throwIfAborted(); // Be careful, |this| can be different from |selectorInFrame.frame|. - const context = await selectorInFrame.frame._context(selectorInFrame.info.world); + const context = await selectorInFrame.frame._context(options.mainWorld ? 'main' : selectorInFrame.info.world); const injected = await context.injectedScript(); progress.throwIfAborted(); return { injected, info: selectorInFrame.info }; @@ -1154,7 +1155,7 @@ export class Frame extends SdkObject { action: (handle: dom.ElementHandle) => Promise): Promise { progress.log(`waiting for ${this._asLocator(selector)}`); return this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => { - const resolved = await this._resolveInjectedForSelector(progress, selector, strict); + const resolved = await this._resolveInjectedForSelector(progress, selector, { strict }); if (!resolved) return continuePolling; const result = await resolved.injected.evaluateHandle((injected, { info }) => { @@ -1268,30 +1269,30 @@ export class Frame extends SdkObject { } async textContent(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise { - return this._scheduleRerunnableTask(metadata, selector, (progress, element) => element.textContent, undefined, options); + return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.textContent, undefined, options); } async innerText(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise { - return this._scheduleRerunnableTask(metadata, selector, (progress, element) => { + return this._callOnElementOnceMatches(metadata, selector, (injectedScript, element) => { if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml') - throw progress.injectedScript.createStacklessError('Node is not an HTMLElement'); + throw injectedScript.createStacklessError('Node is not an HTMLElement'); return (element as HTMLElement).innerText; }, undefined, options); } async innerHTML(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise { - return this._scheduleRerunnableTask(metadata, selector, (progress, element) => element.innerHTML, undefined, options); + return this._callOnElementOnceMatches(metadata, selector, (injected, element) => element.innerHTML, undefined, options); } async getAttribute(metadata: CallMetadata, selector: string, name: string, options: types.QueryOnSelectorOptions = {}): Promise { - return this._scheduleRerunnableTask(metadata, selector, (progress, element, data) => element.getAttribute(data.name), { name }, options); + return this._callOnElementOnceMatches(metadata, selector, (injected, element, data) => element.getAttribute(data.name), { name }, options); } async inputValue(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions = {}): Promise { - return this._scheduleRerunnableTask(metadata, selector, (progress, node) => { - const element = progress.injectedScript.retarget(node, 'follow-label'); + return this._callOnElementOnceMatches(metadata, selector, (injectedScript, node) => { + const element = injectedScript.retarget(node, 'follow-label'); if (!element || (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')) - throw progress.injectedScript.createStacklessError('Node is not an ,