From d560299aa815f8b666c5963a0611519489e95707 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> Date: Fri, 16 Jun 2023 09:16:04 +0200 Subject: [PATCH] chore: add waitForSelector for BiDi (#10383) --- package-lock.json | 16 +- packages/puppeteer-core/package.json | 2 +- .../puppeteer-core/src/api/ElementHandle.ts | 35 +- packages/puppeteer-core/src/api/Frame.ts | 96 ++++- .../src/common/ElementHandle.ts | 42 --- packages/puppeteer-core/src/common/Frame.ts | 100 ++---- .../puppeteer-core/src/common/FrameManager.ts | 5 +- .../src/common/IsolatedWorld.ts | 3 +- .../puppeteer-core/src/common/QueryHandler.ts | 7 +- .../puppeteer-core/src/common/WaitTask.ts | 6 +- .../src/common/bidi/BrowsingContext.ts | 155 +-------- .../src/common/bidi/Connection.ts | 8 +- .../src/common/bidi/ElementHandle.ts | 18 +- .../puppeteer-core/src/common/bidi/Frame.ts | 24 +- .../src/common/bidi/JSHandle.ts | 17 +- .../puppeteer-core/src/common/bidi/Page.ts | 16 +- .../puppeteer-core/src/common/bidi/Realm.ts | 171 +++++++++ .../puppeteer-core/src/common/bidi/Sandbox.ts | 122 ++++++- .../src/common/bidi/Serializer.ts | 5 +- .../puppeteer-core/src/common/bidi/utils.ts | 6 +- test/TestExpectations.json | 328 +++++++++++++++--- test/src/headful.spec.ts | 2 +- test/src/launcher.spec.ts | 2 +- 23 files changed, 795 insertions(+), 391 deletions(-) create mode 100644 packages/puppeteer-core/src/common/bidi/Realm.ts diff --git a/package-lock.json b/package-lock.json index 5143584e..2aa23273 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3085,9 +3085,9 @@ "license": "ISC" }, "node_modules/chromium-bidi": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.11.tgz", - "integrity": "sha512-p03ajLhlQ5gebw3cmbDBFmBc2wnJM5dnXS8Phu6mblGn/KQd76yOVL5VwE0VAisa7oazNfKGTaXlIZ8Q5Bb9OA==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.12.tgz", + "integrity": "sha512-yl0ngMHtYUGJa2G0lkcbPvbnUZ9WMQyMNSfYmlrGD1nHRNyI9KOGw3dOaofFugXHHToneUaSmF9iUdgCBamCjA==", "dependencies": { "mitt": "3.0.0" }, @@ -10164,7 +10164,7 @@ "license": "Apache-2.0", "dependencies": { "@puppeteer/browsers": "1.4.1", - "chromium-bidi": "0.4.11", + "chromium-bidi": "0.4.12", "cross-fetch": "3.1.6", "debug": "4.3.4", "devtools-protocol": "0.0.1135028", @@ -12317,9 +12317,9 @@ "version": "1.1.4" }, "chromium-bidi": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.11.tgz", - "integrity": "sha512-p03ajLhlQ5gebw3cmbDBFmBc2wnJM5dnXS8Phu6mblGn/KQd76yOVL5VwE0VAisa7oazNfKGTaXlIZ8Q5Bb9OA==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.12.tgz", + "integrity": "sha512-yl0ngMHtYUGJa2G0lkcbPvbnUZ9WMQyMNSfYmlrGD1nHRNyI9KOGw3dOaofFugXHHToneUaSmF9iUdgCBamCjA==", "requires": { "mitt": "3.0.0" } @@ -15738,7 +15738,7 @@ "version": "file:packages/puppeteer-core", "requires": { "@puppeteer/browsers": "1.4.1", - "chromium-bidi": "0.4.11", + "chromium-bidi": "0.4.12", "cross-fetch": "3.1.6", "debug": "4.3.4", "devtools-protocol": "0.0.1135028", diff --git a/packages/puppeteer-core/package.json b/packages/puppeteer-core/package.json index 6bd592a0..06adcc78 100644 --- a/packages/puppeteer-core/package.json +++ b/packages/puppeteer-core/package.json @@ -132,7 +132,7 @@ "author": "The Chromium Authors", "license": "Apache-2.0", "dependencies": { - "chromium-bidi": "0.4.11", + "chromium-bidi": "0.4.12", "cross-fetch": "3.1.6", "debug": "4.3.4", "devtools-protocol": "0.0.1135028", diff --git a/packages/puppeteer-core/src/api/ElementHandle.ts b/packages/puppeteer-core/src/api/ElementHandle.ts index f5314b82..59a6f78d 100644 --- a/packages/puppeteer-core/src/api/ElementHandle.ts +++ b/packages/puppeteer-core/src/api/ElementHandle.ts @@ -16,11 +16,13 @@ import {Protocol} from 'devtools-protocol'; +import {Frame} from '../api/Frame.js'; import {CDPSession} from '../common/Connection.js'; import {ExecutionContext} from '../common/ExecutionContext.js'; import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; import {MouseClickOptions} from '../common/Input.js'; import {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; +import {LazyArg} from '../common/LazyArg.js'; import { ElementFor, EvaluateFuncWith, @@ -33,7 +35,6 @@ import {isString, withSourcePuppeteerURLIfNone} from '../common/util.js'; import {assert} from '../util/assert.js'; import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js'; -import {Frame} from './Frame.js'; import {JSHandle} from './JSHandle.js'; import {ScreenshotOptions} from './Page.js'; @@ -485,12 +486,30 @@ export class ElementHandle< )) as ElementHandle> | null; } + async #checkVisibility(visibility: boolean): Promise { + const element = await this.frame.isolatedRealm().adoptHandle(this); + try { + return await this.frame.isolatedRealm().evaluate( + async (PuppeteerUtil, element, visibility) => { + return Boolean(PuppeteerUtil.checkVisibility(element, visibility)); + }, + LazyArg.create(context => { + return context.puppeteerUtil; + }), + element, + visibility + ); + } finally { + await element.dispose(); + } + } + /** * Checks if an element is visible using the same mechanism as * {@link ElementHandle.waitForSelector}. */ async isVisible(): Promise { - throw new Error('Not implemented.'); + return this.#checkVisibility(true); } /** @@ -498,7 +517,7 @@ export class ElementHandle< * {@link ElementHandle.waitForSelector}. */ async isHidden(): Promise { - throw new Error('Not implemented.'); + return this.#checkVisibility(false); } /** @@ -565,14 +584,16 @@ export class ElementHandle< */ async waitForXPath( xpath: string, - options?: { + options: { visible?: boolean; hidden?: boolean; timeout?: number; + } = {} + ): Promise | null> { + if (xpath.startsWith('//')) { + xpath = `.${xpath}`; } - ): Promise | null>; - async waitForXPath(): Promise | null> { - throw new Error('Not implemented'); + return this.waitForSelector(`xpath/${xpath}`, options); } /** diff --git a/packages/puppeteer-core/src/api/Frame.ts b/packages/puppeteer-core/src/api/Frame.ts index 85d6f919..a45cc2fb 100644 --- a/packages/puppeteer-core/src/api/Frame.ts +++ b/packages/puppeteer-core/src/api/Frame.ts @@ -20,6 +20,7 @@ import {Page, WaitTimeoutOptions} from '../api/Page.js'; import {CDPSession} from '../common/Connection.js'; import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js'; import {ExecutionContext} from '../common/ExecutionContext.js'; +import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; import { IsolatedWorldChart, WaitForSelectorOptions, @@ -29,11 +30,52 @@ import { EvaluateFunc, EvaluateFuncWith, HandleFor, + InnerLazyParams, NodeFor, } from '../common/types.js'; +import {TaskManager} from '../common/WaitTask.js'; +import {JSHandle} from './JSHandle.js'; import {Locator} from './Locator.js'; +/** + * @internal + */ +export interface Realm { + taskManager: TaskManager; + waitForFunction< + Params extends unknown[], + Func extends EvaluateFunc> = EvaluateFunc< + InnerLazyParams + > + >( + pageFunction: Func | string, + options: { + polling?: 'raf' | 'mutation' | number; + timeout?: number; + root?: ElementHandle; + signal?: AbortSignal; + }, + ...args: Params + ): Promise>>>; + adoptHandle>(handle: T): Promise; + transferHandle>(handle: T): Promise; + evaluateHandle< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + pageFunction: Func | string, + ...args: Params + ): Promise>>>; + evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + pageFunction: Func | string, + ...args: Params + ): Promise>>; +} + /** * @public */ @@ -307,6 +349,20 @@ export class Frame { throw new Error('Not implemented'); } + /** + * @internal + */ + mainRealm(): Realm { + throw new Error('Not implemented'); + } + + /** + * @internal + */ + isolatedRealm(): Realm { + throw new Error('Not implemented'); + } + /** * Behaves identically to {@link Page.evaluateHandle} except it's run within * the context of this frame. @@ -529,12 +585,15 @@ export class Frame { */ async waitForSelector( selector: Selector, - options?: WaitForSelectorOptions - ): Promise> | null>; - async waitForSelector(): Promise - > | null> { - throw new Error('Not implemented'); + options: WaitForSelectorOptions = {} + ): Promise> | null> { + const {updatedSelector, QueryHandler} = + getQueryHandlerAndSelector(selector); + return (await QueryHandler.waitFor( + this, + updatedSelector, + options + )) as ElementHandle> | null; } /** @@ -561,10 +620,12 @@ export class Frame { */ async waitForXPath( xpath: string, - options?: WaitForSelectorOptions - ): Promise | null>; - async waitForXPath(): Promise | null> { - throw new Error('Not implemented'); + options: WaitForSelectorOptions = {} + ): Promise | null> { + if (xpath.startsWith('//')) { + xpath = `.${xpath}`; + } + return this.waitForSelector(`xpath/${xpath}`, options); } /** @@ -605,16 +666,15 @@ export class Frame { Func extends EvaluateFunc = EvaluateFunc >( pageFunction: Func | string, - options?: FrameWaitForFunctionOptions, + options: FrameWaitForFunctionOptions = {}, ...args: Params - ): Promise>>>; - waitForFunction< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >(): Promise>>> { - throw new Error('Not implemented'); + ): Promise>>> { + return this.mainRealm().waitForFunction( + pageFunction, + options, + ...args + ) as Promise>>>; } - /** * The full HTML contents of the frame, including the DOCTYPE. */ diff --git a/packages/puppeteer-core/src/common/ElementHandle.ts b/packages/puppeteer-core/src/common/ElementHandle.ts index c91aff90..02041338 100644 --- a/packages/puppeteer-core/src/common/ElementHandle.ts +++ b/packages/puppeteer-core/src/common/ElementHandle.ts @@ -33,9 +33,7 @@ import {ExecutionContext} from './ExecutionContext.js'; import {Frame} from './Frame.js'; import {FrameManager} from './FrameManager.js'; import {WaitForSelectorOptions} from './IsolatedWorld.js'; -import {PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {CDPJSHandle} from './JSHandle.js'; -import {LazyArg} from './LazyArg.js'; import {CDPPage} from './Page.js'; import {NodeFor} from './types.js'; import {KeyInput} from './USKeyboardLayout.js'; @@ -128,46 +126,6 @@ export class CDPElementHandle< > | null; } - override async waitForXPath( - xpath: string, - options: { - visible?: boolean; - hidden?: boolean; - timeout?: number; - } = {} - ): Promise | null> { - if (xpath.startsWith('//')) { - xpath = `.${xpath}`; - } - return this.waitForSelector(`xpath/${xpath}`, options); - } - - async #checkVisibility(visibility: boolean): Promise { - const element = await this.frame.worlds[PUPPETEER_WORLD].adoptHandle(this); - try { - return await this.frame.worlds[PUPPETEER_WORLD].evaluate( - async (PuppeteerUtil, element, visibility) => { - return Boolean(PuppeteerUtil.checkVisibility(element, visibility)); - }, - LazyArg.create(context => { - return context.puppeteerUtil; - }), - element, - visibility - ); - } finally { - await element.dispose(); - } - } - - override async isVisible(): Promise { - return this.#checkVisibility(true); - } - - override async isHidden(): Promise { - return this.#checkVisibility(false); - } - override async contentFrame(): Promise { const nodeInfo = await this.client.send('DOM.describeNode', { objectId: this.id, diff --git a/packages/puppeteer-core/src/common/Frame.ts b/packages/puppeteer-core/src/common/Frame.ts index 864afd7c..a50f091f 100644 --- a/packages/puppeteer-core/src/common/Frame.ts +++ b/packages/puppeteer-core/src/common/Frame.ts @@ -21,7 +21,6 @@ import { Frame as BaseFrame, FrameAddScriptTagOptions, FrameAddStyleTagOptions, - FrameWaitForFunctionOptions, } from '../api/Frame.js'; import {HTTPResponse} from '../api/HTTPResponse.js'; import {Page, WaitTimeoutOptions} from '../api/Page.js'; @@ -36,12 +35,7 @@ import { } from './DeviceRequestPrompt.js'; import {ExecutionContext} from './ExecutionContext.js'; import {FrameManager} from './FrameManager.js'; -import {getQueryHandlerAndSelector} from './GetQueryHandler.js'; -import { - IsolatedWorld, - IsolatedWorldChart, - WaitForSelectorOptions, -} from './IsolatedWorld.js'; +import {IsolatedWorld, IsolatedWorldChart} from './IsolatedWorld.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {LazyArg} from './LazyArg.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; @@ -221,6 +215,20 @@ export class Frame extends BaseFrame { return this.worlds[MAIN_WORLD].executionContext(); } + /** + * @internal + */ + override mainRealm(): IsolatedWorld { + return this.worlds[MAIN_WORLD]; + } + + /** + * @internal + */ + override isolatedRealm(): IsolatedWorld { + return this.worlds[PUPPETEER_WORLD]; + } + override async evaluateHandle< Params extends unknown[], Func extends EvaluateFunc = EvaluateFunc @@ -232,7 +240,7 @@ export class Frame extends BaseFrame { this.evaluateHandle.name, pageFunction ); - return this.worlds[MAIN_WORLD].evaluateHandle(pageFunction, ...args); + return this.mainRealm().evaluateHandle(pageFunction, ...args); } override async evaluate< @@ -246,19 +254,19 @@ export class Frame extends BaseFrame { this.evaluate.name, pageFunction ); - return this.worlds[MAIN_WORLD].evaluate(pageFunction, ...args); + return this.mainRealm().evaluate(pageFunction, ...args); } override async $( selector: Selector ): Promise> | null> { - return this.worlds[MAIN_WORLD].$(selector); + return this.mainRealm().$(selector); } override async $$( selector: Selector ): Promise>>> { - return this.worlds[MAIN_WORLD].$$(selector); + return this.mainRealm().$$(selector); } override async $eval< @@ -274,7 +282,7 @@ export class Frame extends BaseFrame { ...args: Params ): Promise>> { pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction); - return this.worlds[MAIN_WORLD].$eval(selector, pageFunction, ...args); + return this.mainRealm().$eval(selector, pageFunction, ...args); } override async $$eval< @@ -290,53 +298,15 @@ export class Frame extends BaseFrame { ...args: Params ): Promise>> { pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction); - return this.worlds[MAIN_WORLD].$$eval(selector, pageFunction, ...args); + return this.mainRealm().$$eval(selector, pageFunction, ...args); } override async $x(expression: string): Promise>> { - return this.worlds[MAIN_WORLD].$x(expression); - } - - override async waitForSelector( - selector: Selector, - options: WaitForSelectorOptions = {} - ): Promise> | null> { - const {updatedSelector, QueryHandler} = - getQueryHandlerAndSelector(selector); - return (await QueryHandler.waitFor( - this, - updatedSelector, - options - )) as ElementHandle> | null; - } - - override async waitForXPath( - xpath: string, - options: WaitForSelectorOptions = {} - ): Promise | null> { - if (xpath.startsWith('//')) { - xpath = `.${xpath}`; - } - return this.waitForSelector(`xpath/${xpath}`, options); - } - - override waitForFunction< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >( - pageFunction: Func | string, - options: FrameWaitForFunctionOptions = {}, - ...args: Params - ): Promise>>> { - return this.worlds[MAIN_WORLD].waitForFunction( - pageFunction, - options, - ...args - ) as Promise>>>; + return this.mainRealm().$x(expression); } override async content(): Promise { - return this.worlds[PUPPETEER_WORLD].content(); + return this.isolatedRealm().content(); } override async setContent( @@ -346,7 +316,7 @@ export class Frame extends BaseFrame { waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; } = {} ): Promise { - return this.worlds[PUPPETEER_WORLD].setContent(html, options); + return this.isolatedRealm().setContent(html, options); } override name(): string { @@ -388,8 +358,8 @@ export class Frame extends BaseFrame { type = type ?? 'text/javascript'; - return this.worlds[MAIN_WORLD].transferHandle( - await this.worlds[PUPPETEER_WORLD].evaluateHandle( + return this.mainRealm().transferHandle( + await this.isolatedRealm().evaluateHandle( async ({Deferred}, {url, id, type, content}) => { const deferred = Deferred.create(); const script = document.createElement('script'); @@ -456,8 +426,8 @@ export class Frame extends BaseFrame { options.content = content; } - return this.worlds[MAIN_WORLD].transferHandle( - await this.worlds[PUPPETEER_WORLD].evaluateHandle( + return this.mainRealm().transferHandle( + await this.isolatedRealm().evaluateHandle( async ({Deferred}, {url, content}) => { const deferred = Deferred.create(); let element: HTMLStyleElement | HTMLLinkElement; @@ -504,23 +474,23 @@ export class Frame extends BaseFrame { selector: string, options: Readonly = {} ): Promise { - return this.worlds[PUPPETEER_WORLD].click(selector, options); + return this.isolatedRealm().click(selector, options); } override async focus(selector: string): Promise { - return this.worlds[PUPPETEER_WORLD].focus(selector); + return this.isolatedRealm().focus(selector); } override async hover(selector: string): Promise { - return this.worlds[PUPPETEER_WORLD].hover(selector); + return this.isolatedRealm().hover(selector); } override select(selector: string, ...values: string[]): Promise { - return this.worlds[PUPPETEER_WORLD].select(selector, ...values); + return this.isolatedRealm().select(selector, ...values); } override async tap(selector: string): Promise { - return this.worlds[PUPPETEER_WORLD].tap(selector); + return this.isolatedRealm().tap(selector); } override async type( @@ -528,11 +498,11 @@ export class Frame extends BaseFrame { text: string, options?: {delay: number} ): Promise { - return this.worlds[PUPPETEER_WORLD].type(selector, text, options); + return this.isolatedRealm().type(selector, text, options); } override async title(): Promise { - return this.worlds[PUPPETEER_WORLD].title(); + return this.isolatedRealm().title(); } _deviceRequestPromptManager(): DeviceRequestPromptManager { diff --git a/packages/puppeteer-core/src/common/FrameManager.ts b/packages/puppeteer-core/src/common/FrameManager.ts index 245d3422..1232e3ea 100644 --- a/packages/puppeteer-core/src/common/FrameManager.ts +++ b/packages/puppeteer-core/src/common/FrameManager.ts @@ -34,7 +34,10 @@ import {Target} from './Target.js'; import {TimeoutSettings} from './TimeoutSettings.js'; import {debugError, PuppeteerURL} from './util.js'; -const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; +/** + * @internal + */ +export const UTILITY_WORLD_NAME = '__puppeteer_utility_world__'; /** * We use symbols to prevent external parties listening to these events. diff --git a/packages/puppeteer-core/src/common/IsolatedWorld.ts b/packages/puppeteer-core/src/common/IsolatedWorld.ts index ed3706b8..8700c348 100644 --- a/packages/puppeteer-core/src/common/IsolatedWorld.ts +++ b/packages/puppeteer-core/src/common/IsolatedWorld.ts @@ -17,6 +17,7 @@ import {Protocol} from 'devtools-protocol'; import type {ClickOptions, ElementHandle} from '../api/ElementHandle.js'; +import {Realm} from '../api/Frame.js'; import {JSHandle} from '../api/JSHandle.js'; import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; @@ -99,7 +100,7 @@ export interface IsolatedWorldChart { /** * @internal */ -export class IsolatedWorld { +export class IsolatedWorld implements Realm { #frame: Frame; #document?: ElementHandle; #context = Deferred.create(); diff --git a/packages/puppeteer-core/src/common/QueryHandler.ts b/packages/puppeteer-core/src/common/QueryHandler.ts index 0d572676..5e4d8c70 100644 --- a/packages/puppeteer-core/src/common/QueryHandler.ts +++ b/packages/puppeteer-core/src/common/QueryHandler.ts @@ -22,7 +22,6 @@ import {interpolateFunction, stringifyFunction} from '../util/Function.js'; import {transposeIterableHandle} from './HandleIterator.js'; import type {WaitForSelectorOptions} from './IsolatedWorld.js'; -import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {LazyArg} from './LazyArg.js'; import type {Awaitable, AwaitableIterable} from './types.js'; @@ -160,7 +159,7 @@ export class QueryHandler { frame = elementOrFrame; } else { frame = elementOrFrame.frame; - element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(elementOrFrame); + element = await frame.isolatedRealm().adoptHandle(elementOrFrame); } const {visible = false, hidden = false, timeout, signal} = options; @@ -168,7 +167,7 @@ export class QueryHandler { try { signal?.throwIfAborted(); - const handle = await frame.worlds[PUPPETEER_WORLD].waitForFunction( + const handle = await frame.isolatedRealm().waitForFunction( async (PuppeteerUtil, query, selector, root, visible) => { const querySelector = PuppeteerUtil.createFunction( query @@ -204,7 +203,7 @@ export class QueryHandler { await handle.dispose(); return null; } - return frame.worlds[MAIN_WORLD].transferHandle(handle); + return frame.mainRealm().transferHandle(handle); } catch (error) { if (!isErrorLike(error)) { throw error; diff --git a/packages/puppeteer-core/src/common/WaitTask.ts b/packages/puppeteer-core/src/common/WaitTask.ts index 17595c10..9ce1b578 100644 --- a/packages/puppeteer-core/src/common/WaitTask.ts +++ b/packages/puppeteer-core/src/common/WaitTask.ts @@ -15,6 +15,7 @@ */ import {ElementHandle} from '../api/ElementHandle.js'; +import {Realm} from '../api/Frame.js'; import {JSHandle} from '../api/JSHandle.js'; import type {Poller} from '../injected/Poller.js'; import {Deferred} from '../util/Deferred.js'; @@ -22,7 +23,6 @@ import {isErrorLike} from '../util/ErrorLike.js'; import {stringifyFunction} from '../util/Function.js'; import {TimeoutError} from './Errors.js'; -import {IsolatedWorld} from './IsolatedWorld.js'; import {LazyArg} from './LazyArg.js'; import {HandleFor} from './types.js'; @@ -40,7 +40,7 @@ export interface WaitTaskOptions { * @internal */ export class WaitTask { - #world: IsolatedWorld; + #world: Realm; #polling: 'raf' | 'mutation' | number; #root?: ElementHandle; @@ -55,7 +55,7 @@ export class WaitTask { #signal?: AbortSignal; constructor( - world: IsolatedWorld, + world: Realm, options: WaitTaskOptions, fn: ((...args: unknown[]) => Promise) | string, ...args: unknown[] diff --git a/packages/puppeteer-core/src/common/bidi/BrowsingContext.ts b/packages/puppeteer-core/src/common/bidi/BrowsingContext.ts index 22126df8..17edd3b2 100644 --- a/packages/puppeteer-core/src/common/bidi/BrowsingContext.ts +++ b/packages/puppeteer-core/src/common/bidi/BrowsingContext.ts @@ -2,37 +2,17 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js'; import {WaitForOptions} from '../../api/Page.js'; -import PuppeteerUtil from '../../injected/injected.js'; import {assert} from '../../util/assert.js'; import {Deferred} from '../../util/Deferred.js'; -import {stringifyFunction} from '../../util/Function.js'; import type {CDPSession, Connection as CDPConnection} from '../Connection.js'; import {ProtocolError, TimeoutError} from '../Errors.js'; import {EventEmitter} from '../EventEmitter.js'; import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js'; -import {scriptInjector} from '../ScriptInjector.js'; import {TimeoutSettings} from '../TimeoutSettings.js'; -import {EvaluateFunc, HandleFor} from '../types.js'; -import { - PuppeteerURL, - getPageContent, - getSourcePuppeteerURLIfAvailable, - isString, - setPageContent, - waitWithTimeout, -} from '../util.js'; +import {getPageContent, setPageContent, waitWithTimeout} from '../util.js'; import {Connection} from './Connection.js'; -import {ElementHandle} from './ElementHandle.js'; -import {JSHandle} from './JSHandle.js'; -import {BidiSerializer} from './Serializer.js'; -import {createEvaluationError} from './utils.js'; - -const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; - -const getSourceUrlComment = (url: string) => { - return `//# sourceURL=${url}`; -}; +import {Realm} from './Realm.js'; /** * @internal @@ -104,8 +84,7 @@ export class CDPSessionWrapper extends EventEmitter implements CDPSession { /** * @internal */ -export class BrowsingContext extends EventEmitter { - connection: Connection; +export class BrowsingContext extends Realm { #timeoutSettings: TimeoutSettings; #id: string; #url = 'about:blank'; @@ -116,27 +95,15 @@ export class BrowsingContext extends EventEmitter { timeoutSettings: TimeoutSettings, info: Bidi.BrowsingContext.Info ) { - super(); + super(connection, info.context); this.connection = connection; this.#timeoutSettings = timeoutSettings; this.#id = info.context; this.#cdpSession = new CDPSessionWrapper(this); } - #puppeteerUtil?: Promise>; - get puppeteerUtil(): Promise> { - const promise = Promise.resolve() as Promise; - scriptInjector.inject(script => { - if (this.#puppeteerUtil) { - void this.#puppeteerUtil.then(handle => { - void handle.dispose(); - }); - } - this.#puppeteerUtil = promise.then(() => { - return this.evaluateHandle(script) as Promise>; - }); - }, !this.#puppeteerUtil); - return this.#puppeteerUtil as Promise>; + createSandboxRealm(sandbox: string): Realm { + return new Realm(this.connection, this.#id, sandbox); } get url(): string { @@ -212,97 +179,6 @@ export class BrowsingContext extends EventEmitter { ); } - async evaluateHandle< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >( - pageFunction: Func | string, - ...args: Params - ): Promise>>> { - return this.#evaluate(false, pageFunction, ...args); - } - - async evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >( - pageFunction: Func | string, - ...args: Params - ): Promise>> { - return this.#evaluate(true, pageFunction, ...args); - } - - async #evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >( - returnByValue: true, - pageFunction: Func | string, - ...args: Params - ): Promise>>; - async #evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >( - returnByValue: false, - pageFunction: Func | string, - ...args: Params - ): Promise>>>; - async #evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc - >( - returnByValue: boolean, - pageFunction: Func | string, - ...args: Params - ): Promise>> | Awaited>> { - const sourceUrlComment = getSourceUrlComment( - getSourcePuppeteerURLIfAvailable(pageFunction)?.toString() ?? - PuppeteerURL.INTERNAL_URL - ); - - let responsePromise; - const resultOwnership = returnByValue ? 'none' : 'root'; - if (isString(pageFunction)) { - const expression = SOURCE_URL_REGEX.test(pageFunction) - ? pageFunction - : `${pageFunction}\n${sourceUrlComment}\n`; - - responsePromise = this.connection.send('script.evaluate', { - expression, - target: {context: this.#id}, - resultOwnership, - awaitPromise: true, - }); - } else { - let functionDeclaration = stringifyFunction(pageFunction); - functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration) - ? functionDeclaration - : `${functionDeclaration}\n${sourceUrlComment}\n`; - responsePromise = this.connection.send('script.callFunction', { - functionDeclaration, - arguments: await Promise.all( - args.map(arg => { - return BidiSerializer.serialize(arg, this); - }) - ), - target: {context: this.#id}, - resultOwnership, - awaitPromise: true, - }); - } - - const {result} = await responsePromise; - - if ('type' in result && result.type === 'exception') { - throw createEvaluationError(result.exceptionDetails); - } - - return returnByValue - ? BidiSerializer.deserialize(result.result) - : getBidiHandle(this, result.result); - } - async setContent( html: string, options: { @@ -344,29 +220,16 @@ export class BrowsingContext extends EventEmitter { return this.#cdpSession.send(method, ...paramArgs); } - dispose(): void { - this.removeAllListeners(); - this.connection.unregisterBrowsingContexts(this.#id); - } - title(): Promise { return this.evaluate(() => { return document.title; }); } -} -/** - * @internal - */ -export function getBidiHandle( - context: BrowsingContext, - result: Bidi.CommonDataTypes.RemoteValue -): JSHandle | ElementHandle { - if (result.type === 'node' || result.type === 'window') { - return new ElementHandle(context, result); + dispose(): void { + this.removeAllListeners(); + this.connection.unregisterBrowsingContexts(this.#id); } - return new JSHandle(context, result); } /** diff --git a/packages/puppeteer-core/src/common/bidi/Connection.ts b/packages/puppeteer-core/src/common/bidi/Connection.ts index a4341817..ed08033c 100644 --- a/packages/puppeteer-core/src/common/bidi/Connection.ts +++ b/packages/puppeteer-core/src/common/bidi/Connection.ts @@ -65,6 +65,10 @@ interface Commands { params: Bidi.Script.DisownParameters; returnType: Bidi.Script.DisownResult; }; + 'script.addPreloadScript': { + params: Bidi.Script.AddPreloadScriptParameters; + returnType: Bidi.Script.AddPreloadScriptResult; + }; 'browsingContext.create': { params: Bidi.BrowsingContext.CreateParameters; @@ -116,11 +120,11 @@ interface Commands { }; 'session.subscribe': { params: Bidi.Session.SubscriptionRequest; - returnType: Bidi.Session.SubscribeResult; + returnType: Bidi.Message.EmptyResult; }; 'session.unsubscribe': { params: Bidi.Session.SubscriptionRequest; - returnType: Bidi.Session.UnsubscribeResult; + returnType: Bidi.Message.EmptyResult; }; 'cdp.sendCommand': { params: Bidi.CDP.SendCommandParams; diff --git a/packages/puppeteer-core/src/common/bidi/ElementHandle.ts b/packages/puppeteer-core/src/common/bidi/ElementHandle.ts index 453cefa8..b4e05030 100644 --- a/packages/puppeteer-core/src/common/bidi/ElementHandle.ts +++ b/packages/puppeteer-core/src/common/bidi/ElementHandle.ts @@ -18,8 +18,9 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import {ElementHandle as BaseElementHandle} from '../../api/ElementHandle.js'; -import {BrowsingContext} from './BrowsingContext.js'; +import {Frame} from './Frame.js'; import {JSHandle} from './JSHandle.js'; +import {Realm} from './Realm.js'; /** * @internal @@ -28,15 +29,22 @@ export class ElementHandle< ElementType extends Node = Element > extends BaseElementHandle { declare handle: JSHandle; + #frame: Frame; constructor( - context: BrowsingContext, - remoteValue: Bidi.CommonDataTypes.RemoteValue + realm: Realm, + remoteValue: Bidi.CommonDataTypes.RemoteValue, + frame: Frame ) { - super(new JSHandle(context, remoteValue)); + super(new JSHandle(realm, remoteValue)); + this.#frame = frame; } - context(): BrowsingContext { + override get frame(): Frame { + return this.#frame; + } + + context(): Realm { return this.handle.context(); } diff --git a/packages/puppeteer-core/src/common/bidi/Frame.ts b/packages/puppeteer-core/src/common/bidi/Frame.ts index 102c7807..ea56e037 100644 --- a/packages/puppeteer-core/src/common/bidi/Frame.ts +++ b/packages/puppeteer-core/src/common/bidi/Frame.ts @@ -16,7 +16,9 @@ import {ElementHandle} from '../../api/ElementHandle.js'; import {Frame as BaseFrame} from '../../api/Frame.js'; +import {UTILITY_WORLD_NAME} from '../FrameManager.js'; import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js'; +import {TimeoutSettings} from '../TimeoutSettings.js'; import {EvaluateFunc, EvaluateFuncWith, HandleFor, NodeFor} from '../types.js'; import {withSourcePuppeteerURLIfNone} from '../util.js'; @@ -40,7 +42,12 @@ export class Frame extends BaseFrame { sandboxes: SandboxChart; override _id: string; - constructor(page: Page, context: BrowsingContext, parentId?: string | null) { + constructor( + page: Page, + context: BrowsingContext, + timeoutSettings: TimeoutSettings, + parentId?: string | null + ) { super(); this.#page = page; this.#context = context; @@ -48,11 +55,22 @@ export class Frame extends BaseFrame { this._parentId = parentId ?? undefined; this.sandboxes = { - [MAIN_SANDBOX]: new Sandbox(context), - [PUPPETEER_SANDBOX]: new Sandbox(context), + [MAIN_SANDBOX]: new Sandbox(context, timeoutSettings), + [PUPPETEER_SANDBOX]: new Sandbox( + context.createSandboxRealm(UTILITY_WORLD_NAME), + timeoutSettings + ), }; } + override mainRealm(): Sandbox { + return this.sandboxes[MAIN_SANDBOX]; + } + + override isolatedRealm(): Sandbox { + return this.sandboxes[PUPPETEER_SANDBOX]; + } + override page(): Page { return this.#page; } diff --git a/packages/puppeteer-core/src/common/bidi/JSHandle.ts b/packages/puppeteer-core/src/common/bidi/JSHandle.ts index 38ae5a8e..5e249a85 100644 --- a/packages/puppeteer-core/src/common/bidi/JSHandle.ts +++ b/packages/puppeteer-core/src/common/bidi/JSHandle.ts @@ -21,26 +21,23 @@ import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js'; import {EvaluateFuncWith, HandleFor, HandleOr} from '../../common/types.js'; import {withSourcePuppeteerURLIfNone} from '../util.js'; -import {BrowsingContext} from './BrowsingContext.js'; +import {Realm} from './Realm.js'; import {BidiSerializer} from './Serializer.js'; import {releaseReference} from './utils.js'; export class JSHandle extends BaseJSHandle { #disposed = false; - #context; + #realm: Realm; #remoteValue; - constructor( - context: BrowsingContext, - remoteValue: Bidi.CommonDataTypes.RemoteValue - ) { + constructor(realm: Realm, remoteValue: Bidi.CommonDataTypes.RemoteValue) { super(); - this.#context = context; + this.#realm = realm; this.#remoteValue = remoteValue; } - context(): BrowsingContext { - return this.#context; + context(): Realm { + return this.#realm; } override get disposed(): boolean { @@ -136,7 +133,7 @@ export class JSHandle extends BaseJSHandle { } this.#disposed = true; if ('handle' in this.#remoteValue) { - await releaseReference(this.#context, this.#remoteValue); + await releaseReference(this.#realm, this.#remoteValue); } } diff --git a/packages/puppeteer-core/src/common/bidi/Page.ts b/packages/puppeteer-core/src/common/bidi/Page.ts index 324235c5..14b01a1b 100644 --- a/packages/puppeteer-core/src/common/bidi/Page.ts +++ b/packages/puppeteer-core/src/common/bidi/Page.ts @@ -53,12 +53,13 @@ import { import {Browser} from './Browser.js'; import {BrowserContext} from './BrowserContext.js'; -import {BrowsingContext, getBidiHandle} from './BrowsingContext.js'; +import {BrowsingContext} from './BrowsingContext.js'; import {Connection} from './Connection.js'; import {Frame} from './Frame.js'; import {HTTPRequest} from './HTTPRequest.js'; import {HTTPResponse} from './HTTPResponse.js'; import {NetworkManager} from './NetworkManager.js'; +import {getBidiHandle} from './Realm.js'; import {BidiSerializer} from './Serializer.js'; /** @@ -208,7 +209,13 @@ export class Page extends PageBase { ); this.#connection.registerBrowsingContexts(context); - const frame = new Frame(this, context, info.parent); + const frame = new Frame( + this, + context, + this.#timeoutSettings, + info.parent + ); + context.setFrame(frame); this.#frameTree.addFrame(frame); this.emit(FrameManagerEmittedEvents.FrameAttached, frame); @@ -250,12 +257,13 @@ export class Page extends PageBase { } #onLogEntryAdded(event: Bidi.Log.LogEntry): void { - if (!this.frame(event.source.context)) { + const frame = this.frame(event.source.context); + if (!frame) { return; } if (isConsoleLogEntry(event)) { const args = event.args.map(arg => { - return getBidiHandle(this.mainFrame().context(), arg); + return getBidiHandle(frame.context(), arg, frame); }); const text = args diff --git a/packages/puppeteer-core/src/common/bidi/Realm.ts b/packages/puppeteer-core/src/common/bidi/Realm.ts new file mode 100644 index 00000000..c633bf2d --- /dev/null +++ b/packages/puppeteer-core/src/common/bidi/Realm.ts @@ -0,0 +1,171 @@ +import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; + +import PuppeteerUtil from '../../injected/injected.js'; +import {stringifyFunction} from '../../util/Function.js'; +import {EventEmitter} from '../EventEmitter.js'; +import {scriptInjector} from '../ScriptInjector.js'; +import {EvaluateFunc, HandleFor} from '../types.js'; +import { + PuppeteerURL, + getSourcePuppeteerURLIfAvailable, + isString, +} from '../util.js'; + +import {Connection} from './Connection.js'; +import {ElementHandle} from './ElementHandle.js'; +import {Frame} from './Frame.js'; +import {JSHandle} from './JSHandle.js'; +import {BidiSerializer} from './Serializer.js'; +import {createEvaluationError} from './utils.js'; + +export const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; + +export const getSourceUrlComment = (url: string): string => { + return `//# sourceURL=${url}`; +}; + +export class Realm extends EventEmitter { + connection: Connection; + #frame!: Frame; + #id: string; + #sandbox?: string; + + constructor(connection: Connection, id: string, sandbox?: string) { + super(); + this.connection = connection; + this.#id = id; + this.#sandbox = sandbox; + } + + get target(): Bidi.Script.Target { + return { + context: this.#id, + sandbox: this.#sandbox, + }; + } + + setFrame(frame: Frame): void { + this.#frame = frame; + } + + protected internalPuppeteerUtil?: Promise>; + get puppeteerUtil(): Promise> { + const promise = Promise.resolve() as Promise; + scriptInjector.inject(script => { + if (this.internalPuppeteerUtil) { + void this.internalPuppeteerUtil.then(handle => { + void handle.dispose(); + }); + } + this.internalPuppeteerUtil = promise.then(() => { + return this.evaluateHandle(script) as Promise>; + }); + }, !this.internalPuppeteerUtil); + return this.internalPuppeteerUtil as Promise>; + } + + async evaluateHandle< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + pageFunction: Func | string, + ...args: Params + ): Promise>>> { + return this.#evaluate(false, pageFunction, ...args); + } + + async evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + pageFunction: Func | string, + ...args: Params + ): Promise>> { + return this.#evaluate(true, pageFunction, ...args); + } + + async #evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + returnByValue: true, + pageFunction: Func | string, + ...args: Params + ): Promise>>; + async #evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + returnByValue: false, + pageFunction: Func | string, + ...args: Params + ): Promise>>>; + async #evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + returnByValue: boolean, + pageFunction: Func | string, + ...args: Params + ): Promise>> | Awaited>> { + const sourceUrlComment = getSourceUrlComment( + getSourcePuppeteerURLIfAvailable(pageFunction)?.toString() ?? + PuppeteerURL.INTERNAL_URL + ); + + let responsePromise; + const resultOwnership = returnByValue ? 'none' : 'root'; + if (isString(pageFunction)) { + const expression = SOURCE_URL_REGEX.test(pageFunction) + ? pageFunction + : `${pageFunction}\n${sourceUrlComment}\n`; + + responsePromise = this.connection.send('script.evaluate', { + expression, + target: this.target, + resultOwnership, + awaitPromise: true, + }); + } else { + let functionDeclaration = stringifyFunction(pageFunction); + functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration) + ? functionDeclaration + : `${functionDeclaration}\n${sourceUrlComment}\n`; + responsePromise = this.connection.send('script.callFunction', { + functionDeclaration, + arguments: await Promise.all( + args.map(arg => { + return BidiSerializer.serialize(arg, this as any); + }) + ), + target: this.target, + resultOwnership, + awaitPromise: true, + }); + } + + const {result} = await responsePromise; + + if ('type' in result && result.type === 'exception') { + throw createEvaluationError(result.exceptionDetails); + } + + return returnByValue + ? BidiSerializer.deserialize(result.result) + : getBidiHandle(this as any, result.result, this.#frame); + } +} + +/** + * @internal + */ +export function getBidiHandle( + realmOrContext: Realm, + result: Bidi.CommonDataTypes.RemoteValue, + frame: Frame +): JSHandle | ElementHandle { + if (result.type === 'node' || result.type === 'window') { + return new ElementHandle(realmOrContext, result, frame); + } + return new JSHandle(realmOrContext, result); +} diff --git a/packages/puppeteer-core/src/common/bidi/Sandbox.ts b/packages/puppeteer-core/src/common/bidi/Sandbox.ts index 622f53e1..5cf035ac 100644 --- a/packages/puppeteer-core/src/common/bidi/Sandbox.ts +++ b/packages/puppeteer-core/src/common/bidi/Sandbox.ts @@ -15,11 +15,21 @@ */ import {ElementHandle} from '../../api/ElementHandle.js'; -import {withSourcePuppeteerURLIfNone} from '../common.js'; -import {EvaluateFuncWith, NodeFor} from '../types.js'; - -import {BrowsingContext} from './BrowsingContext.js'; +import {Realm as RealmBase} from '../../api/Frame.js'; +import {JSHandle as BaseJSHandle} from '../../api/JSHandle.js'; +import {TimeoutSettings} from '../TimeoutSettings.js'; +import { + EvaluateFunc, + EvaluateFuncWith, + HandleFor, + InnerLazyParams, + NodeFor, +} from '../types.js'; +import {withSourcePuppeteerURLIfNone} from '../util.js'; +import {TaskManager, WaitTask} from '../WaitTask.js'; +import {JSHandle} from './JSHandle.js'; +import {Realm} from './Realm.js'; /** * A unique key for {@link SandboxChart} to denote the default world. * Realms are automatically created in the default sandbox. @@ -47,19 +57,27 @@ export interface SandboxChart { /** * @internal */ -export class Sandbox { +export class Sandbox implements RealmBase { #document?: ElementHandle; - #context: BrowsingContext; + #realm: Realm; - constructor(context: BrowsingContext) { - this.#context = context; + #timeoutSettings: TimeoutSettings; + #taskManager = new TaskManager(); + + constructor(context: Realm, timeoutSettings: TimeoutSettings) { + this.#realm = context; + this.#timeoutSettings = timeoutSettings; + } + + get taskManager(): TaskManager { + return this.#taskManager; } async document(): Promise> { if (this.#document) { return this.#document; } - this.#document = await this.#context.evaluateHandle(() => { + this.#document = await this.#realm.evaluateHandle(() => { return document; }); return this.#document; @@ -117,4 +135,90 @@ export class Sandbox { const document = await this.document(); return document.$x(expression); } + + async evaluateHandle< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + pageFunction: Func | string, + ...args: Params + ): Promise>>> { + pageFunction = withSourcePuppeteerURLIfNone( + this.evaluateHandle.name, + pageFunction + ); + return this.#realm.evaluateHandle(pageFunction, ...args); + } + + async evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc + >( + pageFunction: Func | string, + ...args: Params + ): Promise>> { + pageFunction = withSourcePuppeteerURLIfNone( + this.evaluate.name, + pageFunction + ); + return this.#realm.evaluate(pageFunction, ...args); + } + + async adoptHandle>(handle: T): Promise { + return (await this.evaluateHandle(node => { + return node; + }, handle)) as unknown as T; + } + + async transferHandle>(handle: T): Promise { + if ((handle as unknown as JSHandle).context() === this.#realm) { + return handle; + } + const transferredHandle = await this.evaluateHandle(node => { + return node; + }, handle); + + await handle.dispose(); + return transferredHandle as unknown as T; + } + + waitForFunction< + Params extends unknown[], + Func extends EvaluateFunc> = EvaluateFunc< + InnerLazyParams + > + >( + pageFunction: Func | string, + options: { + polling?: 'raf' | 'mutation' | number; + timeout?: number; + root?: ElementHandle; + signal?: AbortSignal; + } = {}, + ...args: Params + ): Promise>>> { + const { + polling = 'raf', + timeout = this.#timeoutSettings.timeout(), + root, + signal, + } = options; + if (typeof polling === 'number' && polling < 0) { + throw new Error('Cannot poll with non-positive interval'); + } + const waitTask = new WaitTask( + this, + { + polling, + root, + timeout, + signal, + }, + pageFunction as unknown as + | ((...args: unknown[]) => Promise>>) + | string, + ...args + ); + return waitTask.result; + } } diff --git a/packages/puppeteer-core/src/common/bidi/Serializer.ts b/packages/puppeteer-core/src/common/bidi/Serializer.ts index 28fbe5d4..23dc3a5c 100644 --- a/packages/puppeteer-core/src/common/bidi/Serializer.ts +++ b/packages/puppeteer-core/src/common/bidi/Serializer.ts @@ -156,7 +156,10 @@ export class BidiSerializer { ? arg : null; if (objectHandle) { - if (objectHandle.context() !== context) { + if ( + objectHandle.context() !== context && + !('sharedId' in objectHandle.remoteValue()) + ) { throw new Error( 'JSHandles can be evaluated only in the context they were created!' ); diff --git a/packages/puppeteer-core/src/common/bidi/utils.ts b/packages/puppeteer-core/src/common/bidi/utils.ts index d99dd755..9b563162 100644 --- a/packages/puppeteer-core/src/common/bidi/utils.ts +++ b/packages/puppeteer-core/src/common/bidi/utils.ts @@ -19,7 +19,7 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import {debug} from '../Debug.js'; import {PuppeteerURL} from '../util.js'; -import {BrowsingContext} from './BrowsingContext.js'; +import {Realm} from './Realm.js'; import {BidiSerializer} from './Serializer.js'; /** @@ -30,7 +30,7 @@ export const debugError = debug('puppeteer:error'); * @internal */ export async function releaseReference( - client: BrowsingContext, + client: Realm, remoteReference: Bidi.CommonDataTypes.RemoteReference ): Promise { if (!remoteReference.handle) { @@ -38,7 +38,7 @@ export async function releaseReference( } await client.connection .send('script.disown', { - target: {context: client.id}, + target: client.target, handles: [remoteReference.handle], }) .catch((error: any) => { diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 70ecedb3..a7ef1683 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -263,6 +263,24 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler waitForSelector (aria) should have an error message specifically for awaiting an element to be hidden", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler waitForSelector (aria) should have correct stack trace for timeout", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[ariaqueryhandler.spec] AriaQueryHandler waitForSelector (aria) should respect timeout", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[browser.spec] Browser specs Browser.process should return child_process instance", "platforms": ["darwin", "linux", "win32"], @@ -275,6 +293,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |pipe| option should fire \"disconnected\" when closing with pipe", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[Connection.spec] WebDriver BiDi Connection should work", "platforms": ["darwin", "linux", "win32"], @@ -311,35 +335,35 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, - { - "testIdPattern": "[elementhandle.spec] ElementHandle specs Custom queries should wait correctly with waitFor", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[elementhandle.spec] ElementHandle specs Custom queries should wait correctly with waitForSelector", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[elementhandle.spec] ElementHandle specs Custom queries should wait correctly with waitForSelector on an element", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs Element.toElement should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs Element.waitForSelector should wait correctly with waitForSelector on an element", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs Element.waitForXPath should wait correctly with waitForXPath on an element", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.isVisible and ElementHandle.isHidden should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] + }, + { + "testIdPattern": "[emulation.spec] *", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] }, { "testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts", @@ -773,6 +797,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[page.spec] Page Page.url should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work with aborted requests", "platforms": ["darwin", "linux", "win32"], @@ -807,7 +837,7 @@ "testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work for ARIA selectors in multiple isolated worlds", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["FAIL", "TIMEOUT"] }, { "testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work with :hover", @@ -875,6 +905,120 @@ "parameters": ["webDriverBiDi"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should be cancellable", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should have an error message specifically for awaiting an element to be hidden", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should have correct stack trace for timeout", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should immediately resolve promise if node exists", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should resolve promise when node is added", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should respect timeout", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should respond to node attribute mutation", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should return null if waiting to hide non-existing element", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should return the element handle", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (bounding box)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (display)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (removal)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be hidden (visibility)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible (bounding box)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible (display)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible (visibility)", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should wait for element to be visible recursively", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should work when node is added through innerHTML", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForSelector should work with removed MutationObserver", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[waittask.spec] waittask specs Frame.waitForTimeout waits for the given timeout before resolving", "platforms": ["darwin", "linux", "win32"], @@ -971,12 +1115,30 @@ "parameters": ["chrome", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should be able to connect using browserUrl, with and without trailing slash", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should throw when trying to connect to non-existing browser", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |browserURL| option should throw when trying to connect to non-existing browser", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[chromiumonly.spec] Chromium-Specific Launcher tests Puppeteer.launch |pipe| option should fire \"disconnected\" when closing with pipe", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["SKIP"] + }, { "testIdPattern": "[click.spec] Page.click should click on checkbox label and toggle", "platforms": ["darwin", "linux", "win32"], @@ -1175,18 +1337,42 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[emulation.spec] Emulation Page.emulate should support clicking", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"] + }, + { + "testIdPattern": "[emulation.spec] Emulation Page.emulate should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.emulateCPUThrottling should change the CPU throttling rate successfully", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[emulation.spec] Emulation Page.emulateMediaFeatures should throw in case of bad argument", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.emulateMediaFeatures should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[emulation.spec] Emulation Page.emulateMediaType should throw in case of bad argument", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.emulateMediaType should work", "platforms": ["darwin", "linux", "win32"], @@ -1199,6 +1385,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[emulation.spec] Emulation Page.emulateNetworkConditions should change navigator.connection.effectiveType", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.emulateTimezone should throw for invalid timezone IDs", "platforms": ["darwin", "linux", "win32"], @@ -1211,12 +1403,24 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[emulation.spec] Emulation Page.emulateVisionDeficiency should throw for invalid vision deficiencies", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.emulateVisionDeficiency should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[emulation.spec] Emulation Page.viewport should detect touch when applying viewport with touches", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.viewport should get the proper viewport size", "platforms": ["darwin", "linux", "win32"], @@ -1229,42 +1433,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[emulation.spec] *", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["PASS"] - }, { "testIdPattern": "[emulation.spec] Emulation Page.viewport should support touch emulation", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[emulation.spec] Emulation Page.viewport should detect touch when applying viewport with touches", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[emulation.spec] Emulation Page.emulate should work", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[emulation.spec] Emulation Page.emulate should support clicking", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[emulation.spec] Emulation Page.emulateNetworkConditions should change navigator.connection.effectiveType", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[evaluation.spec] Evaluation specs \"after each\" hook for \"should transfer 100Mb of data from page to node.js\"", "platforms": ["darwin"], @@ -1367,6 +1541,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with mixed content", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with request interception", "platforms": ["darwin", "linux", "win32"], @@ -1553,12 +1733,36 @@ "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[locator.spec] Locator Locator.click can be aborted", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[locator.spec] Locator Locator.click should retry clicks on errors", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[locator.spec] Locator Locator.click should time out", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[locator.spec] Locator Locator.race can be aborted", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[locator.spec] Locator Locator.scroll should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[mouse.spec] Mouse should reset properly", "platforms": ["darwin", "linux", "win32"], @@ -2177,6 +2381,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[page.spec] Page Page.addScriptTag should throw an error if loading from url fail", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[page.spec] Page Page.addScriptTag should throw when added with content to the CSP page", "platforms": ["darwin", "linux", "win32"], @@ -2189,6 +2399,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[page.spec] Page Page.addStyleTag should throw an error if loading from url fail", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[page.spec] Page Page.addStyleTag should throw when added with content to the CSP page", "platforms": ["darwin", "linux", "win32"], @@ -2459,12 +2675,6 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[page.spec] Page Page.url should work", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["PASS"] - }, { "testIdPattern": "[page.spec] Page Page.waitForNetworkIdle should work", "platforms": ["darwin", "linux", "win32"], @@ -2783,6 +2993,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[worker.spec] Workers should report errors", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events", "platforms": ["win32"], diff --git a/test/src/headful.spec.ts b/test/src/headful.spec.ts index 6be13b82..deb64570 100644 --- a/test/src/headful.spec.ts +++ b/test/src/headful.spec.ts @@ -39,7 +39,7 @@ describe('headful tests', function () { /* These tests fire up an actual browser so let's * allow a higher timeout */ - this.timeout(20 * 1000); + this.timeout(20_000); let headfulOptions: PuppeteerLaunchOptions | undefined; let headlessOptions: PuppeteerLaunchOptions & {headless: boolean}; diff --git a/test/src/launcher.spec.ts b/test/src/launcher.spec.ts index ae548b9d..2acb7284 100644 --- a/test/src/launcher.spec.ts +++ b/test/src/launcher.spec.ts @@ -35,7 +35,7 @@ import { import {dumpFrames, waitEvent} from './utils.js'; const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); -const FIREFOX_TIMEOUT = 30 * 1000; +const FIREFOX_TIMEOUT = 30_000; describe('Launcher specs', function () { setupTestBrowserHooks();