chore: abstract web worker API (#11558)

This commit is contained in:
jrandolf 2023-12-15 13:08:28 +01:00 committed by GitHub
parent c3000160e6
commit af6eba4bea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 204 additions and 150 deletions

View File

@ -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<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>>;
>(func: Func | string, ...args: Params): Promise<Awaited<ReturnType<Func>>>;
}
```
## Parameters
| Parameter | Type | Description |
| ------------ | -------------- | ----------------------------------------------- |
| pageFunction | Func \| string | Function to be evaluated in the worker context. |
| args | Params | Arguments to pass to <code>pageFunction</code>. |
| --------- | -------------- | ----------------------------------------- |
| func | Func \| string | Function to be evaluated. |
| args | Params | Arguments to pass into <code>func</code>. |
**Returns:**
Promise&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;
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.

View File

@ -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<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
func: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
}
@ -23,12 +23,18 @@ class WebWorker {
## Parameters
| Parameter | Type | Description |
| ------------ | -------------- | ----------------------------------------------- |
| pageFunction | Func \| string | Function to be evaluated in the page context. |
| args | Params | Arguments to pass to <code>pageFunction</code>. |
| --------- | -------------- | ----------------------------------------- |
| func | Func \| string | Function to be evaluated. |
| args | Params | Arguments to pass into <code>func</code>. |
**Returns:**
Promise&lt;[HandleFor](./puppeteer.handlefor.md)&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;&gt;
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.

View File

@ -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<Record<EventType, unknown>>
export declare abstract class WebWorker extends EventEmitter<Record<EventType, unknown>>
```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;Record&lt;[EventType](./puppeteer.eventtype.md), unknown&gt;&gt;
@ -45,7 +45,7 @@ for (const worker of page.workers()) {
## Methods
| Method | Modifiers | Description |
| ----------------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [evaluate(pageFunction, args)](./puppeteer.webworker.evaluate.md) | | If the function passed to the <code>worker.evaluate</code> returns a Promise, then <code>worker.evaluate</code> would wait for the promise to resolve and return its value. If the function passed to the <code>worker.evaluate</code> returns a non-serializable value, then <code>worker.evaluate</code> resolves to <code>undefined</code>. DevTools Protocol also supports transferring some additional values that are not serializable by <code>JSON</code>: <code>-0</code>, <code>NaN</code>, <code>Infinity</code>, <code>-Infinity</code>, and bigint literals. Shortcut for <code>await worker.executionContext()).evaluate(pageFunction, ...args)</code>. |
| [evaluateHandle(pageFunction, args)](./puppeteer.webworker.evaluatehandle.md) | | The only difference between <code>worker.evaluate</code> and <code>worker.evaluateHandle</code> is that <code>worker.evaluateHandle</code> returns in-page object (JSHandle). If the function passed to the <code>worker.evaluateHandle</code> returns a <code>Promise</code>, then <code>worker.evaluateHandle</code> would wait for the promise to resolve and return its value. Shortcut for <code>await worker.executionContext()).evaluateHandle(pageFunction, ...args)</code> |
| --------------------------------------------------------------------- | --------- | --------------------------------------------------------------------- |
| [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. |

View File

@ -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

View File

@ -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

View File

@ -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<EventType, unknown>
> {
/**
* @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<Params> = EvaluateFunc<Params>,
>(func: Func | string, ...args: Params): Promise<Awaited<ReturnType<Func>>> {
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<Params> = EvaluateFunc<Params>,
>(
func: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
func = withSourcePuppeteerURLIfNone(this.evaluateHandle.name, func);
return await this.mainRealm().evaluateHandle(func, ...args);
}
}

View File

@ -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';

View File

@ -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;
}

View File

@ -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<string, string>();
#coverage: Coverage;
#viewport: Viewport | null;
#workers = new Map<string, WebWorker>();
#workers = new Map<string, CdpWebWorker>();
#fileChooserDeferreds = new Set<Deferred<FileChooser>>();
#sessionCloseDeferred = Deferred.create<never, TargetCloseError>();
#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());
}

View File

@ -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<WebWorker>;
#workerPromise?: Promise<CdpWebWorker>;
override async worker(): Promise<WebWorker | null> {
override async worker(): Promise<CdpWebWorker | null> {
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 */,

View File

@ -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';
@ -43,54 +42,21 @@ export type ExceptionThrownCallback = (
event: Protocol.Runtime.ExceptionThrownEvent
) => 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
*/
export class WebWorker extends EventEmitter<Record<EventType, unknown>> {
/**
* @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<Record<EventType, unknown>> {
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<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<Awaited<ReturnType<Func>>> {
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<Params> = EvaluateFunc<Params>,
>(
pageFunction: Func | string,
...args: Params
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
pageFunction = withSourcePuppeteerURLIfNone(
this.evaluateHandle.name,
pageFunction
);
return await this.mainRealm().evaluateHandle(pageFunction, ...args);
}
}

View File

@ -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';