diff --git a/docs/api/puppeteer.webworker.evaluate.md b/docs/api/puppeteer.webworker.evaluate.md index 61884c3c767..a202c51fa76 100644 --- a/docs/api/puppeteer.webworker.evaluate.md +++ b/docs/api/puppeteer.webworker.evaluate.md @@ -4,7 +4,7 @@ sidebar_label: WebWorker.evaluate # WebWorker.evaluate() method -If the function passed to the `worker.evaluate` returns a Promise, then `worker.evaluate` would wait for the promise to resolve and return its value. If the function passed to the `worker.evaluate` returns a non-serializable value, then `worker.evaluate` resolves to `undefined`. DevTools Protocol also supports transferring some additional values that are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`, and bigint literals. Shortcut for `await worker.executionContext()).evaluate(pageFunction, ...args)`. +Evaluates a given function in the [worker](./puppeteer.webworker.md). #### Signature: @@ -13,22 +13,27 @@ class WebWorker { evaluate< Params extends unknown[], Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>>; + >(func: Func | string, ...args: Params): Promise>>; } ``` ## Parameters -| Parameter | Type | Description | -| ------------ | -------------- | ----------------------------------------------- | -| pageFunction | Func \| string | Function to be evaluated in the worker context. | -| args | Params | Arguments to pass to pageFunction. | +| Parameter | Type | Description | +| --------- | -------------- | ----------------------------------------- | +| func | Func \| string | Function to be evaluated. | +| args | Params | Arguments to pass into func. | **Returns:** Promise<Awaited<ReturnType<Func>>> -Promise which resolves to the return value of `pageFunction`. +The result of `func`. + +## Remarks + +If the given function returns a promise, [evaluate](./puppeteer.webworker.evaluate.md) will wait for the promise to resolve. + +As a rule of thumb, if the return value of the given function is more complicated than a JSON object (e.g. most classes), then [evaluate](./puppeteer.webworker.evaluate.md) will \_likely\_ return some truncated value (or `{}`). This is because we are not returning the actual return value, but a deserialized version as a result of transferring the return value through a protocol to Puppeteer. + +In general, you should use [evaluateHandle](./puppeteer.webworker.evaluatehandle.md) if [evaluate](./puppeteer.webworker.evaluate.md) cannot serialize the return value properly or you need a mutable [handle](./puppeteer.jshandle.md) to the return object. diff --git a/docs/api/puppeteer.webworker.evaluatehandle.md b/docs/api/puppeteer.webworker.evaluatehandle.md index d84a05d0833..cafab45bfee 100644 --- a/docs/api/puppeteer.webworker.evaluatehandle.md +++ b/docs/api/puppeteer.webworker.evaluatehandle.md @@ -4,7 +4,7 @@ sidebar_label: WebWorker.evaluateHandle # WebWorker.evaluateHandle() method -The only difference between `worker.evaluate` and `worker.evaluateHandle` is that `worker.evaluateHandle` returns in-page object (JSHandle). If the function passed to the `worker.evaluateHandle` returns a `Promise`, then `worker.evaluateHandle` would wait for the promise to resolve and return its value. Shortcut for `await worker.executionContext()).evaluateHandle(pageFunction, ...args)` +Evaluates a given function in the [worker](./puppeteer.webworker.md). #### Signature: @@ -14,7 +14,7 @@ class WebWorker { Params extends unknown[], Func extends EvaluateFunc = EvaluateFunc, >( - pageFunction: Func | string, + func: Func | string, ...args: Params ): Promise>>>; } @@ -22,13 +22,19 @@ class WebWorker { ## Parameters -| Parameter | Type | Description | -| ------------ | -------------- | ----------------------------------------------- | -| pageFunction | Func \| string | Function to be evaluated in the page context. | -| args | Params | Arguments to pass to pageFunction. | +| Parameter | Type | Description | +| --------- | -------------- | ----------------------------------------- | +| func | Func \| string | Function to be evaluated. | +| args | Params | Arguments to pass into func. | **Returns:** Promise<[HandleFor](./puppeteer.handlefor.md)<Awaited<ReturnType<Func>>>> -Promise which resolves to the return value of `pageFunction`. +A [handle](./puppeteer.jshandle.md) to the return value of `func`. + +## Remarks + +If the given function returns a promise, [evaluate](./puppeteer.webworker.evaluate.md) will wait for the promise to resolve. + +In general, you should use [evaluateHandle](./puppeteer.webworker.evaluatehandle.md) if [evaluate](./puppeteer.webworker.evaluate.md) cannot serialize the return value properly or you need a mutable [handle](./puppeteer.jshandle.md) to the return object. diff --git a/docs/api/puppeteer.webworker.md b/docs/api/puppeteer.webworker.md index 8991a22304a..cc96c91b447 100644 --- a/docs/api/puppeteer.webworker.md +++ b/docs/api/puppeteer.webworker.md @@ -9,7 +9,7 @@ This class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web #### Signature: ```typescript -export declare class WebWorker extends EventEmitter> +export declare abstract class WebWorker extends EventEmitter> ``` **Extends:** [EventEmitter](./puppeteer.eventemitter.md)<Record<[EventType](./puppeteer.eventtype.md), unknown>> @@ -44,8 +44,8 @@ for (const worker of page.workers()) { ## Methods -| Method | Modifiers | Description | -| ----------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [evaluate(pageFunction, args)](./puppeteer.webworker.evaluate.md) | | If the function passed to the worker.evaluate returns a Promise, then worker.evaluate would wait for the promise to resolve and return its value. If the function passed to the worker.evaluate returns a non-serializable value, then worker.evaluate resolves to undefined. DevTools Protocol also supports transferring some additional values that are not serializable by JSON: -0, NaN, Infinity, -Infinity, and bigint literals. Shortcut for await worker.executionContext()).evaluate(pageFunction, ...args). | -| [evaluateHandle(pageFunction, args)](./puppeteer.webworker.evaluatehandle.md) | | The only difference between worker.evaluate and worker.evaluateHandle is that worker.evaluateHandle returns in-page object (JSHandle). If the function passed to the worker.evaluateHandle returns a Promise, then worker.evaluateHandle would wait for the promise to resolve and return its value. Shortcut for await worker.executionContext()).evaluateHandle(pageFunction, ...args) | -| [url()](./puppeteer.webworker.url.md) | | The URL of this web worker. | +| Method | Modifiers | Description | +| --------------------------------------------------------------------- | --------- | --------------------------------------------------------------------- | +| [evaluate(func, args)](./puppeteer.webworker.evaluate.md) | | Evaluates a given function in the [worker](./puppeteer.webworker.md). | +| [evaluateHandle(func, args)](./puppeteer.webworker.evaluatehandle.md) | | Evaluates a given function in the [worker](./puppeteer.webworker.md). | +| [url()](./puppeteer.webworker.url.md) | | The URL of this web worker. | diff --git a/packages/puppeteer-core/src/api/Page.ts b/packages/puppeteer-core/src/api/Page.ts index 9d240248fd3..d42d7f0a428 100644 --- a/packages/puppeteer-core/src/api/Page.ts +++ b/packages/puppeteer-core/src/api/Page.ts @@ -46,7 +46,6 @@ import type { NetworkConditions, } from '../cdp/NetworkManager.js'; import type {Tracing} from '../cdp/Tracing.js'; -import type {WebWorker} from '../cdp/WebWorker.js'; import type {ConsoleMessage} from '../common/ConsoleMessage.js'; import type {Device} from '../common/Device.js'; import {TargetCloseError} from '../common/Errors.js'; @@ -122,6 +121,7 @@ import { type AwaitedLocator, } from './locators/locators.js'; import type {Target} from './Target.js'; +import type {WebWorker} from './WebWorker.js'; /** * @public diff --git a/packages/puppeteer-core/src/api/Target.ts b/packages/puppeteer-core/src/api/Target.ts index 3be4a0a9d1e..8ed461fa2fd 100644 --- a/packages/puppeteer-core/src/api/Target.ts +++ b/packages/puppeteer-core/src/api/Target.ts @@ -14,12 +14,11 @@ * limitations under the License. */ -import type {Browser} from '../api/Browser.js'; -import type {BrowserContext} from '../api/BrowserContext.js'; -import type {Page} from '../api/Page.js'; -import type {WebWorker} from '../cdp/WebWorker.js'; - +import type {Browser} from './Browser.js'; +import type {BrowserContext} from './BrowserContext.js'; import type {CDPSession} from './CDPSession.js'; +import type {Page} from './Page.js'; +import type {WebWorker} from './WebWorker.js'; /** * @public diff --git a/packages/puppeteer-core/src/api/WebWorker.ts b/packages/puppeteer-core/src/api/WebWorker.ts new file mode 100644 index 00000000000..ba9d8714569 --- /dev/null +++ b/packages/puppeteer-core/src/api/WebWorker.ts @@ -0,0 +1,144 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {EventEmitter, type EventType} from '../common/EventEmitter.js'; +import {TimeoutSettings} from '../common/TimeoutSettings.js'; +import type {EvaluateFunc, HandleFor} from '../common/types.js'; +import {withSourcePuppeteerURLIfNone} from '../common/util.js'; + +import type {CDPSession} from './CDPSession.js'; +import type {Realm} from './Realm.js'; + +/** + * This class represents a + * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker}. + * + * @remarks + * The events `workercreated` and `workerdestroyed` are emitted on the page + * object to signal the worker lifecycle. + * + * @example + * + * ```ts + * page.on('workercreated', worker => + * console.log('Worker created: ' + worker.url()) + * ); + * page.on('workerdestroyed', worker => + * console.log('Worker destroyed: ' + worker.url()) + * ); + * + * console.log('Current workers:'); + * for (const worker of page.workers()) { + * console.log(' ' + worker.url()); + * } + * ``` + * + * @public + */ +export abstract class WebWorker extends EventEmitter< + Record +> { + /** + * @internal + */ + readonly timeoutSettings = new TimeoutSettings(); + + readonly #url: string; + + /** + * @internal + */ + constructor(url: string) { + super(); + + this.#url = url; + } + + /** + * @internal + */ + abstract mainRealm(): Realm; + + /** + * The URL of this web worker. + */ + url(): string { + return this.#url; + } + + /** + * The CDP session client the WebWorker belongs to. + */ + abstract get client(): CDPSession; + + /** + * Evaluates a given function in the {@link WebWorker | worker}. + * + * @remarks If the given function returns a promise, + * {@link WebWorker.evaluate | evaluate} will wait for the promise to resolve. + * + * As a rule of thumb, if the return value of the given function is more + * complicated than a JSON object (e.g. most classes), then + * {@link WebWorker.evaluate | evaluate} will _likely_ return some truncated + * value (or `{}`). This is because we are not returning the actual return + * value, but a deserialized version as a result of transferring the return + * value through a protocol to Puppeteer. + * + * In general, you should use + * {@link WebWorker.evaluateHandle | evaluateHandle} if + * {@link WebWorker.evaluate | evaluate} cannot serialize the return value + * properly or you need a mutable {@link JSHandle | handle} to the return + * object. + * + * @param func - Function to be evaluated. + * @param args - Arguments to pass into `func`. + * @returns The result of `func`. + */ + async evaluate< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc, + >(func: Func | string, ...args: Params): Promise>> { + func = withSourcePuppeteerURLIfNone(this.evaluate.name, func); + return await this.mainRealm().evaluate(func, ...args); + } + + /** + * Evaluates a given function in the {@link WebWorker | worker}. + * + * @remarks If the given function returns a promise, + * {@link WebWorker.evaluate | evaluate} will wait for the promise to resolve. + * + * In general, you should use + * {@link WebWorker.evaluateHandle | evaluateHandle} if + * {@link WebWorker.evaluate | evaluate} cannot serialize the return value + * properly or you need a mutable {@link JSHandle | handle} to the return + * object. + * + * @param func - Function to be evaluated. + * @param args - Arguments to pass into `func`. + * @returns A {@link JSHandle | handle} to the return value of `func`. + */ + async evaluateHandle< + Params extends unknown[], + Func extends EvaluateFunc = EvaluateFunc, + >( + func: Func | string, + ...args: Params + ): Promise>>> { + func = withSourcePuppeteerURLIfNone(this.evaluateHandle.name, func); + return await this.mainRealm().evaluateHandle(func, ...args); + } +} diff --git a/packages/puppeteer-core/src/api/api.ts b/packages/puppeteer-core/src/api/api.ts index 672a0d4ed98..7f82c1f1c1e 100644 --- a/packages/puppeteer-core/src/api/api.ts +++ b/packages/puppeteer-core/src/api/api.ts @@ -16,6 +16,7 @@ export * from './Browser.js'; export * from './BrowserContext.js'; +export * from './CDPSession.js'; export * from './Dialog.js'; export * from './ElementHandle.js'; export * from './Environment.js'; @@ -24,8 +25,8 @@ export * from './HTTPRequest.js'; export * from './HTTPResponse.js'; export * from './Input.js'; export * from './JSHandle.js'; -export * from './locators/locators.js'; export * from './Page.js'; export * from './Realm.js'; export * from './Target.js'; -export * from './CDPSession.js'; +export * from './WebWorker.js'; +export * from './locators/locators.js'; diff --git a/packages/puppeteer-core/src/cdp/IsolatedWorld.ts b/packages/puppeteer-core/src/cdp/IsolatedWorld.ts index bb128a5c1bc..671189fc5ab 100644 --- a/packages/puppeteer-core/src/cdp/IsolatedWorld.ts +++ b/packages/puppeteer-core/src/cdp/IsolatedWorld.ts @@ -34,7 +34,7 @@ import type {Binding} from './Binding.js'; import {ExecutionContext, createCdpHandle} from './ExecutionContext.js'; import type {CdpFrame} from './Frame.js'; import type {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; -import type {WebWorker} from './WebWorker.js'; +import type {CdpWebWorker} from './WebWorker.js'; /** * @internal @@ -69,10 +69,10 @@ export class IsolatedWorld extends Realm { return this.#bindings; } - readonly #frameOrWorker: CdpFrame | WebWorker; + readonly #frameOrWorker: CdpFrame | CdpWebWorker; constructor( - frameOrWorker: CdpFrame | WebWorker, + frameOrWorker: CdpFrame | CdpWebWorker, timeoutSettings: TimeoutSettings ) { super(timeoutSettings); @@ -80,7 +80,7 @@ export class IsolatedWorld extends Realm { this.frameUpdated(); } - get environment(): CdpFrame | WebWorker { + get environment(): CdpFrame | CdpWebWorker { return this.#frameOrWorker; } diff --git a/packages/puppeteer-core/src/cdp/Page.ts b/packages/puppeteer-core/src/cdp/Page.ts index 20e57ca89cd..b37b3df1727 100644 --- a/packages/puppeteer-core/src/cdp/Page.ts +++ b/packages/puppeteer-core/src/cdp/Page.ts @@ -87,7 +87,7 @@ import type {CdpTarget} from './Target.js'; import type {TargetManager} from './TargetManager.js'; import {TargetManagerEvent} from './TargetManager.js'; import {Tracing} from './Tracing.js'; -import {WebWorker} from './WebWorker.js'; +import {CdpWebWorker} from './WebWorker.js'; /** * @internal @@ -133,7 +133,7 @@ export class CdpPage extends Page { #exposedFunctions = new Map(); #coverage: Coverage; #viewport: Viewport | null; - #workers = new Map(); + #workers = new Map(); #fileChooserDeferreds = new Set>(); #sessionCloseDeferred = Deferred.create(); #serviceWorkerBypassed = false; @@ -352,7 +352,7 @@ export class CdpPage extends Page { assert(session instanceof CdpCDPSession); this.#frameManager.onAttachedToTarget(session._target()); if (session._target()._getTargetInfo().type === 'worker') { - const worker = new WebWorker( + const worker = new CdpWebWorker( session, session._target().url(), this.#addConsoleMessage.bind(this), @@ -512,7 +512,7 @@ export class CdpPage extends Page { return this.#frameManager.frames(); } - override workers(): WebWorker[] { + override workers(): CdpWebWorker[] { return Array.from(this.#workers.values()); } diff --git a/packages/puppeteer-core/src/cdp/Target.ts b/packages/puppeteer-core/src/cdp/Target.ts index 936137a4b81..11347dde15b 100644 --- a/packages/puppeteer-core/src/cdp/Target.ts +++ b/packages/puppeteer-core/src/cdp/Target.ts @@ -28,7 +28,7 @@ import {Deferred} from '../util/Deferred.js'; import {CdpCDPSession} from './CDPSession.js'; import {CdpPage} from './Page.js'; import type {TargetManager} from './TargetManager.js'; -import {WebWorker} from './WebWorker.js'; +import {CdpWebWorker} from './WebWorker.js'; /** * @internal @@ -276,9 +276,9 @@ export class DevToolsTarget extends PageTarget {} * @internal */ export class WorkerTarget extends CdpTarget { - #workerPromise?: Promise; + #workerPromise?: Promise; - override async worker(): Promise { + override async worker(): Promise { if (!this.#workerPromise) { const session = this._session(); // TODO(einbinder): Make workers send their console logs. @@ -287,7 +287,7 @@ export class WorkerTarget extends CdpTarget { ? Promise.resolve(session) : this._sessionFactory()(/* isAutoAttachEmulated=*/ false) ).then(client => { - return new WebWorker( + return new CdpWebWorker( client, this._getTargetInfo().url, () => {} /* consoleAPICalled */, diff --git a/packages/puppeteer-core/src/cdp/WebWorker.ts b/packages/puppeteer-core/src/cdp/WebWorker.ts index bf51e2b744a..c4a25eebb05 100644 --- a/packages/puppeteer-core/src/cdp/WebWorker.ts +++ b/packages/puppeteer-core/src/cdp/WebWorker.ts @@ -17,11 +17,10 @@ import type {Protocol} from 'devtools-protocol'; import type {CDPSession} from '../api/CDPSession.js'; import type {Realm} from '../api/Realm.js'; +import {WebWorker} from '../api/WebWorker.js'; import type {ConsoleMessageType} from '../common/ConsoleMessage.js'; -import {EventEmitter, type EventType} from '../common/EventEmitter.js'; import {TimeoutSettings} from '../common/TimeoutSettings.js'; -import type {EvaluateFunc, HandleFor} from '../common/types.js'; -import {debugError, withSourcePuppeteerURLIfNone} from '../common/util.js'; +import {debugError} from '../common/util.js'; import {ExecutionContext} from './ExecutionContext.js'; import {IsolatedWorld} from './IsolatedWorld.js'; @@ -44,53 +43,20 @@ export type ExceptionThrownCallback = ( ) => void; /** - * This class represents a - * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | WebWorker}. - * - * @remarks - * The events `workercreated` and `workerdestroyed` are emitted on the page - * object to signal the worker lifecycle. - * - * @example - * - * ```ts - * page.on('workercreated', worker => - * console.log('Worker created: ' + worker.url()) - * ); - * page.on('workerdestroyed', worker => - * console.log('Worker destroyed: ' + worker.url()) - * ); - * - * console.log('Current workers:'); - * for (const worker of page.workers()) { - * console.log(' ' + worker.url()); - * } - * ``` - * - * @public + * @internal */ -export class WebWorker extends EventEmitter> { - /** - * @internal - */ - readonly timeoutSettings = new TimeoutSettings(); - +export class CdpWebWorker extends WebWorker { #world: IsolatedWorld; #client: CDPSession; - #url: string; - /** - * @internal - */ constructor( client: CDPSession, url: string, consoleAPICalled: ConsoleAPICalledCallback, exceptionThrown: ExceptionThrownCallback ) { - super(); + super(url); this.#client = client; - this.#url = url; this.#world = new IsolatedWorld(this, new TimeoutSettings()); this.#client.once('Runtime.executionContextCreated', async event => { @@ -117,78 +83,11 @@ export class WebWorker extends EventEmitter> { this.#client.send('Runtime.enable').catch(debugError); } - /** - * @internal - */ mainRealm(): Realm { return this.#world; } - /** - * The URL of this web worker. - */ - url(): string { - return this.#url; - } - - /** - * The CDP session client the WebWorker belongs to. - */ get client(): CDPSession { return this.#client; } - - /** - * If the function passed to the `worker.evaluate` returns a Promise, then - * `worker.evaluate` would wait for the promise to resolve and return its - * value. If the function passed to the `worker.evaluate` returns a - * non-serializable value, then `worker.evaluate` resolves to `undefined`. - * DevTools Protocol also supports transferring some additional values that - * are not serializable by `JSON`: `-0`, `NaN`, `Infinity`, `-Infinity`, and - * bigint literals. - * Shortcut for `await worker.executionContext()).evaluate(pageFunction, ...args)`. - * - * @param pageFunction - Function to be evaluated in the worker context. - * @param args - Arguments to pass to `pageFunction`. - * @returns Promise which resolves to the return value of `pageFunction`. - */ - async evaluate< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>> { - pageFunction = withSourcePuppeteerURLIfNone( - this.evaluate.name, - pageFunction - ); - return await this.mainRealm().evaluate(pageFunction, ...args); - } - - /** - * The only difference between `worker.evaluate` and `worker.evaluateHandle` - * is that `worker.evaluateHandle` returns in-page object (JSHandle). If the - * function passed to the `worker.evaluateHandle` returns a `Promise`, then - * `worker.evaluateHandle` would wait for the promise to resolve and return - * its value. Shortcut for - * `await worker.executionContext()).evaluateHandle(pageFunction, ...args)` - * - * @param pageFunction - Function to be evaluated in the page context. - * @param args - Arguments to pass to `pageFunction`. - * @returns Promise which resolves to the return value of `pageFunction`. - */ - async evaluateHandle< - Params extends unknown[], - Func extends EvaluateFunc = EvaluateFunc, - >( - pageFunction: Func | string, - ...args: Params - ): Promise>>> { - pageFunction = withSourcePuppeteerURLIfNone( - this.evaluateHandle.name, - pageFunction - ); - return await this.mainRealm().evaluateHandle(pageFunction, ...args); - } } diff --git a/test/src/worker.spec.ts b/test/src/worker.spec.ts index d8602a101e1..24208bbc605 100644 --- a/test/src/worker.spec.ts +++ b/test/src/worker.spec.ts @@ -15,7 +15,7 @@ */ import expect from 'expect'; -import type {WebWorker} from 'puppeteer-core/internal/cdp/WebWorker.js'; +import type {WebWorker} from 'puppeteer-core/internal/api/WebWorker.js'; import type {ConsoleMessage} from 'puppeteer-core/internal/common/ConsoleMessage.js'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js';