chore: refactor JSHandle and ExecutionContext (#8773)

This commit is contained in:
jrandolf 2022-08-11 11:45:35 +02:00 committed by GitHub
parent ee2540baef
commit a238f5758d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 298 additions and 333 deletions

View File

@ -6,40 +6,40 @@ sidebar_label: API
## Classes ## Classes
| Class | Description | | Class | Description |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Accessibility](./puppeteer.accessibility.md) | The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or [switches](https://en.wikipedia.org/wiki/Switch_access). | | [Accessibility](./puppeteer.accessibility.md) | The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by assistive technology such as [screen readers](https://en.wikipedia.org/wiki/Screen_reader) or [switches](https://en.wikipedia.org/wiki/Switch_access). |
| [Browser](./puppeteer.browser.md) | A Browser is created when Puppeteer connects to a Chromium instance, either through [PuppeteerNode.launch()](./puppeteer.puppeteernode.launch.md) or [Puppeteer.connect()](./puppeteer.puppeteer.connect.md). | | [Browser](./puppeteer.browser.md) | A Browser is created when Puppeteer connects to a Chromium instance, either through [PuppeteerNode.launch()](./puppeteer.puppeteernode.launch.md) or [Puppeteer.connect()](./puppeteer.puppeteer.connect.md). |
| [BrowserContext](./puppeteer.browsercontext.md) | BrowserContexts provide a way to operate multiple independent browser sessions. When a browser is launched, it has a single BrowserContext used by default. The method [Browser.newPage](./puppeteer.browser.newpage.md) creates a page in the default browser context. | | [BrowserContext](./puppeteer.browsercontext.md) | BrowserContexts provide a way to operate multiple independent browser sessions. When a browser is launched, it has a single BrowserContext used by default. The method [Browser.newPage](./puppeteer.browser.newpage.md) creates a page in the default browser context. |
| [BrowserFetcher](./puppeteer.browserfetcher.md) | BrowserFetcher can download and manage different versions of Chromium and Firefox. | | [BrowserFetcher](./puppeteer.browserfetcher.md) | BrowserFetcher can download and manage different versions of Chromium and Firefox. |
| [CDPSession](./puppeteer.cdpsession.md) | The <code>CDPSession</code> instances are used to talk raw Chrome Devtools Protocol. | | [CDPSession](./puppeteer.cdpsession.md) | The <code>CDPSession</code> instances are used to talk raw Chrome Devtools Protocol. |
| [Connection](./puppeteer.connection.md) | | | [Connection](./puppeteer.connection.md) | |
| [ConsoleMessage](./puppeteer.consolemessage.md) | ConsoleMessage objects are dispatched by page via the 'console' event. | | [ConsoleMessage](./puppeteer.consolemessage.md) | ConsoleMessage objects are dispatched by page via the 'console' event. |
| [Coverage](./puppeteer.coverage.md) | The Coverage class provides methods to gathers information about parts of JavaScript and CSS that were used by the page. | | [Coverage](./puppeteer.coverage.md) | The Coverage class provides methods to gathers information about parts of JavaScript and CSS that were used by the page. |
| [CSSCoverage](./puppeteer.csscoverage.md) | | | [CSSCoverage](./puppeteer.csscoverage.md) | |
| [CustomError](./puppeteer.customerror.md) | | | [CustomError](./puppeteer.customerror.md) | |
| [Dialog](./puppeteer.dialog.md) | Dialog instances are dispatched by the [Page](./puppeteer.page.md) via the <code>dialog</code> event. | | [Dialog](./puppeteer.dialog.md) | Dialog instances are dispatched by the [Page](./puppeteer.page.md) via the <code>dialog</code> event. |
| [ElementHandle](./puppeteer.elementhandle.md) | ElementHandle represents an in-page DOM element. | | [ElementHandle](./puppeteer.elementhandle.md) | ElementHandle represents an in-page DOM element. |
| [EventEmitter](./puppeteer.eventemitter.md) | The EventEmitter class that many Puppeteer classes extend. | | [EventEmitter](./puppeteer.eventemitter.md) | The EventEmitter class that many Puppeteer classes extend. |
| [ExecutionContext](./puppeteer.executioncontext.md) | <p>This class represents a context for JavaScript execution. A \[Page\] might have many execution contexts: - each [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) has "default" execution context that is always created after frame is attached to DOM. This context is returned by the [Frame.executionContext()](./puppeteer.frame.executioncontext.md) method. - [Extension](https://developer.chrome.com/extensions)'s content scripts create additional execution contexts.</p><p>Besides pages, execution contexts can be found in [workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).</p> | | [ExecutionContext](./puppeteer.executioncontext.md) | Represents a context for JavaScript execution. |
| [FileChooser](./puppeteer.filechooser.md) | File choosers let you react to the page requesting for a file. | | [FileChooser](./puppeteer.filechooser.md) | File choosers let you react to the page requesting for a file. |
| [Frame](./puppeteer.frame.md) | At every point of time, page exposes its current frame tree via the [page.mainFrame](./puppeteer.page.mainframe.md) and [frame.childFrames](./puppeteer.frame.childframes.md) methods. | | [Frame](./puppeteer.frame.md) | At every point of time, page exposes its current frame tree via the [page.mainFrame](./puppeteer.page.mainframe.md) and [frame.childFrames](./puppeteer.frame.childframes.md) methods. |
| [HTTPRequest](./puppeteer.httprequest.md) | Represents an HTTP request sent by a page. | | [HTTPRequest](./puppeteer.httprequest.md) | Represents an HTTP request sent by a page. |
| [HTTPResponse](./puppeteer.httpresponse.md) | The HTTPResponse class represents responses which are received by the [Page](./puppeteer.page.md) class. | | [HTTPResponse](./puppeteer.httpresponse.md) | The HTTPResponse class represents responses which are received by the [Page](./puppeteer.page.md) class. |
| [JSCoverage](./puppeteer.jscoverage.md) | | | [JSCoverage](./puppeteer.jscoverage.md) | |
| [JSHandle](./puppeteer.jshandle.md) | Represents an in-page JavaScript object. JSHandles can be created with the [page.evaluateHandle](./puppeteer.page.evaluatehandle.md) method. | | [JSHandle](./puppeteer.jshandle.md) | <p>Represents a reference to a JavaScript object. Instances can be created using [Page.evaluateHandle()](./puppeteer.page.evaluatehandle.md).</p><p>Handles prevent the referenced JavaScript object from being garbage-collected unless the handle is purposely [disposed](./puppeteer.jshandle.dispose.md). JSHandles are auto-disposed when their associated frame is navigated away or the parent context gets destroyed.</p><p>Handles can be used as arguments for any evaluation function such as [Page.$eval()](./puppeteer.page._eval.md), [Page.evaluate()](./puppeteer.page.evaluate.md), and [Page.evaluateHandle()](./puppeteer.page.evaluatehandle.md). They are resolved to their referenced object.</p> |
| [Keyboard](./puppeteer.keyboard.md) | Keyboard provides an api for managing a virtual keyboard. The high level api is [Keyboard.type()](./puppeteer.keyboard.type.md), which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page. | | [Keyboard](./puppeteer.keyboard.md) | Keyboard provides an api for managing a virtual keyboard. The high level api is [Keyboard.type()](./puppeteer.keyboard.type.md), which takes raw characters and generates proper keydown, keypress/input, and keyup events on your page. |
| [Mouse](./puppeteer.mouse.md) | The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport. | | [Mouse](./puppeteer.mouse.md) | The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport. |
| [Page](./puppeteer.page.md) | <p>Page provides methods to interact with a single tab or [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium.</p><p>:::note</p><p>One Browser instance might have multiple Page instances.</p><p>:::</p> | | [Page](./puppeteer.page.md) | <p>Page provides methods to interact with a single tab or [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium.</p><p>:::note</p><p>One Browser instance might have multiple Page instances.</p><p>:::</p> |
| [ProtocolError](./puppeteer.protocolerror.md) | ProtocolError is emitted whenever there is an error from the protocol. | | [ProtocolError](./puppeteer.protocolerror.md) | ProtocolError is emitted whenever there is an error from the protocol. |
| [Puppeteer](./puppeteer.puppeteer.md) | <p>The main Puppeteer class.</p><p>IMPORTANT: if you are using Puppeteer in a Node environment, you will get an instance of [PuppeteerNode](./puppeteer.puppeteernode.md) when you import or require <code>puppeteer</code>. That class extends <code>Puppeteer</code>, so has all the methods documented below as well as all that are defined on [PuppeteerNode](./puppeteer.puppeteernode.md).</p> | | [Puppeteer](./puppeteer.puppeteer.md) | <p>The main Puppeteer class.</p><p>IMPORTANT: if you are using Puppeteer in a Node environment, you will get an instance of [PuppeteerNode](./puppeteer.puppeteernode.md) when you import or require <code>puppeteer</code>. That class extends <code>Puppeteer</code>, so has all the methods documented below as well as all that are defined on [PuppeteerNode](./puppeteer.puppeteernode.md).</p> |
| [PuppeteerNode](./puppeteer.puppeteernode.md) | <p>Extends the main [Puppeteer](./puppeteer.puppeteer.md) class with Node specific behaviour for fetching and downloading browsers.</p><p>If you're using Puppeteer in a Node environment, this is the class you'll get when you run <code>require('puppeteer')</code> (or the equivalent ES <code>import</code>).</p> | | [PuppeteerNode](./puppeteer.puppeteernode.md) | <p>Extends the main [Puppeteer](./puppeteer.puppeteer.md) class with Node specific behaviour for fetching and downloading browsers.</p><p>If you're using Puppeteer in a Node environment, this is the class you'll get when you run <code>require('puppeteer')</code> (or the equivalent ES <code>import</code>).</p> |
| [SecurityDetails](./puppeteer.securitydetails.md) | The SecurityDetails class represents the security details of a response that was received over a secure connection. | | [SecurityDetails](./puppeteer.securitydetails.md) | The SecurityDetails class represents the security details of a response that was received over a secure connection. |
| [Target](./puppeteer.target.md) | | | [Target](./puppeteer.target.md) | |
| [TimeoutError](./puppeteer.timeouterror.md) | TimeoutError is emitted whenever certain operations are terminated due to timeout. | | [TimeoutError](./puppeteer.timeouterror.md) | TimeoutError is emitted whenever certain operations are terminated due to timeout. |
| [Touchscreen](./puppeteer.touchscreen.md) | The Touchscreen class exposes touchscreen events. | | [Touchscreen](./puppeteer.touchscreen.md) | The Touchscreen class exposes touchscreen events. |
| [Tracing](./puppeteer.tracing.md) | The Tracing class exposes the tracing audit interface. | | [Tracing](./puppeteer.tracing.md) | The Tracing class exposes the tracing audit interface. |
| [WebWorker](./puppeteer.webworker.md) | This class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). | | [WebWorker](./puppeteer.webworker.md) | This class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). |
## Enumerations ## Enumerations

View File

@ -4,6 +4,8 @@ sidebar_label: ExecutionContext.evaluate
# ExecutionContext.evaluate() method # ExecutionContext.evaluate() method
Evaluates the given function.
**Signature:** **Signature:**
```typescript ```typescript
@ -20,20 +22,16 @@ class ExecutionContext {
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| ------------ | -------------- | --------------------------------------------------------------- | | ------------ | -------------- | ----------------------------------------------- |
| pageFunction | Func \| string | a function to be evaluated in the <code>executionContext</code> | | pageFunction | Func \| string | The function to evaluate. |
| args | Params | argument to pass to the page function | | args | Params | Additional arguments to pass into the function. |
**Returns:** **Returns:**
Promise&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt; Promise&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;
A promise that resolves to the return value of the given function. The result of evaluating the function. If the result is an object, a vanilla object containing the serializable properties of the result is returned.
## Remarks
If the function passed to the `executionContext.evaluate` returns a Promise, then `executionContext.evaluate` would wait for the promise to resolve and return its value. If the function passed to the `executionContext.evaluate` returns a non-serializable value, then `executionContext.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.
## Example 1 ## Example 1
@ -45,7 +43,7 @@ console.log(result); // prints "56"
## Example 2 ## Example 2
A string can also be passed in instead of a function. A string can also be passed in instead of a function:
```ts ```ts
console.log(await executionContext.evaluate('1 + 2')); // prints "3" console.log(await executionContext.evaluate('1 + 2')); // prints "3"
@ -53,13 +51,15 @@ console.log(await executionContext.evaluate('1 + 2')); // prints "3"
## Example 3 ## Example 3
[JSHandle](./puppeteer.jshandle.md) instances can be passed as arguments to the `executionContext.* evaluate`: Handles can also be passed as `args`. They resolve to their referenced object:
```ts ```ts
const oneHandle = await executionContext.evaluateHandle(() => 1); const oneHandle = await executionContext.evaluateHandle(() => 1);
const twoHandle = await executionContext.evaluateHandle(() => 2); const twoHandle = await executionContext.evaluateHandle(() => 2);
const result = await executionContext.evaluate( const result = await executionContext.evaluate(
(a, b) => a + b, oneHandle, * twoHandle (a, b) => a + b,
oneHandle,
twoHandle
); );
await oneHandle.dispose(); await oneHandle.dispose();
await twoHandle.dispose(); await twoHandle.dispose();

View File

@ -4,6 +4,12 @@ sidebar_label: ExecutionContext.evaluateHandle
# ExecutionContext.evaluateHandle() method # ExecutionContext.evaluateHandle() method
Evaluates the given function.
Unlike [evaluate](./puppeteer.executioncontext.evaluate.md), this method returns a handle to the result of the function.
This method may be better suited if the object cannot be serialized (e.g. `Map`) and requires further manipulation.
**Signature:** **Signature:**
```typescript ```typescript
@ -20,27 +26,24 @@ class ExecutionContext {
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| ------------ | -------------- | --------------------------------------------------------------- | | ------------ | -------------- | ----------------------------------------------- |
| pageFunction | Func \| string | a function to be evaluated in the <code>executionContext</code> | | pageFunction | Func \| string | The function to evaluate. |
| args | Params | argument to pass to the page function | | args | Params | Additional arguments to pass into the function. |
**Returns:** **Returns:**
Promise&lt;[HandleFor](./puppeteer.handlefor.md)&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;&gt; Promise&lt;[HandleFor](./puppeteer.handlefor.md)&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;&gt;
A promise that resolves to the return value of the given function as an in-page object (a [JSHandle](./puppeteer.jshandle.md)). A [handle](./puppeteer.jshandle.md) to the result of evaluating the function. If the result is a `Node`, then this will return an [element handle](./puppeteer.elementhandle.md).
## Remarks
The only difference between `executionContext.evaluate` and `executionContext.evaluateHandle` is that `executionContext.evaluateHandle` returns an in-page object (a [JSHandle](./puppeteer.jshandle.md)). If the function passed to the `executionContext.evaluateHandle` returns a Promise, then `executionContext.evaluateHandle` would wait for the promise to resolve and return its value.
## Example 1 ## Example 1
```ts ```ts
const context = await page.mainFrame().executionContext(); const context = await page.mainFrame().executionContext();
const aHandle = await context.evaluateHandle(() => Promise.resolve(self)); const handle: JSHandle<typeof globalThis> = await context.evaluateHandle(() =>
aHandle; // Handle for the global object. Promise.resolve(self)
);
``` ```
## Example 2 ## Example 2
@ -48,18 +51,25 @@ aHandle; // Handle for the global object.
A string can also be passed in instead of a function. A string can also be passed in instead of a function.
```ts ```ts
// Handle for the '3' * object. const handle: JSHandle<number> = await context.evaluateHandle('1 + 2');
const aHandle = await context.evaluateHandle('1 + 2');
``` ```
## Example 3 ## Example 3
JSHandle instances can be passed as arguments to the `executionContext.* evaluateHandle`: Handles can also be passed as `args`. They resolve to their referenced object:
```ts ```ts
const aHandle = await context.evaluateHandle(() => document.body); const bodyHandle: ElementHandle<HTMLBodyElement> = await context.evaluateHandle(
const resultHandle = await context.evaluateHandle(body => body.innerHTML, * aHandle); () => {
console.log(await resultHandle.jsonValue()); // prints body's innerHTML return document.body;
await aHandle.dispose(); }
await resultHandle.dispose(); );
const stringHandle: JSHandle<string> = await context.evaluateHandle(
body => body.innerHTML,
body
);
console.log(await stringHandle.jsonValue()); // prints body's innerHTML
// Always dispose your garbage! :)
await bodyHandle.dispose();
await stringHandle.dispose();
``` ```

View File

@ -20,4 +20,4 @@ The frame associated with this execution context.
## Remarks ## Remarks
Not every execution context is associated with a frame. For example, workers and extensions have execution contexts that are not associated with frames. Not every execution context is associated with a frame. For example, [workers](./puppeteer.webworker.md) have execution contexts that are not associated with frames.

View File

@ -4,9 +4,7 @@ sidebar_label: ExecutionContext
# ExecutionContext class # ExecutionContext class
This class represents a context for JavaScript execution. A \[Page\] might have many execution contexts: - each [frame](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) has "default" execution context that is always created after frame is attached to DOM. This context is returned by the [Frame.executionContext()](./puppeteer.frame.executioncontext.md) method. - [Extension](https://developer.chrome.com/extensions)'s content scripts create additional execution contexts. Represents a context for JavaScript execution.
Besides pages, execution contexts can be found in [workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).
**Signature:** **Signature:**
@ -16,13 +14,21 @@ export declare class ExecutionContext
## Remarks ## Remarks
Besides pages, execution contexts can be found in [workers](./puppeteer.webworker.md).
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ExecutionContext` class. The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ExecutionContext` class.
## Example
A [Page](./puppeteer.page.md) can have several execution contexts:
- Each [Frame](./puppeteer.frame.md) of a [page](./puppeteer.page.md) has a "default" execution context that is always created after frame is attached to DOM. This context is returned by the [Frame.executionContext()](./puppeteer.frame.executioncontext.md) method. - Each [Chrome extensions](https://developer.chrome.com/extensions) creates additional execution contexts to isolate their code.
## Methods ## Methods
| Method | Modifiers | Description | | Method | Modifiers | Description |
| ------------------------------------------------------------------------------------ | --------- | -------------------------------------------------------------------------------------------- | | ------------------------------------------------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [evaluate(pageFunction, args)](./puppeteer.executioncontext.evaluate.md) | | | | [evaluate(pageFunction, args)](./puppeteer.executioncontext.evaluate.md) | | Evaluates the given function. |
| [evaluateHandle(pageFunction, args)](./puppeteer.executioncontext.evaluatehandle.md) | | | | [evaluateHandle(pageFunction, args)](./puppeteer.executioncontext.evaluatehandle.md) | | <p>Evaluates the given function.</p><p>Unlike [evaluate](./puppeteer.executioncontext.evaluate.md), this method returns a handle to the result of the function.</p><p>This method may be better suited if the object cannot be serialized (e.g. <code>Map</code>) and requires further manipulation.</p> |
| [frame()](./puppeteer.executioncontext.frame.md) | | | | [frame()](./puppeteer.executioncontext.frame.md) | | |
| [queryObjects(prototypeHandle)](./puppeteer.executioncontext.queryobjects.md) | | This method iterates the JavaScript heap and finds all the objects with the given prototype. | | [queryObjects(prototypeHandle)](./puppeteer.executioncontext.queryobjects.md) | | Iterates through the JavaScript heap and finds all the objects with the given prototype. |

View File

@ -4,7 +4,7 @@ sidebar_label: ExecutionContext.queryObjects
# ExecutionContext.queryObjects() method # ExecutionContext.queryObjects() method
This method iterates the JavaScript heap and finds all the objects with the given prototype. Iterates through the JavaScript heap and finds all the objects with the given prototype.
**Signature:** **Signature:**
@ -28,8 +28,6 @@ Promise&lt;[HandleFor](./puppeteer.handlefor.md)&lt;Prototype\[\]&gt;&gt;
A handle to an array of objects with the given prototype. A handle to an array of objects with the given prototype.
## Remarks
## Example ## Example
```ts ```ts

View File

@ -16,4 +16,4 @@ class JSHandle {
[ElementHandle](./puppeteer.elementhandle.md)&lt;Node&gt; \| null [ElementHandle](./puppeteer.elementhandle.md)&lt;Node&gt; \| null
Either `null` or the object handle itself, if the object handle is an instance of [ElementHandle](./puppeteer.elementhandle.md). Either `null` or the handle itself if the handle is an instance of [ElementHandle](./puppeteer.elementhandle.md).

View File

@ -4,7 +4,7 @@ sidebar_label: JSHandle.dispose
# JSHandle.dispose() method # JSHandle.dispose() method
Stops referencing the element handle, and resolves when the object handle is successfully disposed of. Releases the object referenced by the handle for garbage collection.
**Signature:** **Signature:**

View File

@ -4,7 +4,7 @@ sidebar_label: JSHandle.evaluate
# JSHandle.evaluate() method # JSHandle.evaluate() method
This method passes this handle as the first argument to `pageFunction`. If `pageFunction` returns a Promise, then `handle.evaluate` would wait for the promise to resolve and return its value. Evaluates the given function with the current handle as its first argument.
**Signature:** **Signature:**
@ -32,10 +32,3 @@ class JSHandle {
**Returns:** **Returns:**
Promise&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt; Promise&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;
## Example
```ts
const tweetHandle = await page.$('.tweet .retweets');
expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
```

View File

@ -4,7 +4,7 @@ sidebar_label: JSHandle.evaluateHandle
# JSHandle.evaluateHandle() method # JSHandle.evaluateHandle() method
This method passes this handle as the first argument to `pageFunction`. Evaluates the given function with the current handle as its first argument.
**Signature:** **Signature:**
@ -32,11 +32,3 @@ class JSHandle {
**Returns:** **Returns:**
Promise&lt;[HandleFor](./puppeteer.handlefor.md)&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;&gt; Promise&lt;[HandleFor](./puppeteer.handlefor.md)&lt;Awaited&lt;ReturnType&lt;Func&gt;&gt;&gt;&gt;
## Remarks
The only difference between `jsHandle.evaluate` and `jsHandle.evaluateHandle` is that `jsHandle.evaluateHandle` returns an in-page object (JSHandle).
If the function passed to `jsHandle.evaluateHandle` returns a Promise, then `evaluateHandle.evaluateHandle` waits for the promise to resolve and returns its value.
See [Page.evaluateHandle()](./puppeteer.page.evaluatehandle.md) for more details.

View File

@ -4,8 +4,6 @@ sidebar_label: JSHandle.executionContext
# JSHandle.executionContext() method # JSHandle.executionContext() method
Returns the execution context the handle belongs to.
**Signature:** **Signature:**
```typescript ```typescript
@ -17,3 +15,5 @@ class JSHandle {
**Returns:** **Returns:**
[ExecutionContext](./puppeteer.executioncontext.md) [ExecutionContext](./puppeteer.executioncontext.md)
The execution context the handle belongs to.

View File

@ -4,7 +4,7 @@ sidebar_label: JSHandle.getProperties
# JSHandle.getProperties() method # JSHandle.getProperties() method
The method returns a map with property names as keys and JSHandle instances for the property values. Gets a map of handles representing the properties of the current handle.
**Signature:** **Signature:**
@ -26,7 +26,9 @@ const properties = await listHandle.getProperties();
const children = []; const children = [];
for (const property of properties.values()) { for (const property of properties.values()) {
const element = property.asElement(); const element = property.asElement();
if (element) children.push(element); if (element) {
children.push(element);
}
} }
children; // holds elementHandles to all children of document.body children; // holds elementHandles to all children of document.body
``` ```

View File

@ -8,7 +8,7 @@ sidebar_label: JSHandle.jsonValue
```typescript ```typescript
class JSHandle { class JSHandle {
jsonValue<T = unknown>(): Promise<T>; jsonValue(): Promise<T>;
} }
``` ```
@ -16,8 +16,12 @@ class JSHandle {
Promise&lt;T&gt; Promise&lt;T&gt;
Returns a JSON representation of the object.If the object has a `toJSON` function, it will not be called. A vanilla object representing the serializable portions of the referenced object.
## Exceptions
Throws if the object cannot be serialized due to circularity.
## Remarks ## Remarks
The JSON is generated by running [JSON.stringify](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) on the object in page and consequent [JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) in puppeteer. \*\*NOTE\*\* The method throws if the referenced object is not stringifiable. If the object has a `toJSON` function, it \*will not\* be called.

View File

@ -4,7 +4,11 @@ sidebar_label: JSHandle
# JSHandle class # JSHandle class
Represents an in-page JavaScript object. JSHandles can be created with the [page.evaluateHandle](./puppeteer.page.evaluatehandle.md) method. Represents a reference to a JavaScript object. Instances can be created using [Page.evaluateHandle()](./puppeteer.page.evaluatehandle.md).
Handles prevent the referenced JavaScript object from being garbage-collected unless the handle is purposely [disposed](./puppeteer.jshandle.dispose.md). JSHandles are auto-disposed when their associated frame is navigated away or the parent context gets destroyed.
Handles can be used as arguments for any evaluation function such as [Page.$eval()](./puppeteer.page._eval.md), [Page.evaluate()](./puppeteer.page.evaluate.md), and [Page.evaluateHandle()](./puppeteer.page.evaluatehandle.md). They are resolved to their referenced object.
**Signature:** **Signature:**
@ -22,10 +26,6 @@ The constructor for this class is marked as internal. Third-party code should no
const windowHandle = await page.evaluateHandle(() => window); const windowHandle = await page.evaluateHandle(() => window);
``` ```
JSHandle prevents the referenced JavaScript object from being garbage-collected unless the handle is [disposed](./puppeteer.jshandle.dispose.md). JSHandles are auto- disposed when their origin frame gets navigated or the parent context gets destroyed.
JSHandle instances can be used as arguments for [Page.$eval()](./puppeteer.page._eval.md), [Page.evaluate()](./puppeteer.page.evaluate.md), and [Page.evaluateHandle()](./puppeteer.page.evaluatehandle.md).
## Properties ## Properties
| Property | Modifiers | Type | Description | | Property | Modifiers | Type | Description |
@ -34,16 +34,16 @@ JSHandle instances can be used as arguments for [Page.$eval()](./puppeteer.page.
## Methods ## Methods
| Method | Modifiers | Description | | Method | Modifiers | Description |
| ---------------------------------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------------------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [asElement()](./puppeteer.jshandle.aselement.md) | | | | [asElement()](./puppeteer.jshandle.aselement.md) | | |
| [dispose()](./puppeteer.jshandle.dispose.md) | | Stops referencing the element handle, and resolves when the object handle is successfully disposed of. | | [dispose()](./puppeteer.jshandle.dispose.md) | | Releases the object referenced by the handle for garbage collection. |
| [evaluate(pageFunction, args)](./puppeteer.jshandle.evaluate.md) | | This method passes this handle as the first argument to <code>pageFunction</code>. If <code>pageFunction</code> returns a Promise, then <code>handle.evaluate</code> would wait for the promise to resolve and return its value. | | [evaluate(pageFunction, args)](./puppeteer.jshandle.evaluate.md) | | Evaluates the given function with the current handle as its first argument. |
| [evaluateHandle(pageFunction, args)](./puppeteer.jshandle.evaluatehandle.md) | | This method passes this handle as the first argument to <code>pageFunction</code>. | | [evaluateHandle(pageFunction, args)](./puppeteer.jshandle.evaluatehandle.md) | | Evaluates the given function with the current handle as its first argument. |
| [executionContext()](./puppeteer.jshandle.executioncontext.md) | | Returns the execution context the handle belongs to. | | [executionContext()](./puppeteer.jshandle.executioncontext.md) | | |
| [getProperties()](./puppeteer.jshandle.getproperties.md) | | The method returns a map with property names as keys and JSHandle instances for the property values. | | [getProperties()](./puppeteer.jshandle.getproperties.md) | | Gets a map of handles representing the properties of the current handle. |
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty.md) | | Fetches a single property from the referenced object. | | [getProperty(propertyName)](./puppeteer.jshandle.getproperty.md) | | Fetches a single property from the referenced object. |
| [getProperty(propertyName)](./puppeteer.jshandle.getproperty_1.md) | | | | [getProperty(propertyName)](./puppeteer.jshandle.getproperty_1.md) | | |
| [jsonValue()](./puppeteer.jshandle.jsonvalue.md) | | | | [jsonValue()](./puppeteer.jshandle.jsonvalue.md) | | |
| [remoteObject()](./puppeteer.jshandle.remoteobject.md) | | Provides access to \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this JSHandle. | | [remoteObject()](./puppeteer.jshandle.remoteobject.md) | | Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this handle. |
| [toString()](./puppeteer.jshandle.tostring.md) | | Returns a string representation of the JSHandle. | | [toString()](./puppeteer.jshandle.tostring.md) | | Returns a string representation of the JSHandle. |

View File

@ -4,7 +4,7 @@ sidebar_label: JSHandle.remoteObject
# JSHandle.remoteObject() method # JSHandle.remoteObject() method
Provides access to \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this JSHandle. Provides access to the \[Protocol.Runtime.RemoteObject\](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/\#type-RemoteObject) backing this handle.
**Signature:** **Signature:**

View File

@ -185,7 +185,7 @@ export class Accessibility {
let backendNodeId: number | undefined; let backendNodeId: number | undefined;
if (root) { if (root) {
const {node} = await this.#client.send('DOM.describeNode', { const {node} = await this.#client.send('DOM.describeNode', {
objectId: root._remoteObject.objectId, objectId: root.remoteObject().objectId,
}); });
backendNodeId = node.backendNodeId; backendNodeId = node.backendNodeId;
} }

View File

@ -33,7 +33,7 @@ async function queryAXTree(
role?: string role?: string
): Promise<Protocol.Accessibility.AXNode[]> { ): Promise<Protocol.Accessibility.AXNode[]> {
const {nodes} = await client.send('Accessibility.queryAXTree', { const {nodes} = await client.send('Accessibility.queryAXTree', {
objectId: element._remoteObject.objectId, objectId: element.remoteObject().objectId,
accessibleName, accessibleName,
role, role,
}); });

View File

@ -277,7 +277,7 @@ export class ElementHandle<
selector: Selector, selector: Selector,
options: Exclude<WaitForSelectorOptions, 'root'> = {} options: Exclude<WaitForSelectorOptions, 'root'> = {}
): Promise<ElementHandle<NodeFor<Selector>> | null> { ): Promise<ElementHandle<NodeFor<Selector>> | null> {
const frame = this._context.frame(); const frame = this.executionContext().frame();
assert(frame); assert(frame);
const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this); const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this);
const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector( const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector(
@ -376,8 +376,8 @@ export class ElementHandle<
* iframe nodes, or null otherwise * iframe nodes, or null otherwise
*/ */
async contentFrame(): Promise<Frame | null> { async contentFrame(): Promise<Frame | null> {
const nodeInfo = await this._client.send('DOM.describeNode', { const nodeInfo = await this.client.send('DOM.describeNode', {
objectId: this._remoteObject.objectId, objectId: this.remoteObject().objectId,
}); });
if (typeof nodeInfo.node.frameId !== 'string') { if (typeof nodeInfo.node.frameId !== 'string') {
return null; return null;
@ -403,8 +403,8 @@ export class ElementHandle<
} }
try { try {
await this._client.send('DOM.scrollIntoViewIfNeeded', { await this.client.send('DOM.scrollIntoViewIfNeeded', {
objectId: this._remoteObject.objectId, objectId: this.remoteObject().objectId,
}); });
} catch (_err) { } catch (_err) {
// Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported // Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
@ -470,9 +470,9 @@ export class ElementHandle<
*/ */
async clickablePoint(offset?: Offset): Promise<Point> { async clickablePoint(offset?: Offset): Promise<Point> {
const [result, layoutMetrics] = await Promise.all([ const [result, layoutMetrics] = await Promise.all([
this._client this.client
.send('DOM.getContentQuads', { .send('DOM.getContentQuads', {
objectId: this._remoteObject.objectId, objectId: this.remoteObject().objectId,
}) })
.catch(debugError), .catch(debugError),
this.#page._client().send('Page.getLayoutMetrics'), this.#page._client().send('Page.getLayoutMetrics'),
@ -539,9 +539,9 @@ export class ElementHandle<
#getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> { #getBoxModel(): Promise<void | Protocol.DOM.GetBoxModelResponse> {
const params: Protocol.DOM.GetBoxModelRequest = { const params: Protocol.DOM.GetBoxModelRequest = {
objectId: this._remoteObject.objectId, objectId: this.remoteObject().objectId,
}; };
return this._client.send('DOM.getBoxModel', params).catch(error => { return this.client.send('DOM.getBoxModel', params).catch(error => {
return debugError(error); return debugError(error);
}); });
} }
@ -758,8 +758,8 @@ export class ElementHandle<
return path.resolve(filePath); return path.resolve(filePath);
} }
}); });
const {objectId} = this._remoteObject; const {objectId} = this.remoteObject();
const {node} = await this._client.send('DOM.describeNode', {objectId}); const {node} = await this.client.send('DOM.describeNode', {objectId});
const {backendNodeId} = node; const {backendNodeId} = node;
/* The zero-length array is a special case, it seems that /* The zero-length array is a special case, it seems that
@ -775,7 +775,7 @@ export class ElementHandle<
element.dispatchEvent(new Event('change', {bubbles: true})); element.dispatchEvent(new Event('change', {bubbles: true}));
}); });
} else { } else {
await this._client.send('DOM.setFileInputFiles', { await this.client.send('DOM.setFileInputFiles', {
objectId, objectId,
files, files,
backendNodeId, backendNodeId,
@ -954,7 +954,7 @@ export class ElementHandle<
assert(boundingBox.width !== 0, 'Node has 0 width.'); assert(boundingBox.width !== 0, 'Node has 0 width.');
assert(boundingBox.height !== 0, 'Node has 0 height.'); assert(boundingBox.height !== 0, 'Node has 0 height.');
const layoutMetrics = await this._client.send('Page.getLayoutMetrics'); const layoutMetrics = await this.client.send('Page.getLayoutMetrics');
// Fallback to `layoutViewport` in case of using Firefox. // Fallback to `layoutViewport` in case of using Firefox.
const {pageX, pageY} = const {pageX, pageY} =
layoutMetrics.cssVisualViewport || layoutMetrics.layoutViewport; layoutMetrics.cssVisualViewport || layoutMetrics.layoutViewport;

View File

@ -17,16 +17,15 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js'; import {assert} from './assert.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {IsolatedWorld} from './IsolatedWorld.js';
import {ElementHandle} from './ElementHandle.js';
import {Frame} from './FrameManager.js'; import {Frame} from './FrameManager.js';
import {IsolatedWorld} from './IsolatedWorld.js';
import {JSHandle} from './JSHandle.js'; import {JSHandle} from './JSHandle.js';
import {EvaluateFunc, HandleFor} from './types.js'; import {EvaluateFunc, HandleFor} from './types.js';
import { import {
createJSHandle,
getExceptionMessage, getExceptionMessage,
isString, isString,
valueFromRemoteObject, valueFromRemoteObject,
createJSHandle,
} from './util.js'; } from './util.js';
/** /**
@ -36,18 +35,24 @@ export const EVALUATION_SCRIPT_URL = 'pptr://__puppeteer_evaluation_script__';
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m; const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
/** /**
* This class represents a context for JavaScript execution. A [Page] might have * Represents a context for JavaScript execution.
* many execution contexts:
* - each {@link
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe | frame}
* has "default" execution context that is always created after frame is
* attached to DOM. This context is returned by the
* {@link Frame.executionContext} method.
* - {@link https://developer.chrome.com/extensions | Extension}'s content
* scripts create additional execution contexts.
* *
* Besides pages, execution contexts can be found in {@link * @example
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API | workers}. * A {@link Page} can have several execution contexts:
*
* - Each {@link Frame} of a {@link Page | page} has a "default" execution
* context that is always created after frame is attached to DOM. This context
* is returned by the {@link Frame.executionContext} method.
* - Each {@link https://developer.chrome.com/extensions | Chrome extensions}
* creates additional execution contexts to isolate their code.
*
* @remarks
* By definition, each context is isolated from one another, however they are
* all able to manipulate non-JavaScript resources (such as DOM).
*
* @remarks
* Besides pages, execution contexts can be found in
* {@link WebWorker | workers}.
*/ */
export class ExecutionContext { export class ExecutionContext {
/** /**
@ -82,28 +87,19 @@ export class ExecutionContext {
} }
/** /**
* @remarks
*
* Not every execution context is associated with a frame. For example,
* workers and extensions have execution contexts that are not associated with
* frames.
*
* @returns The frame associated with this execution context. * @returns The frame associated with this execution context.
*
* @remarks
* Not every execution context is associated with a frame. For example,
* {@link WebWorker | workers} have execution contexts that are not associated
* with frames.
*/ */
frame(): Frame | null { frame(): Frame | null {
return this._world ? this._world.frame() : null; return this._world ? this._world.frame() : null;
} }
/** /**
* @remarks * Evaluates the given function.
* If the function passed to the `executionContext.evaluate` returns a
* Promise, then `executionContext.evaluate` would wait for the promise to
* resolve and return its value. If the function passed to the
* `executionContext.evaluate` returns a non-serializable value, then
* `executionContext.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.
*
* *
* @example * @example
* ```ts * ```ts
@ -113,29 +109,29 @@ export class ExecutionContext {
* ``` * ```
* *
* @example * @example
* A string can also be passed in instead of a function. * A string can also be passed in instead of a function:
*
* ```ts * ```ts
* console.log(await executionContext.evaluate('1 + 2')); // prints "3" * console.log(await executionContext.evaluate('1 + 2')); // prints "3"
* ``` * ```
* *
* @example * @example
* {@link JSHandle} instances can be passed as arguments to the * Handles can also be passed as `args`. They resolve to their referenced object:
* `executionContext.* evaluate`:
* ```ts * ```ts
* const oneHandle = await executionContext.evaluateHandle(() => 1); * const oneHandle = await executionContext.evaluateHandle(() => 1);
* const twoHandle = await executionContext.evaluateHandle(() => 2); * const twoHandle = await executionContext.evaluateHandle(() => 2);
* const result = await executionContext.evaluate( * const result = await executionContext.evaluate(
* (a, b) => a + b, oneHandle, * twoHandle * (a, b) => a + b, oneHandle, twoHandle
* ); * );
* await oneHandle.dispose(); * await oneHandle.dispose();
* await twoHandle.dispose(); * await twoHandle.dispose();
* console.log(result); // prints '3'. * console.log(result); // prints '3'.
* ``` * ```
* @param pageFunction - a function to be evaluated in the `executionContext`
* @param args - argument to pass to the page function
* *
* @returns A promise that resolves to the return value of the given function. * @param pageFunction - The function to evaluate.
* @param args - Additional arguments to pass into the function.
* @returns The result of evaluating the function. If the result is an object,
* a vanilla object containing the serializable properties of the result is
* returned.
*/ */
async evaluate< async evaluate<
Params extends unknown[], Params extends unknown[],
@ -148,46 +144,51 @@ export class ExecutionContext {
} }
/** /**
* @remarks * Evaluates the given function.
* The only difference between `executionContext.evaluate` and *
* `executionContext.evaluateHandle` is that `executionContext.evaluateHandle` * Unlike {@link ExecutionContext.evaluate | evaluate}, this method returns a
* returns an in-page object (a {@link JSHandle}). * handle to the result of the function.
* If the function passed to the `executionContext.evaluateHandle` returns a *
* Promise, then `executionContext.evaluateHandle` would wait for the * This method may be better suited if the object cannot be serialized (e.g.
* promise to resolve and return its value. * `Map`) and requires further manipulation.
* *
* @example * @example
* ```ts * ```ts
* const context = await page.mainFrame().executionContext(); * const context = await page.mainFrame().executionContext();
* const aHandle = await context.evaluateHandle(() => Promise.resolve(self)); * const handle: JSHandle<typeof globalThis> = await context.evaluateHandle(() =>
* aHandle; // Handle for the global object. * Promise.resolve(self)
* );
* ``` * ```
* *
* @example * @example
* A string can also be passed in instead of a function. * A string can also be passed in instead of a function.
*
* ```ts * ```ts
* // Handle for the '3' * object. * const handle: JSHandle<number> = await context.evaluateHandle('1 + 2');
* const aHandle = await context.evaluateHandle('1 + 2');
* ``` * ```
* *
* @example * @example
* JSHandle instances can be passed as arguments * Handles can also be passed as `args`. They resolve to their referenced object:
* to the `executionContext.* evaluateHandle`:
*
* ```ts * ```ts
* const aHandle = await context.evaluateHandle(() => document.body); * const bodyHandle: ElementHandle<HTMLBodyElement> = await context.evaluateHandle(
* const resultHandle = await context.evaluateHandle(body => body.innerHTML, * aHandle); * () => {
* console.log(await resultHandle.jsonValue()); // prints body's innerHTML * return document.body;
* await aHandle.dispose(); * }
* await resultHandle.dispose(); * );
* const stringHandle: JSHandle<string> = await context.evaluateHandle(
* body => body.innerHTML,
* body
* );
* console.log(await stringHandle.jsonValue()); // prints body's innerHTML
* // Always dispose your garbage! :)
* await bodyHandle.dispose();
* await stringHandle.dispose();
* ``` * ```
* *
* @param pageFunction - a function to be evaluated in the `executionContext` * @param pageFunction - The function to evaluate.
* @param args - argument to pass to the page function * @param args - Additional arguments to pass into the function.
* * @returns A {@link JSHandle | handle} to the result of evaluating the
* @returns A promise that resolves to the return value of the given function * function. If the result is a `Node`, then this will return an
* as an in-page object (a {@link JSHandle}). * {@link ElementHandle | element handle}.
*/ */
async evaluateHandle< async evaluateHandle<
Params extends unknown[], Params extends unknown[],
@ -253,12 +254,6 @@ export class ExecutionContext {
: createJSHandle(this, remoteObject); : createJSHandle(this, remoteObject);
} }
if (typeof pageFunction !== 'function') {
throw new Error(
`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`
);
}
let functionText = pageFunction.toString(); let functionText = pageFunction.toString();
try { try {
new Function('(' + functionText + ')'); new Function('(' + functionText + ')');
@ -330,23 +325,24 @@ export class ExecutionContext {
} }
const objectHandle = arg && arg instanceof JSHandle ? arg : null; const objectHandle = arg && arg instanceof JSHandle ? arg : null;
if (objectHandle) { if (objectHandle) {
if (objectHandle._context !== this) { if (objectHandle.executionContext() !== this) {
throw new Error( throw new Error(
'JSHandles can be evaluated only in the context they were created!' 'JSHandles can be evaluated only in the context they were created!'
); );
} }
if (objectHandle._disposed) { if (objectHandle.disposed) {
throw new Error('JSHandle is disposed!'); throw new Error('JSHandle is disposed!');
} }
if (objectHandle._remoteObject.unserializableValue) { if (objectHandle.remoteObject().unserializableValue) {
return { return {
unserializableValue: objectHandle._remoteObject.unserializableValue, unserializableValue:
objectHandle.remoteObject().unserializableValue,
}; };
} }
if (!objectHandle._remoteObject.objectId) { if (!objectHandle.remoteObject().objectId) {
return {value: objectHandle._remoteObject.value}; return {value: objectHandle.remoteObject().value};
} }
return {objectId: objectHandle._remoteObject.objectId}; return {objectId: objectHandle.remoteObject().objectId};
} }
return {value: arg}; return {value: arg};
} }
@ -372,9 +368,9 @@ export class ExecutionContext {
} }
/** /**
* This method iterates the JavaScript heap and finds all the objects with the * Iterates through the JavaScript heap and finds all the objects with the
* given prototype. * given prototype.
* @remarks *
* @example * @example
* ```ts * ```ts
* // Create a Map object * // Create a Map object
@ -390,33 +386,20 @@ export class ExecutionContext {
* ``` * ```
* *
* @param prototypeHandle - a handle to the object prototype * @param prototypeHandle - a handle to the object prototype
*
* @returns A handle to an array of objects with the given prototype. * @returns A handle to an array of objects with the given prototype.
*/ */
async queryObjects<Prototype>( async queryObjects<Prototype>(
prototypeHandle: JSHandle<Prototype> prototypeHandle: JSHandle<Prototype>
): Promise<HandleFor<Prototype[]>> { ): Promise<HandleFor<Prototype[]>> {
assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!'); assert(!prototypeHandle.disposed, 'Prototype JSHandle is disposed!');
const remoteObject = prototypeHandle.remoteObject();
assert( assert(
prototypeHandle._remoteObject.objectId, remoteObject.objectId,
'Prototype JSHandle must not be referencing primitive value' 'Prototype JSHandle must not be referencing primitive value'
); );
const response = await this._client.send('Runtime.queryObjects', { const response = await this._client.send('Runtime.queryObjects', {
prototypeObjectId: prototypeHandle._remoteObject.objectId, prototypeObjectId: remoteObject.objectId,
}); });
return createJSHandle(this, response.objects) as HandleFor<Prototype[]>; return createJSHandle(this, response.objects) as HandleFor<Prototype[]>;
} }
/**
* @internal
*/
async _adoptBackendNodeId(
backendNodeId?: Protocol.DOM.BackendNodeId
): Promise<ElementHandle<Node>> {
const {object} = await this._client.send('DOM.resolveNode', {
backendNodeId: backendNodeId,
executionContextId: this._contextId,
});
return createJSHandle(this, object) as ElementHandle<Node>;
}
} }

