diff --git a/packages/puppeteer-core/src/api/Frame.ts b/packages/puppeteer-core/src/api/Frame.ts index 34b6dce5..ea7b1525 100644 --- a/packages/puppeteer-core/src/api/Frame.ts +++ b/packages/puppeteer-core/src/api/Frame.ts @@ -36,9 +36,11 @@ import { NodeFor, } from '../common/types.js'; import { + getPageContent, importFSPromises, withSourcePuppeteerURLIfNone, } from '../common/util.js'; +import {assert} from '../util/assert.js'; import {throwIfDisposed} from '../util/decorators.js'; import {KeyboardTypeOptions} from './Input.js'; @@ -320,6 +322,16 @@ export abstract class Frame extends EventEmitter { */ abstract isolatedRealm(): Realm; + /** + * @internal + */ + async document(): Promise> { + // TODO(#10813): Implement document caching. + return await this.evaluateHandle(() => { + return document; + }); + } + /** * @internal */ @@ -429,7 +441,8 @@ export abstract class Frame extends EventEmitter { async $( selector: Selector ): Promise> | null> { - return await this.mainRealm().$(selector); + using document = await this.document(); + return await document.$(selector); } /** @@ -443,7 +456,8 @@ export abstract class Frame extends EventEmitter { async $$( selector: Selector ): Promise>>> { - return await this.mainRealm().$$(selector); + using document = await this.document(); + return await document.$$(selector); } /** @@ -480,7 +494,8 @@ export abstract class Frame extends EventEmitter { ...args: Params ): Promise>> { pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction); - return await this.mainRealm().$eval(selector, pageFunction, ...args); + using document = await this.document(); + return await document.$eval(selector, pageFunction, ...args); } /** @@ -517,7 +532,8 @@ export abstract class Frame extends EventEmitter { ...args: Params ): Promise>> { pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction); - return await this.mainRealm().$$eval(selector, pageFunction, ...args); + using document = await this.document(); + return await document.$$eval(selector, pageFunction, ...args); } /** @@ -532,7 +548,8 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async $x(expression: string): Promise>> { - return await this.mainRealm().$x(expression); + using document = await this.document(); + return await document.$x(expression); } /** @@ -670,7 +687,7 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async content(): Promise { - return await this.isolatedRealm().content(); + return await this.evaluate(getPageContent); } /** @@ -918,7 +935,10 @@ export abstract class Frame extends EventEmitter { selector: string, options: Readonly = {} ): Promise { - return await this.isolatedRealm().click(selector, options); + using handle = await this.$(selector); + assert(handle, `No element found for selector: ${selector}`); + await handle.click(options); + await handle.dispose(); } /** @@ -929,7 +949,9 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async focus(selector: string): Promise { - return await this.isolatedRealm().focus(selector); + using handle = await this.$(selector); + assert(handle, `No element found for selector: ${selector}`); + await handle.focus(); } /** @@ -941,7 +963,9 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async hover(selector: string): Promise { - return await this.isolatedRealm().hover(selector); + using handle = await this.$(selector); + assert(handle, `No element found for selector: ${selector}`); + await handle.hover(); } /** @@ -964,7 +988,9 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async select(selector: string, ...values: string[]): Promise { - return await this.isolatedRealm().select(selector, ...values); + using handle = await this.$(selector); + assert(handle, `No element found for selector: ${selector}`); + return await handle.select(...values); } /** @@ -975,7 +1001,9 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async tap(selector: string): Promise { - return await this.isolatedRealm().tap(selector); + using handle = await this.$(selector); + assert(handle, `No element found for selector: ${selector}`); + await handle.tap(); } /** @@ -1005,7 +1033,9 @@ export abstract class Frame extends EventEmitter { text: string, options?: Readonly ): Promise { - return await this.isolatedRealm().type(selector, text, options); + using handle = await this.$(selector); + assert(handle, `No element found for selector: ${selector}`); + await handle.type(text, options); } /** @@ -1039,7 +1069,9 @@ export abstract class Frame extends EventEmitter { */ @throwIfDetached async title(): Promise { - return await this.isolatedRealm().title(); + return await this.isolatedRealm().evaluate(() => { + return document.title; + }); } /** diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index 0495be88..ae830939 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -982,12 +982,12 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable { >( pageFunction: Func | string, ...args: Params - ): Promise>>>; - async evaluateHandle< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >(): Promise>>> { - throw new Error('Not implemented'); + ): Promise>>> { + pageFunction = withSourcePuppeteerURLIfNone( + this.evaluateHandle.name, + pageFunction + ); + return await this.mainFrame().evaluateHandle(pageFunction, ...args); } /** @@ -1440,14 +1440,14 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable { * {@link Frame.url | page.mainFrame().url()}. */ url(): string { - throw new Error('Not implemented'); + return this.mainFrame().url(); } /** * The full HTML contents of the page, including the DOCTYPE. */ async content(): Promise { - throw new Error('Not implemented'); + return await this.mainFrame().content(); } /** @@ -1476,9 +1476,8 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable { * - `networkidle2` : consider setting content to be finished when there are * no more than 2 network connections for at least `500` ms. */ - async setContent(html: string, options?: WaitForOptions): Promise; - async setContent(): Promise { - throw new Error('Not implemented'); + async setContent(html: string, options?: WaitForOptions): Promise { + await this.mainFrame().setContent(html, options); } /** @@ -1541,9 +1540,8 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable { async goto( url: string, options?: WaitForOptions & {referer?: string; referrerPolicy?: string} - ): Promise; - async goto(): Promise { - throw new Error('Not implemented'); + ): Promise { + return await this.mainFrame().goto(url, options); } /** @@ -2235,12 +2233,12 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable { >( pageFunction: Func | string, ...args: Params - ): Promise>>; - async evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >(): Promise>> { - throw new Error('Not implemented'); + ): Promise>> { + pageFunction = withSourcePuppeteerURLIfNone( + this.evaluate.name, + pageFunction + ); + return await this.mainFrame().evaluate(pageFunction, ...args); } /** @@ -2473,7 +2471,7 @@ export class Page extends EventEmitter implements AsyncDisposable, Disposable { * Shortcut for {@link Frame.title | page.mainFrame().title()}. */ async title(): Promise { - throw new Error('Not implemented'); + return await this.mainFrame().title(); } async close(options?: {runBeforeUnload?: boolean}): Promise; diff --git a/packages/puppeteer-core/src/api/Realm.ts b/packages/puppeteer-core/src/api/Realm.ts index cabd6773..fb79443d 100644 --- a/packages/puppeteer-core/src/api/Realm.ts +++ b/packages/puppeteer-core/src/api/Realm.ts @@ -15,20 +15,11 @@ */ import {TimeoutSettings} from '../common/TimeoutSettings.js'; -import { - EvaluateFunc, - EvaluateFuncWith, - HandleFor, - InnerLazyParams, - NodeFor, -} from '../common/types.js'; -import {getPageContent, withSourcePuppeteerURLIfNone} from '../common/util.js'; +import {EvaluateFunc, HandleFor, InnerLazyParams} from '../common/types.js'; import {TaskManager, WaitTask} from '../common/WaitTask.js'; -import {assert} from '../util/assert.js'; -import {ClickOptions, ElementHandle} from './ElementHandle.js'; +import {ElementHandle} from './ElementHandle.js'; import {Environment} from './Environment.js'; -import {KeyboardTypeOptions} from './Input.js'; import {JSHandle} from './JSHandle.js'; /** @@ -61,76 +52,6 @@ export abstract class Realm implements Disposable { ...args: Params ): Promise>>; - async document(): Promise> { - // TODO(#10813): Implement document caching. - return await this.evaluateHandle(() => { - return document; - }); - } - - async $( - selector: Selector - ): Promise> | null> { - using document = await this.document(); - return await document.$(selector); - } - - async $$( - selector: Selector - ): Promise>>> { - using document = await this.document(); - return await document.$$(selector); - } - - async $x(expression: string): Promise>> { - using document = await this.document(); - return await document.$x(expression); - } - - async $eval< - Selector extends string, - Params extends unknown[], - Func extends EvaluateFuncWith, Params> = EvaluateFuncWith< - NodeFor, - Params - >, - >( - selector: Selector, - pageFunction: Func | string, - ...args: Params - ): Promise>> { - pageFunction = withSourcePuppeteerURLIfNone(this.$eval.name, pageFunction); - using document = await this.document(); - return await document.$eval(selector, pageFunction, ...args); - } - - async $$eval< - Selector extends string, - Params extends unknown[], - Func extends EvaluateFuncWith< - Array>, - Params - > = EvaluateFuncWith>, Params>, - >( - selector: Selector, - pageFunction: Func | string, - ...args: Params - ): Promise>> { - pageFunction = withSourcePuppeteerURLIfNone(this.$$eval.name, pageFunction); - using document = await this.document(); - return await document.$$eval(selector, pageFunction, ...args); - } - - async content(): Promise { - return await this.evaluate(getPageContent); - } - - async title(): Promise { - return await this.evaluate(() => { - return document.title; - }); - } - async waitForFunction< Params extends unknown[], Func extends EvaluateFunc> = EvaluateFunc< @@ -171,50 +92,6 @@ export abstract class Realm implements Disposable { return await waitTask.result; } - async click( - selector: string, - options?: Readonly - ): Promise { - using handle = await this.$(selector); - assert(handle, `No element found for selector: ${selector}`); - await handle.click(options); - await handle.dispose(); - } - - async focus(selector: string): Promise { - using handle = await this.$(selector); - assert(handle, `No element found for selector: ${selector}`); - await handle.focus(); - } - - async hover(selector: string): Promise { - using handle = await this.$(selector); - assert(handle, `No element found for selector: ${selector}`); - await handle.hover(); - } - - async select(selector: string, ...values: string[]): Promise { - using handle = await this.$(selector); - assert(handle, `No element found for selector: ${selector}`); - return await handle.select(...values); - } - - async tap(selector: string): Promise { - using handle = await this.$(selector); - assert(handle, `No element found for selector: ${selector}`); - await handle.tap(); - } - - async type( - selector: string, - text: string, - options?: Readonly - ): Promise { - using handle = await this.$(selector); - assert(handle, `No element found for selector: ${selector}`); - await handle.type(text, options); - } - get disposed(): boolean { return this.#disposed; } diff --git a/packages/puppeteer-core/src/common/Frame.ts b/packages/puppeteer-core/src/common/Frame.ts index f8a24e71..eaddf802 100644 --- a/packages/puppeteer-core/src/common/Frame.ts +++ b/packages/puppeteer-core/src/common/Frame.ts @@ -32,6 +32,7 @@ import {FrameManager} from './FrameManager.js'; import {IsolatedWorld} from './IsolatedWorld.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; +import {setPageContent} from './util.js'; /** * We use symbols to prevent external parties listening to these events. @@ -257,7 +258,27 @@ export class CDPFrame extends Frame { waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; } = {} ): Promise { - return await this.isolatedRealm().setContent(html, options); + const { + waitUntil = ['load'], + timeout = this._frameManager.timeoutSettings.navigationTimeout(), + } = options; + + await setPageContent(this.isolatedRealm(), html); + + const watcher = new LifecycleWatcher( + this._frameManager.networkManager, + this, + waitUntil, + timeout + ); + const error = await Deferred.race([ + watcher.terminationPromise(), + watcher.lifecyclePromise(), + ]); + watcher.dispose(); + if (error) { + throw error; + } } override url(): string { diff --git a/packages/puppeteer-core/src/common/IsolatedWorld.ts b/packages/puppeteer-core/src/common/IsolatedWorld.ts index 52b66b3e..b5950fe1 100644 --- a/packages/puppeteer-core/src/common/IsolatedWorld.ts +++ b/packages/puppeteer-core/src/common/IsolatedWorld.ts @@ -18,7 +18,6 @@ import {Protocol} from 'devtools-protocol'; import {JSHandle} from '../api/JSHandle.js'; import {Realm} from '../api/Realm.js'; -import {assert} from '../util/assert.js'; import {Deferred} from '../util/Deferred.js'; import {Binding} from './Binding.js'; @@ -26,7 +25,6 @@ import {CDPSession} from './Connection.js'; import {ExecutionContext} from './ExecutionContext.js'; import {CDPFrame} from './Frame.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; -import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {TimeoutSettings} from './TimeoutSettings.js'; import {BindingPayload, EvaluateFunc, HandleFor} from './types.js'; import { @@ -34,7 +32,6 @@ import { createCdpHandle, debugError, Mutex, - setPageContent, withSourcePuppeteerURLIfNone, } from './util.js'; import {WebWorker} from './WebWorker.js'; @@ -127,11 +124,6 @@ export class IsolatedWorld extends Realm { return this.#frameOrWorker.client; } - get #frame(): CDPFrame { - assert(this.#frameOrWorker instanceof CDPFrame); - return this.#frameOrWorker; - } - clearContext(): void { this.#context = Deferred.create(); } @@ -188,36 +180,6 @@ export class IsolatedWorld extends Realm { return await context.evaluate(pageFunction, ...args); } - async setContent( - html: string, - options: { - timeout?: number; - waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; - } = {} - ): Promise { - const { - waitUntil = ['load'], - timeout = this.timeoutSettings.navigationTimeout(), - } = options; - - await setPageContent(this, html); - - const watcher = new LifecycleWatcher( - this.#frame._frameManager.networkManager, - this.#frame, - waitUntil, - timeout - ); - const error = await Deferred.race([ - watcher.terminationPromise(), - watcher.lifecyclePromise(), - ]); - watcher.dispose(); - if (error) { - throw error; - } - } - // If multiple waitFor are set up asynchronously, we need to wait for the // first one to set up the binding in the page before running the others. #mutex = new Mutex(); diff --git a/packages/puppeteer-core/src/common/Page.ts b/packages/puppeteer-core/src/common/Page.ts index 2fbb07da..223e7afd 100644 --- a/packages/puppeteer-core/src/common/Page.ts +++ b/packages/puppeteer-core/src/common/Page.ts @@ -73,7 +73,7 @@ import {TargetManagerEmittedEvents} from './TargetManager.js'; import {TaskQueue} from './TaskQueue.js'; import {TimeoutSettings} from './TimeoutSettings.js'; import {Tracing} from './Tracing.js'; -import {BindingPayload, EvaluateFunc, HandleFor} from './types.js'; +import {BindingPayload, HandleFor} from './types.js'; import { createCdpHandle, createClientError, @@ -88,7 +88,6 @@ import { valueFromRemoteObject, waitForEvent, waitWithTimeout, - withSourcePuppeteerURLIfNone, } from './util.js'; import {WebWorker} from './WebWorker.js'; @@ -525,20 +524,6 @@ export class CDPPage extends Page { return this.#timeoutSettings.timeout(); } - override async evaluateHandle< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>>> { - pageFunction = withSourcePuppeteerURLIfNone( - this.evaluateHandle.name, - pageFunction - ); - return await this.mainFrame().evaluateHandle(pageFunction, ...args); - } - override async queryObjects( prototypeHandle: JSHandle ): Promise> { @@ -863,28 +848,6 @@ export class CDPPage extends Page { this.emit(PageEmittedEvents.Dialog, dialog); } - override url(): string { - return this.mainFrame().url(); - } - - override async content(): Promise { - return await this.mainFrame().content(); - } - - override async setContent( - html: string, - options: WaitForOptions = {} - ): Promise { - await this.mainFrame().setContent(html, options); - } - - override async goto( - url: string, - options: WaitForOptions & {referer?: string; referrerPolicy?: string} = {} - ): Promise { - return await this.mainFrame().goto(url, options); - } - override async reload( options?: WaitForOptions ): Promise { @@ -1042,20 +1005,6 @@ export class CDPPage extends Page { return this.#viewport; } - override async evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>> { - pageFunction = withSourcePuppeteerURLIfNone( - this.evaluate.name, - pageFunction - ); - return await this.mainFrame().evaluate(pageFunction, ...args); - } - override async evaluateOnNewDocument< Params extends unknown[], Func extends (...args: Params) => unknown = (...args: Params) => unknown, @@ -1331,10 +1280,6 @@ export class CDPPage extends Page { return buffer; } - override async title(): Promise { - return await this.mainFrame().title(); - } - override async close( options: {runBeforeUnload?: boolean} = {runBeforeUnload: undefined} ): Promise { diff --git a/packages/puppeteer-core/src/common/bidi/Page.ts b/packages/puppeteer-core/src/common/bidi/Page.ts index 3be64693..5a7c8dca 100644 --- a/packages/puppeteer-core/src/common/bidi/Page.ts +++ b/packages/puppeteer-core/src/common/bidi/Page.ts @@ -43,7 +43,6 @@ import {PDFOptions} from '../PDFOptions.js'; import {Viewport} from '../PuppeteerViewport.js'; import {TimeoutSettings} from '../TimeoutSettings.js'; import {Tracing} from '../Tracing.js'; -import {EvaluateFunc, HandleFor} from '../types.js'; import { debugError, evaluationString, @@ -51,7 +50,6 @@ import { validateDialogType, waitForEvent, waitWithTimeout, - withSourcePuppeteerURLIfNone, } from '../util.js'; import {BidiBrowser} from './Browser.js'; @@ -426,44 +424,6 @@ export class BidiPage extends Page { this.removeAllListeners(); } - override async evaluateHandle< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>>> { - pageFunction = withSourcePuppeteerURLIfNone( - this.evaluateHandle.name, - pageFunction - ); - return await this.mainFrame().evaluateHandle(pageFunction, ...args); - } - - override async evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>> { - pageFunction = withSourcePuppeteerURLIfNone( - this.evaluate.name, - pageFunction - ); - return await this.mainFrame().evaluate(pageFunction, ...args); - } - - override async goto( - url: string, - options?: WaitForOptions & { - referer?: string | undefined; - referrerPolicy?: string | undefined; - } - ): Promise { - return await this.mainFrame().goto(url, options); - } - override async reload( options?: WaitForOptions ): Promise { @@ -486,10 +446,6 @@ export class BidiPage extends Page { return response; } - override url(): string { - return this.mainFrame().url(); - } - override setDefaultNavigationTimeout(timeout: number): void { this.#timeoutSettings.setDefaultNavigationTimeout(timeout); } @@ -502,17 +458,6 @@ export class BidiPage extends Page { return this.#timeoutSettings.timeout(); } - override async setContent( - html: string, - options: WaitForOptions = {} - ): Promise { - await this.mainFrame().setContent(html, options); - } - - override async content(): Promise { - return await this.mainFrame().content(); - } - override isJavaScriptEnabled(): boolean { return this.#cdpEmulationManager.javascriptEnabled; } @@ -721,10 +666,6 @@ export class BidiPage extends Page { ); } - override title(): Promise { - return this.mainFrame().title(); - } - override async createCDPSession(): Promise { const {sessionId} = await this.mainFrame() .context()