View File

@ -789,7 +789,7 @@ export class IsolatedWorld {
'Cannot adopt handle that already belongs to this execution context' 'Cannot adopt handle that already belongs to this execution context'
); );
const nodeInfo = await this.#client.send('DOM.describeNode', { const nodeInfo = await this.#client.send('DOM.describeNode', {
objectId: handle._remoteObject.objectId, objectId: handle.remoteObject().objectId,
}); });
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T; return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
} }

View File

@ -17,11 +17,11 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {assert} from './assert.js'; import {assert} from './assert.js';
import {CDPSession} from './Connection.js'; import {CDPSession} from './Connection.js';
import {EvaluateFunc, HandleFor, HandleOr} from './types.js'; import type {ElementHandle} from './ElementHandle.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {MouseButton} from './Input.js'; import {MouseButton} from './Input.js';
import {releaseObject, valueFromRemoteObject, createJSHandle} from './util.js'; import {EvaluateFunc, HandleFor, HandleOr} from './types.js';
import type {ElementHandle} from './ElementHandle.js'; import {createJSHandle, releaseObject, valueFromRemoteObject} from './util.js';
declare const __JSHandleSymbol: unique symbol; declare const __JSHandleSymbol: unique symbol;
@ -52,21 +52,23 @@ export interface BoundingBox extends Point {
} }
/** /**
* Represents an in-page JavaScript object. JSHandles can be created with the * Represents a reference to a JavaScript object. Instances can be created using
* {@link Page.evaluateHandle | page.evaluateHandle} method. * {@link Page.evaluateHandle}.
*
* Handles prevent the referenced JavaScript object from being garbage-collected
* unless the handle is purposely {@link JSHandle.dispose | disposed}. JSHandles
* are auto-disposed when their associated frame is navigated away or the parent
* context gets destroyed.
*
* Handles can be used as arguments for any evaluation function such as
* {@link Page.$eval}, {@link Page.evaluate}, and {@link Page.evaluateHandle}.
* They are resolved to their referenced object.
* *
* @example * @example
* ```ts * ```ts
* const windowHandle = await page.evaluateHandle(() => window); * const windowHandle = await page.evaluateHandle(() => window);
* ``` * ```
* *
* JSHandle prevents the referenced JavaScript object from being garbage-collected
* unless the handle is {@link JSHandle.dispose | disposed}. JSHandles are auto-
* disposed when their origin frame gets navigated or the parent context gets destroyed.
*
* JSHandle instances can be used as arguments for {@link Page.$eval},
* {@link Page.evaluate}, and {@link Page.evaluateHandle}.
*
* @public * @public
*/ */
export class JSHandle<T = unknown> { export class JSHandle<T = unknown> {
@ -83,31 +85,17 @@ export class JSHandle<T = unknown> {
/** /**
* @internal * @internal
*/ */
get _client(): CDPSession { get client(): CDPSession {
return this.#client; return this.#client;
} }
/** /**
* @internal * @internal
*/ */
get _disposed(): boolean { get disposed(): boolean {
return this.#disposed; return this.#disposed;
} }
/**
* @internal
*/
get _remoteObject(): Protocol.Runtime.RemoteObject {
return this.#remoteObject;
}
/**
* @internal
*/
get _context(): ExecutionContext {
return this.#context;
}
/** /**
* @internal * @internal
*/ */
@ -121,24 +109,18 @@ export class JSHandle<T = unknown> {
this.#remoteObject = remoteObject; this.#remoteObject = remoteObject;
} }
/** Returns the execution context the handle belongs to. /**
* @returns The execution context the handle belongs to.
*/ */
executionContext(): ExecutionContext { executionContext(): ExecutionContext {
return this.#context; return this.#context;
} }
/** /**
* This method passes this handle as the first argument to `pageFunction`. If * Evaluates the given function with the current handle as its first argument.
* `pageFunction` returns a Promise, then `handle.evaluate` would wait for the
* promise to resolve and return its value.
* *
* @example * @see {@link ExecutionContext.evaluate} for more details.
* ```ts
* const tweetHandle = await page.$('.tweet .retweets');
* expect(await tweetHandle.evaluate(node => node.innerText)).toBe('10');
* ```
*/ */
async evaluate< async evaluate<
Params extends unknown[], Params extends unknown[],
Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc< Func extends EvaluateFunc<[this, ...Params]> = EvaluateFunc<
@ -154,19 +136,9 @@ export class JSHandle<T = unknown> {
} }
/** /**
* This method passes this handle as the first argument to `pageFunction`. * Evaluates the given function with the current handle as its first argument.
* *
* @remarks * @see {@link ExecutionContext.evaluateHandle} for more details.
*
* The only difference between `jsHandle.evaluate` and
* `jsHandle.evaluateHandle` is that `jsHandle.evaluateHandle` returns an
* in-page object (JSHandle).
*
* If the function passed to `jsHandle.evaluateHandle` returns a Promise, then
* `evaluateHandle.evaluateHandle` waits for the promise to resolve and
* returns its value.
*
* See {@link Page.evaluateHandle} for more details.
*/ */
async evaluateHandle< async evaluateHandle<
Params extends unknown[], Params extends unknown[],
@ -196,14 +168,13 @@ export class JSHandle<T = unknown> {
async getProperty<K extends keyof T>( async getProperty<K extends keyof T>(
propertyName: HandleOr<K> propertyName: HandleOr<K>
): Promise<HandleFor<T[K]>> { ): Promise<HandleFor<T[K]>> {
return await this.evaluateHandle((object, propertyName) => { return this.evaluateHandle((object, propertyName) => {
return object[propertyName]; return object[propertyName];
}, propertyName); }, propertyName);
} }
/** /**
* The method returns a map with property names as keys and JSHandle instances * Gets a map of handles representing the properties of the current handle.
* for the property values.
* *
* @example * @example
* ```ts * ```ts
@ -212,14 +183,17 @@ export class JSHandle<T = unknown> {
* const children = []; * const children = [];
* for (const property of properties.values()) { * for (const property of properties.values()) {
* const element = property.asElement(); * const element = property.asElement();
* if (element) * if (element) {
* children.push(element); * children.push(element);
* }
* } * }
* children; // holds elementHandles to all children of document.body * children; // holds elementHandles to all children of document.body
* ``` * ```
*/ */
async getProperties(): Promise<Map<string, JSHandle>> { async getProperties(): Promise<Map<string, JSHandle>> {
assert(this.#remoteObject.objectId); assert(this.#remoteObject.objectId);
// We use Runtime.getProperties rather than iterative building because the
// iterative approach might create a distorted snapshot.
const response = await this.#client.send('Runtime.getProperties', { const response = await this.#client.send('Runtime.getProperties', {
objectId: this.#remoteObject.objectId, objectId: this.#remoteObject.objectId,
ownProperties: true, ownProperties: true,
@ -235,41 +209,36 @@ export class JSHandle<T = unknown> {
} }
/** /**
* @returns Returns a JSON representation of the object.If the object has a * @returns A vanilla object representing the serializable portions of the
* `toJSON` function, it will not be called. * referenced object.
* @remarks * @throws Throws if the object cannot be serialized due to circularity.
* *
* The JSON is generated by running {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify | JSON.stringify} * @remarks
* on the object in page and consequent {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse | JSON.parse} in puppeteer. * If the object has a `toJSON` function, it *will not* be called.
* **NOTE** The method throws if the referenced object is not stringifiable.
*/ */
async jsonValue<T = unknown>(): Promise<T> { async jsonValue(): Promise<T> {
if (this.#remoteObject.objectId) { if (!this.#remoteObject.objectId) {
const response = await this.#client.send('Runtime.callFunctionOn', { return valueFromRemoteObject(this.#remoteObject);
functionDeclaration: 'function() { return this; }',
objectId: this.#remoteObject.objectId,
returnByValue: true,
awaitPromise: true,
});
return valueFromRemoteObject(response.result) as T;
} }
return valueFromRemoteObject(this.#remoteObject) as T; const value = await this.evaluate(object => {
return object;
});
if (value === undefined) {
throw new Error('Could not serialize referenced object');
}
return value;
} }
/** /**
* @returns Either `null` or the object handle itself, if the object * @returns Either `null` or the handle itself if the handle is an
* handle is an instance of {@link ElementHandle}. * instance of {@link ElementHandle}.
*/ */
asElement(): ElementHandle<Node> | null { asElement(): ElementHandle<Node> | null {
/* This always returns null, but subclasses can override this and return an
ElementHandle.
*/
return null; return null;
} }
/** /**
* Stops referencing the element handle, and resolves when the object handle is * Releases the object referenced by the handle for garbage collection.
* successfully disposed of.
*/ */
async dispose(): Promise<void> { async dispose(): Promise<void> {
if (this.#disposed) { if (this.#disposed) {
@ -282,18 +251,21 @@ export class JSHandle<T = unknown> {
/** /**
* Returns a string representation of the JSHandle. * Returns a string representation of the JSHandle.
* *
* @remarks Useful during debugging. * @remarks
* Useful during debugging.
*/ */
toString(): string { toString(): string {
if (this.#remoteObject.objectId) { if (!this.#remoteObject.objectId) {
const type = this.#remoteObject.subtype || this.#remoteObject.type; return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject);
return 'JSHandle@' + type;
} }
return 'JSHandle:' + valueFromRemoteObject(this.#remoteObject); const type = this.#remoteObject.subtype || this.#remoteObject.type;
return 'JSHandle@' + type;
} }
/** /**
* Provides access to [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject) backing this JSHandle. * Provides access to the
* [Protocol.Runtime.RemoteObject](https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-RemoteObject)
* backing this handle.
*/ */
remoteObject(): Protocol.Runtime.RemoteObject { remoteObject(): Protocol.Runtime.RemoteObject {
return this.#remoteObject; return this.#remoteObject;

View File

@ -1710,7 +1710,7 @@ export class Page extends EventEmitter {
} }
const textTokens = []; const textTokens = [];
for (const arg of args) { for (const arg of args) {
const remoteObject = arg._remoteObject; const remoteObject = arg.remoteObject();
if (remoteObject.objectId) { if (remoteObject.objectId) {
textTokens.push(arg.toString()); textTokens.push(arg.toString());
} else { } else {

View File

@ -353,7 +353,7 @@ describe('Evaluation specs', function () {
const windowHandle = await page.evaluateHandle(() => { const windowHandle = await page.evaluateHandle(() => {
return window; return window;
}); });
const errorText = await windowHandle.jsonValue<string>().catch(error_ => { const errorText = await windowHandle.jsonValue().catch(error_ => {
return error_.message; return error_.message;
}); });
const error = await page const error = await page

View File

@ -73,14 +73,9 @@ describe('JSHandle', function () {
test.obj = test; test.obj = test;
let error!: Error; let error!: Error;
await page await page
.evaluateHandle( .evaluateHandle(opts => {
opts => { return opts;
// @ts-expect-error we are deliberately passing a bad type here }, test)
// (nested object)
return opts.elem;
},
{test}
)
.catch(error_ => { .catch(error_ => {
return (error = error_); return (error = error_);
}); });
@ -136,7 +131,7 @@ describe('JSHandle', function () {
const aHandle = await page.evaluateHandle(() => { const aHandle = await page.evaluateHandle(() => {
return {foo: 'bar'}; return {foo: 'bar'};
}); });
const json = await aHandle.jsonValue<Record<string, string>>(); const json = await aHandle.jsonValue();
expect(json).toEqual({foo: 'bar'}); expect(json).toEqual({foo: 'bar'});
}); });
@ -146,7 +141,7 @@ describe('JSHandle', function () {
const aHandle = await page.evaluateHandle(() => { const aHandle = await page.evaluateHandle(() => {
return ['a', 'b']; return ['a', 'b'];
}); });
const json = await aHandle.jsonValue<string[]>(); const json = await aHandle.jsonValue();
expect(json).toEqual(['a', 'b']); expect(json).toEqual(['a', 'b']);
}); });
@ -156,8 +151,12 @@ describe('JSHandle', function () {
const aHandle = await page.evaluateHandle(() => { const aHandle = await page.evaluateHandle(() => {
return 'foo'; return 'foo';
}); });
const json = await aHandle.jsonValue<string>(); expect(await aHandle.jsonValue()).toEqual('foo');
expect(json).toEqual('foo');
const bHandle = await page.evaluateHandle(() => {
return undefined;
});
expect(await bHandle.jsonValue()).toEqual(undefined);
}); });
itFailsFirefox('should not work with dates', async () => { itFailsFirefox('should not work with dates', async () => {
@ -172,13 +171,19 @@ describe('JSHandle', function () {
it('should throw for circular objects', async () => { it('should throw for circular objects', async () => {
const {page, isChrome} = getTestState(); const {page, isChrome} = getTestState();
const windowHandle = await page.evaluateHandle('window'); const handle = await page.evaluateHandle(() => {
const t: {t?: unknown; g: number} = {g: 1};
t.t = t;
return t;
});
let error!: Error; let error!: Error;
await windowHandle.jsonValue().catch(error_ => { await handle.jsonValue().catch(error_ => {
return (error = error_); return (error = error_);
}); });
if (isChrome) { if (isChrome) {
expect(error.message).toContain('Object reference chain is too long'); expect(error.message).toContain(
'Could not serialize referenced object'
);
} else { } else {
expect(error.message).toContain('Object is not serializable'); expect(error.message).toContain('Object is not serializable');
} }