feat: implement typed events (#10889)

This commit is contained in:
jrandolf 2023-09-13 15:47:55 +02:00 committed by GitHub
parent a25527a223
commit 9b6f1de8b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
123 changed files with 1898 additions and 1663 deletions

View File

@ -46,12 +46,12 @@ sidebar_label: API
## Enumerations ## Enumerations
| Enumeration | Description | | Enumeration | Description |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- | | --------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [BrowserContextEmittedEvents](./puppeteer.browsercontextemittedevents.md) | | | [BrowserContextEvent](./puppeteer.browsercontextevent.md) | |
| [BrowserEmittedEvents](./puppeteer.browseremittedevents.md) | All the events a [browser instance](./puppeteer.browser.md) may emit. | | [BrowserEvent](./puppeteer.browserevent.md) | All the events a [browser instance](./puppeteer.browser.md) may emit. |
| [InterceptResolutionAction](./puppeteer.interceptresolutionaction.md) | | | [InterceptResolutionAction](./puppeteer.interceptresolutionaction.md) | |
| [LocatorEmittedEvents](./puppeteer.locatoremittedevents.md) | All the events that a locator instance may emit. | | [LocatorEvent](./puppeteer.locatorevent.md) | All the events that a locator instance may emit. |
| [PageEmittedEvents](./puppeteer.pageemittedevents.md) | All the events that a page instance may emit. | | [PageEvent](./puppeteer.pageevent.md) | All the events that a page instance may emit. |
| [TargetType](./puppeteer.targettype.md) | | | [TargetType](./puppeteer.targettype.md) | |
## Functions ## Functions
@ -66,14 +66,17 @@ sidebar_label: API
## Interfaces ## Interfaces
| Interface | Description | | Interface | Description |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ActionOptions](./puppeteer.actionoptions.md) | | | [ActionOptions](./puppeteer.actionoptions.md) | |
| [AutofillData](./puppeteer.autofilldata.md) | | | [AutofillData](./puppeteer.autofilldata.md) | |
| [BoundingBox](./puppeteer.boundingbox.md) | | | [BoundingBox](./puppeteer.boundingbox.md) | |
| [BoxModel](./puppeteer.boxmodel.md) | | | [BoxModel](./puppeteer.boxmodel.md) | |
| [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) | Generic browser options that can be passed when launching any browser or when connecting to an existing browser instance. | | [BrowserConnectOptions](./puppeteer.browserconnectoptions.md) | Generic browser options that can be passed when launching any browser or when connecting to an existing browser instance. |
| [BrowserContextEvents](./puppeteer.browsercontextevents.md) | |
| [BrowserContextOptions](./puppeteer.browsercontextoptions.md) | BrowserContext options. | | [BrowserContextOptions](./puppeteer.browsercontextoptions.md) | BrowserContext options. |
| [BrowserEvents](./puppeteer.browserevents.md) | |
| [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) | Launcher options that only apply to Chrome. | | [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) | Launcher options that only apply to Chrome. |
| [CDPSessionEvents](./puppeteer.cdpsessionevents.md) | |
| [ClickOptions](./puppeteer.clickoptions.md) | | | [ClickOptions](./puppeteer.clickoptions.md) | |
| [CommonEventEmitter](./puppeteer.commoneventemitter.md) | | | [CommonEventEmitter](./puppeteer.commoneventemitter.md) | |
| [Configuration](./puppeteer.configuration.md) | <p>Defines options to configure Puppeteer's behavior during installation and runtime.</p><p>See individual properties for more information.</p> | | [Configuration](./puppeteer.configuration.md) | <p>Defines options to configure Puppeteer's behavior during installation and runtime.</p><p>See individual properties for more information.</p> |
@ -88,6 +91,7 @@ sidebar_label: API
| [Device](./puppeteer.device.md) | | | [Device](./puppeteer.device.md) | |
| [FrameAddScriptTagOptions](./puppeteer.frameaddscripttagoptions.md) | | | [FrameAddScriptTagOptions](./puppeteer.frameaddscripttagoptions.md) | |
| [FrameAddStyleTagOptions](./puppeteer.frameaddstyletagoptions.md) | | | [FrameAddStyleTagOptions](./puppeteer.frameaddstyletagoptions.md) | |
| [FrameEvents](./puppeteer.frameevents.md) | |
| [FrameWaitForFunctionOptions](./puppeteer.framewaitforfunctionoptions.md) | | | [FrameWaitForFunctionOptions](./puppeteer.framewaitforfunctionoptions.md) | |
| [GeolocationOptions](./puppeteer.geolocationoptions.md) | | | [GeolocationOptions](./puppeteer.geolocationoptions.md) | |
| [InterceptResolutionState](./puppeteer.interceptresolutionstate.md) | | | [InterceptResolutionState](./puppeteer.interceptresolutionstate.md) | |
@ -97,7 +101,7 @@ sidebar_label: API
| [KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md) | | | [KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md) | |
| [KeyDownOptions](./puppeteer.keydownoptions.md) | | | [KeyDownOptions](./puppeteer.keydownoptions.md) | |
| [LaunchOptions](./puppeteer.launchoptions.md) | Generic launch options that can be passed when launching any browser. | | [LaunchOptions](./puppeteer.launchoptions.md) | Generic launch options that can be passed when launching any browser. |
| [LocatorEventObject](./puppeteer.locatoreventobject.md) | | | [LocatorEvents](./puppeteer.locatorevents.md) | |
| [LocatorOptions](./puppeteer.locatoroptions.md) | | | [LocatorOptions](./puppeteer.locatoroptions.md) | |
| [LocatorScrollOptions](./puppeteer.locatorscrolloptions.md) | | | [LocatorScrollOptions](./puppeteer.locatorscrolloptions.md) | |
| [MediaFeature](./puppeteer.mediafeature.md) | | | [MediaFeature](./puppeteer.mediafeature.md) | |
@ -110,7 +114,7 @@ sidebar_label: API
| [NetworkConditions](./puppeteer.networkconditions.md) | | | [NetworkConditions](./puppeteer.networkconditions.md) | |
| [NewDocumentScriptEvaluation](./puppeteer.newdocumentscriptevaluation.md) | | | [NewDocumentScriptEvaluation](./puppeteer.newdocumentscriptevaluation.md) | |
| [Offset](./puppeteer.offset.md) | | | [Offset](./puppeteer.offset.md) | |
| [PageEventObject](./puppeteer.pageeventobject.md) | <p>Denotes the objects received by callback functions for page events.</p><p>See [PageEmittedEvents](./puppeteer.pageemittedevents.md) for more detail on the events and when they are emitted.</p> | | [PageEvents](./puppeteer.pageevents.md) | <p>Denotes the objects received by callback functions for page events.</p><p>See [PageEvent](./puppeteer.pageevent.md) for more detail on the events and when they are emitted.</p> |
| [PDFMargin](./puppeteer.pdfmargin.md) | | | [PDFMargin](./puppeteer.pdfmargin.md) | |
| [PDFOptions](./puppeteer.pdfoptions.md) | Valid options to configure PDF generation via [Page.pdf()](./puppeteer.page.pdf.md). | | [PDFOptions](./puppeteer.pdfoptions.md) | Valid options to configure PDF generation via [Page.pdf()](./puppeteer.page.pdf.md). |
| [Point](./puppeteer.point.md) | | | [Point](./puppeteer.point.md) | |
@ -155,13 +159,14 @@ sidebar_label: API
| [Awaitable](./puppeteer.awaitable.md) | | | [Awaitable](./puppeteer.awaitable.md) | |
| [AwaitableIterable](./puppeteer.awaitableiterable.md) | | | [AwaitableIterable](./puppeteer.awaitableiterable.md) | |
| [AwaitedLocator](./puppeteer.awaitedlocator.md) | | | [AwaitedLocator](./puppeteer.awaitedlocator.md) | |
| [CDPEvents](./puppeteer.cdpevents.md) | |
| [ChromeReleaseChannel](./puppeteer.chromereleasechannel.md) | | | [ChromeReleaseChannel](./puppeteer.chromereleasechannel.md) | |
| [ConsoleMessageType](./puppeteer.consolemessagetype.md) | The supported types for console messages. | | [ConsoleMessageType](./puppeteer.consolemessagetype.md) | The supported types for console messages. |
| [ElementFor](./puppeteer.elementfor.md) | | | [ElementFor](./puppeteer.elementfor.md) | |
| [ErrorCode](./puppeteer.errorcode.md) | | | [ErrorCode](./puppeteer.errorcode.md) | |
| [EvaluateFunc](./puppeteer.evaluatefunc.md) | | | [EvaluateFunc](./puppeteer.evaluatefunc.md) | |
| [EvaluateFuncWith](./puppeteer.evaluatefuncwith.md) | | | [EvaluateFuncWith](./puppeteer.evaluatefuncwith.md) | |
| [EventType](./puppeteer.eventtype.md) | | | [EventsWithWildcard](./puppeteer.eventswithwildcard.md) | |
| [ExperimentsConfiguration](./puppeteer.experimentsconfiguration.md) | <p>Defines experiment options for Puppeteer.</p><p>See individual properties for more information.</p> | | [ExperimentsConfiguration](./puppeteer.experimentsconfiguration.md) | <p>Defines experiment options for Puppeteer.</p><p>See individual properties for more information.</p> |
| [FlattenHandle](./puppeteer.flattenhandle.md) | | | [FlattenHandle](./puppeteer.flattenhandle.md) | |
| [HandleFor](./puppeteer.handlefor.md) | | | [HandleFor](./puppeteer.handlefor.md) | |

View File

@ -9,16 +9,16 @@ A Browser is created when Puppeteer connects to a browser instance, either throu
#### Signature: #### Signature:
```typescript ```typescript
export declare class Browser extends EventEmitter implements AsyncDisposable, Disposable export declare class Browser extends EventEmitter<BrowserEvents> implements AsyncDisposable, Disposable
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[BrowserEvents](./puppeteer.browserevents.md)&gt;
**Implements:** AsyncDisposable, Disposable **Implements:** AsyncDisposable, Disposable
## Remarks ## Remarks
The Browser class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [BrowserEmittedEvents](./puppeteer.browseremittedevents.md) enum. The Browser class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [BrowserEvent](./puppeteer.browserevent.md) enum.
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `Browser` 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 `Browser` class.

View File

@ -9,14 +9,14 @@ BrowserContexts provide a way to operate multiple independent browser sessions.
#### Signature: #### Signature:
```typescript ```typescript
export declare class BrowserContext extends EventEmitter export declare class BrowserContext extends EventEmitter<BrowserContextEvents>
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[BrowserContextEvents](./puppeteer.browsercontextevents.md)&gt;
## Remarks ## Remarks
The Browser class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [BrowserContextEmittedEvents](./puppeteer.browsercontextemittedevents.md) enum. The Browser class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [BrowserContextEvents](./puppeteer.browsercontextevents.md) enum.
If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser context. If a page opens another page, e.g. with a `window.open` call, the popup will belong to the parent page's browser context.

View File

@ -1,13 +1,13 @@
--- ---
sidebar_label: BrowserContextEmittedEvents sidebar_label: BrowserContextEvent
--- ---
# BrowserContextEmittedEvents enum # BrowserContextEvent enum
#### Signature: #### Signature:
```typescript ```typescript
export declare const enum BrowserContextEmittedEvents export declare const enum BrowserContextEvent
``` ```
## Enumeration Members ## Enumeration Members

View File

@ -0,0 +1,21 @@
---
sidebar_label: BrowserContextEvents
---
# BrowserContextEvents interface
#### Signature:
```typescript
export interface BrowserContextEvents extends Record<EventType, unknown>
```
**Extends:** Record&lt;EventType, unknown&gt;
## Properties
| Property | Modifiers | Type | Description | Default |
| --------------- | --------- | ------------------------------- | ----------- | ------- |
| targetchanged | | [Target](./puppeteer.target.md) | | |
| targetcreated | | [Target](./puppeteer.target.md) | | |
| targetdestroyed | | [Target](./puppeteer.target.md) | | |

View File

@ -1,15 +1,15 @@
--- ---
sidebar_label: BrowserEmittedEvents sidebar_label: BrowserEvent
--- ---
# BrowserEmittedEvents enum # BrowserEvent enum
All the events a [browser instance](./puppeteer.browser.md) may emit. All the events a [browser instance](./puppeteer.browser.md) may emit.
#### Signature: #### Signature:
```typescript ```typescript
export declare const enum BrowserEmittedEvents export declare const enum BrowserEvent
``` ```
## Enumeration Members ## Enumeration Members

View File

@ -0,0 +1,22 @@
---
sidebar_label: BrowserEvents
---
# BrowserEvents interface
#### Signature:
```typescript
export interface BrowserEvents extends Record<EventType, unknown>
```
**Extends:** Record&lt;EventType, unknown&gt;
## Properties
| Property | Modifiers | Type | Description | Default |
| --------------- | --------- | ------------------------------- | ----------- | ------- |
| disconnected | | undefined | | |
| targetchanged | | [Target](./puppeteer.target.md) | | |
| targetcreated | | [Target](./puppeteer.target.md) | | |
| targetdestroyed | | [Target](./puppeteer.target.md) | | |

View File

@ -0,0 +1,13 @@
---
sidebar_label: CDPEvents
---
# CDPEvents type
#### Signature:
```typescript
export type CDPEvents = {
[Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0];
};
```

View File

@ -9,10 +9,10 @@ The `CDPSession` instances are used to talk raw Chrome Devtools Protocol.
#### Signature: #### Signature:
```typescript ```typescript
export declare class CDPSession extends EventEmitter export declare abstract class CDPSession extends EventEmitter<CDPSessionEvents>
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[CDPSessionEvents](./puppeteer.cdpsessionevents.md)&gt;
## Remarks ## Remarks

View File

@ -0,0 +1,13 @@
---
sidebar_label: CDPSessionEvents
---
# CDPSessionEvents interface
#### Signature:
```typescript
export interface CDPSessionEvents extends CDPEvents, Record<EventType, unknown>
```
**Extends:** [CDPEvents](./puppeteer.cdpevents.md), Record&lt;EventType, unknown&gt;

View File

@ -8,16 +8,19 @@ sidebar_label: CommonEventEmitter.addListener
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
addListener(event: EventType, handler: Handler): this; addListener<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- | | --------- | ------------------------------------------------------ | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md) | | | handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | |
**Returns:** **Returns:**

View File

@ -8,16 +8,16 @@ sidebar_label: CommonEventEmitter.emit
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
emit(event: EventType, eventData?: unknown): boolean; emit<Key extends keyof Events>(type: Key, event: Events[Key]): boolean;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ------------ | | --------- | ------------- | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| eventData | unknown | _(Optional)_ | | event | Events\[Key\] | |
**Returns:** **Returns:**

View File

@ -8,15 +8,15 @@ sidebar_label: CommonEventEmitter.listenerCount
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
listenerCount(event: string): number; listenerCount(event: keyof Events): number;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------ | ----------- | | --------- | ------------ | ----------- |
| event | string | | | event | keyof Events | |
**Returns:** **Returns:**

View File

@ -7,18 +7,18 @@ sidebar_label: CommonEventEmitter
#### Signature: #### Signature:
```typescript ```typescript
export interface CommonEventEmitter export interface CommonEventEmitter<Events extends Record<EventType, unknown>>
``` ```
## Methods ## Methods
| Method | Description | | Method | Description |
| ---------------------------------------------------------------------------------- | ----------- | | --------------------------------------------------------------------------------- | ----------- |
| [addListener(event, handler)](./puppeteer.commoneventemitter.addlistener.md) | | | [addListener(type, handler)](./puppeteer.commoneventemitter.addlistener.md) | |
| [emit(event, eventData)](./puppeteer.commoneventemitter.emit.md) | | | [emit(type, event)](./puppeteer.commoneventemitter.emit.md) | |
| [listenerCount(event)](./puppeteer.commoneventemitter.listenercount.md) | | | [listenerCount(event)](./puppeteer.commoneventemitter.listenercount.md) | |
| [off(event, handler)](./puppeteer.commoneventemitter.off.md) | | | [off(type, handler)](./puppeteer.commoneventemitter.off.md) | |
| [on(event, handler)](./puppeteer.commoneventemitter.on.md) | | | [on(type, handler)](./puppeteer.commoneventemitter.on.md) | |
| [once(event, handler)](./puppeteer.commoneventemitter.once.md) | | | [once(type, handler)](./puppeteer.commoneventemitter.once.md) | |
| [removeAllListeners(event)](./puppeteer.commoneventemitter.removealllisteners.md) | | | [removeAllListeners(event)](./puppeteer.commoneventemitter.removealllisteners.md) | |
| [removeListener(event, handler)](./puppeteer.commoneventemitter.removelistener.md) | | | [removeListener(type, handler)](./puppeteer.commoneventemitter.removelistener.md) | |

View File

@ -8,16 +8,19 @@ sidebar_label: CommonEventEmitter.off
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
off(event: EventType, handler: Handler): this; off<Key extends keyof Events>(
type: Key,
handler?: Handler<Events[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- | | --------- | ------------------------------------------------------ | ------------ |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md) | | | handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | _(Optional)_ |
**Returns:** **Returns:**

View File

@ -8,16 +8,16 @@ sidebar_label: CommonEventEmitter.on
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
on(event: EventType, handler: Handler): this; on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- | | --------- | ------------------------------------------------------ | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md) | | | handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | |
**Returns:** **Returns:**

View File

@ -8,16 +8,19 @@ sidebar_label: CommonEventEmitter.once
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
once(event: EventType, handler: Handler): this; once<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- | | --------- | ------------------------------------------------------ | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md) | | | handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | |
**Returns:** **Returns:**

View File

@ -8,15 +8,15 @@ sidebar_label: CommonEventEmitter.removeAllListeners
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
removeAllListeners(event?: EventType): this; removeAllListeners(event?: keyof Events): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ------------ | | --------- | ------------ | ------------ |
| event | [EventType](./puppeteer.eventtype.md) | _(Optional)_ | | event | keyof Events | _(Optional)_ |
**Returns:** **Returns:**

View File

@ -8,16 +8,19 @@ sidebar_label: CommonEventEmitter.removeListener
```typescript ```typescript
interface CommonEventEmitter { interface CommonEventEmitter {
removeListener(event: EventType, handler: Handler): this; removeListener<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- | | --------- | ------------------------------------------------------ | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md) | | | handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | |
**Returns:** **Returns:**

View File

@ -7,10 +7,10 @@ sidebar_label: Connection
#### Signature: #### Signature:
```typescript ```typescript
export declare class Connection extends EventEmitter export declare class Connection extends EventEmitter<CDPSessionEvents>
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[CDPSessionEvents](./puppeteer.cdpsessionevents.md)&gt;
## Constructors ## Constructors

View File

@ -14,16 +14,19 @@ Add an event listener.
```typescript ```typescript
class EventEmitter { class EventEmitter {
addListener(event: EventType, handler: Handler<any>): this; addListener<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------- | ----------- | | --------- | --------------------------------------------------------------------------------------------------------------------- | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md)&lt;any&gt; | | | handler | [Handler](./puppeteer.handler.md)&lt;[EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;\[Key\]&gt; | |
**Returns:** **Returns:**

View File

@ -10,16 +10,19 @@ Emit an event and call any associated listeners.
```typescript ```typescript
class EventEmitter { class EventEmitter {
emit(event: EventType, eventData?: unknown): boolean; emit<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
event: EventsWithWildcard<Events>[Key]
): boolean;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ------------------------------------------------------- | | --------- | ---------------------------------------------------------------------------- | ---------------------------- |
| event | [EventType](./puppeteer.eventtype.md) | the event you'd like to emit | | type | Key | the event you'd like to emit |
| eventData | unknown | _(Optional)_ any data you'd like to emit with the event | | event | [EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;\[Key\] | |
**Returns:** **Returns:**

View File

@ -10,15 +10,15 @@ Gets the number of listeners for a given event.
```typescript ```typescript
class EventEmitter { class EventEmitter {
listenerCount(event: EventType): number; listenerCount(type: keyof EventsWithWildcard<Events>): number;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | --------------------------------------- | | --------- | --------------------------------------------------------------------------- | --------------------------------------- |
| event | [EventType](./puppeteer.eventtype.md) | the event to get the listener count for | | type | keyof [EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt; | the event to get the listener count for |
**Returns:** **Returns:**

View File

@ -9,10 +9,10 @@ The EventEmitter class that many Puppeteer classes extend.
#### Signature: #### Signature:
```typescript ```typescript
export declare class EventEmitter implements CommonEventEmitter export declare class EventEmitter<Events extends Record<EventType, unknown>> implements CommonEventEmitter<EventsWithWildcard<Events>>
``` ```
**Implements:** [CommonEventEmitter](./puppeteer.commoneventemitter.md) **Implements:** [CommonEventEmitter](./puppeteer.commoneventemitter.md)&lt;[EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;&gt;
## Remarks ## Remarks
@ -23,12 +23,12 @@ The constructor for this class is marked as internal. Third-party code should no
## Methods ## Methods
| Method | Modifiers | Description | | Method | Modifiers | Description |
| ---------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ | | --------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| [addListener(event, handler)](./puppeteer.eventemitter.addlistener.md) | | Add an event listener. | | [addListener(type, handler)](./puppeteer.eventemitter.addlistener.md) | | Add an event listener. |
| [emit(event, eventData)](./puppeteer.eventemitter.emit.md) | | Emit an event and call any associated listeners. | | [emit(type, event)](./puppeteer.eventemitter.emit.md) | | Emit an event and call any associated listeners. |
| [listenerCount(event)](./puppeteer.eventemitter.listenercount.md) | | Gets the number of listeners for a given event. | | [listenerCount(type)](./puppeteer.eventemitter.listenercount.md) | | Gets the number of listeners for a given event. |
| [off(event, handler)](./puppeteer.eventemitter.off.md) | | Remove an event listener from firing. | | [off(type, handler)](./puppeteer.eventemitter.off.md) | | Remove an event listener from firing. |
| [on(event, handler)](./puppeteer.eventemitter.on.md) | | Bind an event listener to fire when an event occurs. | | [on(type, handler)](./puppeteer.eventemitter.on.md) | | Bind an event listener to fire when an event occurs. |
| [once(event, handler)](./puppeteer.eventemitter.once.md) | | Like <code>on</code> but the listener will only be fired once and then it will be removed. | | [once(type, handler)](./puppeteer.eventemitter.once.md) | | Like <code>on</code> but the listener will only be fired once and then it will be removed. |
| [removeAllListeners(event)](./puppeteer.eventemitter.removealllisteners.md) | | Removes all listeners. If given an event argument, it will remove only listeners for that event. | | [removeAllListeners(type)](./puppeteer.eventemitter.removealllisteners.md) | | Removes all listeners. If given an event argument, it will remove only listeners for that event. |
| [removeListener(event, handler)](./puppeteer.eventemitter.removelistener.md) | | Remove an event listener. | | [removeListener(type, handler)](./puppeteer.eventemitter.removelistener.md) | | Remove an event listener. |

View File

@ -10,16 +10,19 @@ Remove an event listener from firing.
```typescript ```typescript
class EventEmitter { class EventEmitter {
off(event: EventType, handler: Handler<any>): this; off<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler?: Handler<EventsWithWildcard<Events>[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------- | ----------------------------------------------- | | --------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| event | [EventType](./puppeteer.eventtype.md) | the event type you'd like to stop listening to. | | type | Key | the event type you'd like to stop listening to. |
| handler | [Handler](./puppeteer.handler.md)&lt;any&gt; | the function that should be removed. | | handler | [Handler](./puppeteer.handler.md)&lt;[EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;\[Key\]&gt; | _(Optional)_ the function that should be removed. |
**Returns:** **Returns:**

View File

@ -10,16 +10,19 @@ Bind an event listener to fire when an event occurs.
```typescript ```typescript
class EventEmitter { class EventEmitter {
on(event: EventType, handler: Handler<any>): this; on<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------- | ------------------------------------------------------------------ | | --------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| event | [EventType](./puppeteer.eventtype.md) | the event type you'd like to listen to. Can be a string or symbol. | | type | Key | the event type you'd like to listen to. Can be a string or symbol. |
| handler | [Handler](./puppeteer.handler.md)&lt;any&gt; | the function to be called when the event occurs. | | handler | [Handler](./puppeteer.handler.md)&lt;[EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;\[Key\]&gt; | the function to be called when the event occurs. |
**Returns:** **Returns:**

View File

@ -10,16 +10,19 @@ Like `on` but the listener will only be fired once and then it will be removed.
```typescript ```typescript
class EventEmitter { class EventEmitter {
once(event: EventType, handler: Handler<any>): this; once<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------- | ------------------------------------------------- | | --------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| event | [EventType](./puppeteer.eventtype.md) | the event you'd like to listen to | | type | Key | the event you'd like to listen to |
| handler | [Handler](./puppeteer.handler.md)&lt;any&gt; | the handler function to run when the event occurs | | handler | [Handler](./puppeteer.handler.md)&lt;[EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;\[Key\]&gt; | the handler function to run when the event occurs |
**Returns:** **Returns:**

View File

@ -10,15 +10,15 @@ Removes all listeners. If given an event argument, it will remove only listeners
```typescript ```typescript
class EventEmitter { class EventEmitter {
removeAllListeners(event?: EventType): this; removeAllListeners(type?: keyof EventsWithWildcard<Events>): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------------------------------------- | ----------------------------------------------- | | --------- | --------------------------------------------------------------------------- | ----------------------------------------------- |
| event | [EventType](./puppeteer.eventtype.md) | _(Optional)_ the event to remove listeners for. | | type | keyof [EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt; | _(Optional)_ the event to remove listeners for. |
**Returns:** **Returns:**

View File

@ -14,16 +14,19 @@ Remove an event listener.
```typescript ```typescript
class EventEmitter { class EventEmitter {
removeListener(event: EventType, handler: Handler<any>): this; removeListener<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this;
} }
``` ```
## Parameters ## Parameters
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | -------------------------------------------- | ----------- | | --------- | --------------------------------------------------------------------------------------------------------------------- | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | | | type | Key | |
| handler | [Handler](./puppeteer.handler.md)&lt;any&gt; | | | handler | [Handler](./puppeteer.handler.md)&lt;[EventsWithWildcard](./puppeteer.eventswithwildcard.md)&lt;Events&gt;\[Key\]&gt; | |
**Returns:** **Returns:**

View File

@ -0,0 +1,14 @@
---
sidebar_label: EventsWithWildcard
---
# EventsWithWildcard type
#### Signature:
```typescript
export type EventsWithWildcard<Events extends Record<EventType, unknown>> =
Events & {
'*': Events[keyof Events];
};
```

View File

@ -1,11 +0,0 @@
---
sidebar_label: EventType
---
# EventType type
#### Signature:
```typescript
export type EventType = string | symbol;
```

View File

@ -11,16 +11,16 @@ To understand frames, you can think of frames as `<iframe>` elements. Just like
#### Signature: #### Signature:
```typescript ```typescript
export declare abstract class Frame extends EventEmitter export declare abstract class Frame extends EventEmitter<FrameEvents>
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[FrameEvents](./puppeteer.frameevents.md)&gt;
## Remarks ## Remarks
Frame lifecycles are controlled by three events that are all dispatched on the parent [page](./puppeteer.frame.page.md): Frame lifecycles are controlled by three events that are all dispatched on the parent [page](./puppeteer.frame.page.md):
- [PageEmittedEvents.FrameAttached](./puppeteer.pageemittedevents.md) - [PageEmittedEvents.FrameNavigated](./puppeteer.pageemittedevents.md) - [PageEmittedEvents.FrameDetached](./puppeteer.pageemittedevents.md) - [PageEvent.FrameAttached](./puppeteer.pageevent.md) - [PageEvent.FrameNavigated](./puppeteer.pageevent.md) - [PageEvent.FrameDetached](./puppeteer.pageevent.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 `Frame` 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 `Frame` class.

View File

@ -0,0 +1,13 @@
---
sidebar_label: FrameEvents
---
# FrameEvents interface
#### Signature:
```typescript
export interface FrameEvents extends Record<EventType, unknown>
```
**Extends:** Record&lt;EventType, unknown&gt;

View File

@ -9,10 +9,10 @@ Locators describe a strategy of locating objects and performing an action on the
#### Signature: #### Signature:
```typescript ```typescript
export declare abstract class Locator<T> extends EventEmitter export declare abstract class Locator<T> extends EventEmitter<LocatorEvents>
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[LocatorEvents](./puppeteer.locatorevents.md)&gt;
## Properties ## Properties
@ -31,9 +31,6 @@ export declare abstract class Locator<T> extends EventEmitter
| [filter(predicate)](./puppeteer.locator.filter.md) | | <p>Creates an expectation that is evaluated against located values.</p><p>If the expectations do not match, then the locator will retry.</p> | | [filter(predicate)](./puppeteer.locator.filter.md) | | <p>Creates an expectation that is evaluated against located values.</p><p>If the expectations do not match, then the locator will retry.</p> |
| [hover(this, options)](./puppeteer.locator.hover.md) | | | | [hover(this, options)](./puppeteer.locator.hover.md) | | |
| [map(mapper)](./puppeteer.locator.map.md) | | Maps the locator using the provided mapper. | | [map(mapper)](./puppeteer.locator.map.md) | | Maps the locator using the provided mapper. |
| [off(eventName, handler)](./puppeteer.locator.off.md) | | |
| [on(eventName, handler)](./puppeteer.locator.on.md) | | |
| [once(eventName, handler)](./puppeteer.locator.once.md) | | |
| [race(locators)](./puppeteer.locator.race.md) | <code>static</code> | Creates a race between multiple locators but ensures that only a single one acts. | | [race(locators)](./puppeteer.locator.race.md) | <code>static</code> | Creates a race between multiple locators but ensures that only a single one acts. |
| [scroll(this, options)](./puppeteer.locator.scroll.md) | | | | [scroll(this, options)](./puppeteer.locator.scroll.md) | | |
| [setEnsureElementIsInTheViewport(this, value)](./puppeteer.locator.setensureelementisintheviewport.md) | | | | [setEnsureElementIsInTheViewport(this, value)](./puppeteer.locator.setensureelementisintheviewport.md) | | |

View File

@ -1,27 +0,0 @@
---
sidebar_label: Locator.off
---
# Locator.off() method
#### Signature:
```typescript
class Locator {
off<K extends keyof LocatorEventObject>(
eventName: K,
handler: (event: LocatorEventObject[K]) => void
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------- | ----------- |
| eventName | K | |
| handler | (event: [LocatorEventObject](./puppeteer.locatoreventobject.md)\[K\]) =&gt; void | |
**Returns:**
this

View File

@ -1,27 +0,0 @@
---
sidebar_label: Locator.on
---
# Locator.on() method
#### Signature:
```typescript
class Locator {
on<K extends keyof LocatorEventObject>(
eventName: K,
handler: (event: LocatorEventObject[K]) => void
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------- | ----------- |
| eventName | K | |
| handler | (event: [LocatorEventObject](./puppeteer.locatoreventobject.md)\[K\]) =&gt; void | |
**Returns:**
this

View File

@ -1,27 +0,0 @@
---
sidebar_label: Locator.once
---
# Locator.once() method
#### Signature:
```typescript
class Locator {
once<K extends keyof LocatorEventObject>(
eventName: K,
handler: (event: LocatorEventObject[K]) => void
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------------- | ----------- |
| eventName | K | |
| handler | (event: [LocatorEventObject](./puppeteer.locatoreventobject.md)\[K\]) =&gt; void | |
**Returns:**
this

View File

@ -1,15 +1,15 @@
--- ---
sidebar_label: LocatorEmittedEvents sidebar_label: LocatorEvent
--- ---
# LocatorEmittedEvents enum # LocatorEvent enum
All the events that a locator instance may emit. All the events that a locator instance may emit.
#### Signature: #### Signature:
```typescript ```typescript
export declare enum LocatorEmittedEvents export declare enum LocatorEvent
``` ```
## Enumeration Members ## Enumeration Members

View File

@ -1,17 +0,0 @@
---
sidebar_label: LocatorEventObject
---
# LocatorEventObject interface
#### Signature:
```typescript
export interface LocatorEventObject
```
## Properties
| Property | Modifiers | Type | Description | Default |
| -------- | --------- | ----- | ----------- | ------- |
| action | | never | | |

View File

@ -0,0 +1,19 @@
---
sidebar_label: LocatorEvents
---
# LocatorEvents interface
#### Signature:
```typescript
export interface LocatorEvents extends Record<EventType, unknown>
```
**Extends:** Record&lt;EventType, unknown&gt;
## Properties
| Property | Modifiers | Type | Description | Default |
| -------- | --------- | --------- | ----------- | ------- |
| action | | undefined | | |

View File

@ -15,10 +15,10 @@ One Browser instance might have multiple Page instances.
#### Signature: #### Signature:
```typescript ```typescript
export declare abstract class Page extends EventEmitter implements AsyncDisposable, Disposable export declare abstract class Page extends EventEmitter<PageEvents> implements AsyncDisposable, Disposable
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;[PageEvents](./puppeteer.pageevents.md)&gt;
**Implements:** AsyncDisposable, Disposable **Implements:** AsyncDisposable, Disposable
@ -42,7 +42,7 @@ import puppeteer from 'puppeteer';
})(); })();
``` ```
The Page class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [PageEmittedEvents](./puppeteer.pageemittedevents.md) enum. The Page class extends from Puppeteer's [EventEmitter](./puppeteer.eventemitter.md) class and will emit various events which are documented in the [PageEvent](./puppeteer.pageevent.md) enum.
## Example 2 ## Example 2
@ -52,7 +52,7 @@ This example logs a message for a single page `load` event:
page.once('load', () => console.log('Page loaded!')); page.once('load', () => console.log('Page loaded!'));
``` ```
To unsubscribe from events use the [Page.off()](./puppeteer.page.off.md) method: To unsubscribe from events use the [EventEmitter.off()](./puppeteer.eventemitter.off.md) method:
```ts ```ts
function logRequest(interceptedRequest) { function logRequest(interceptedRequest) {
@ -126,9 +126,6 @@ page.off('request', logRequest);
| [locator(func)](./puppeteer.page.locator_1.md) | | Creates a locator for the provided function. See [Locator](./puppeteer.locator.md) for details and supported actions. | | [locator(func)](./puppeteer.page.locator_1.md) | | Creates a locator for the provided function. See [Locator](./puppeteer.locator.md) for details and supported actions. |
| [mainFrame()](./puppeteer.page.mainframe.md) | | The page's main frame. | | [mainFrame()](./puppeteer.page.mainframe.md) | | The page's main frame. |
| [metrics()](./puppeteer.page.metrics.md) | | Object containing metrics as key/value pairs. | | [metrics()](./puppeteer.page.metrics.md) | | Object containing metrics as key/value pairs. |
| [off(eventName, handler)](./puppeteer.page.off.md) | | |
| [on(eventName, handler)](./puppeteer.page.on.md) | | <p>Listen to page events.</p><p>:::note</p><p>This method exists to define event typings and handle proper wireup of cooperative request interception. Actual event listening and dispatching is delegated to [EventEmitter](./puppeteer.eventemitter.md).</p><p>:::</p> |
| [once(eventName, handler)](./puppeteer.page.once.md) | | |
| [pdf(options)](./puppeteer.page.pdf.md) | | Generates a PDF of the page with the <code>print</code> CSS media type. | | [pdf(options)](./puppeteer.page.pdf.md) | | Generates a PDF of the page with the <code>print</code> CSS media type. |
| [queryObjects(prototypeHandle)](./puppeteer.page.queryobjects.md) | | This method iterates the JavaScript heap and finds all objects with the given prototype. | | [queryObjects(prototypeHandle)](./puppeteer.page.queryobjects.md) | | This method iterates the JavaScript heap and finds all objects with the given prototype. |
| [reload(options)](./puppeteer.page.reload.md) | | | | [reload(options)](./puppeteer.page.reload.md) | | |

View File

@ -1,27 +0,0 @@
---
sidebar_label: Page.off
---
# Page.off() method
#### Signature:
```typescript
class Page {
off<K extends keyof PageEventObject>(
eventName: K,
handler: (event: PageEventObject[K]) => void
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------- | ----------- |
| eventName | K | |
| handler | (event: [PageEventObject](./puppeteer.pageeventobject.md)\[K\]) =&gt; void | |
**Returns:**
this

View File

@ -1,35 +0,0 @@
---
sidebar_label: Page.on
---
# Page.on() method
Listen to page events.
:::note
This method exists to define event typings and handle proper wireup of cooperative request interception. Actual event listening and dispatching is delegated to [EventEmitter](./puppeteer.eventemitter.md).
:::
#### Signature:
```typescript
class Page {
on<K extends keyof PageEventObject>(
eventName: K,
handler: (event: PageEventObject[K]) => void
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------- | ----------- |
| eventName | K | |
| handler | (event: [PageEventObject](./puppeteer.pageeventobject.md)\[K\]) =&gt; void | |
**Returns:**
this

View File

@ -1,27 +0,0 @@
---
sidebar_label: Page.once
---
# Page.once() method
#### Signature:
```typescript
class Page {
once<K extends keyof PageEventObject>(
eventName: K,
handler: (event: PageEventObject[K]) => void
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | -------------------------------------------------------------------------- | ----------- |
| eventName | K | |
| handler | (event: [PageEventObject](./puppeteer.pageeventobject.md)\[K\]) =&gt; void | |
**Returns:**
this

View File

@ -1,15 +1,15 @@
--- ---
sidebar_label: PageEmittedEvents sidebar_label: PageEvent
--- ---
# PageEmittedEvents enum # PageEvent enum
All the events that a page instance may emit. All the events that a page instance may emit.
#### Signature: #### Signature:
```typescript ```typescript
export declare const enum PageEmittedEvents export declare const enum PageEvent
``` ```
## Enumeration Members ## Enumeration Members

View File

@ -1,35 +1,37 @@
--- ---
sidebar_label: PageEventObject sidebar_label: PageEvents
--- ---
# PageEventObject interface # PageEvents interface
Denotes the objects received by callback functions for page events. Denotes the objects received by callback functions for page events.
See [PageEmittedEvents](./puppeteer.pageemittedevents.md) for more detail on the events and when they are emitted. See [PageEvent](./puppeteer.pageevent.md) for more detail on the events and when they are emitted.
#### Signature: #### Signature:
```typescript ```typescript
export interface PageEventObject export interface PageEvents extends Record<EventType, unknown>
``` ```
**Extends:** Record&lt;EventType, unknown&gt;
## Properties ## Properties
| Property | Modifiers | Type | Description | Default | | Property | Modifiers | Type | Description | Default |
| ---------------------- | --------- | -------------------------------------------------------------- | ----------- | ------- | | ---------------------- | --------- | -------------------------------------------------------------- | ----------- | ------- |
| close | | never | | | | close | | undefined | | |
| console | | [ConsoleMessage](./puppeteer.consolemessage.md) | | | | console | | [ConsoleMessage](./puppeteer.consolemessage.md) | | |
| dialog | | [Dialog](./puppeteer.dialog.md) | | | | dialog | | [Dialog](./puppeteer.dialog.md) | | |
| domcontentloaded | | never | | | | domcontentloaded | | undefined | | |
| error | | Error | | | | error | | Error | | |
| frameattached | | [Frame](./puppeteer.frame.md) | | | | frameattached | | [Frame](./puppeteer.frame.md) | | |
| framedetached | | [Frame](./puppeteer.frame.md) | | | | framedetached | | [Frame](./puppeteer.frame.md) | | |
| framenavigated | | [Frame](./puppeteer.frame.md) | | | | framenavigated | | [Frame](./puppeteer.frame.md) | | |
| load | | never | | | | load | | undefined | | |
| metrics | | { title: string; metrics: [Metrics](./puppeteer.metrics.md); } | | | | metrics | | { title: string; metrics: [Metrics](./puppeteer.metrics.md); } | | |
| pageerror | | Error | | | | pageerror | | Error | | |
| popup | | [Page](./puppeteer.page.md) | | | | popup | | [Page](./puppeteer.page.md) \| null | | |
| request | | [HTTPRequest](./puppeteer.httprequest.md) | | | | request | | [HTTPRequest](./puppeteer.httprequest.md) | | |
| requestfailed | | [HTTPRequest](./puppeteer.httprequest.md) | | | | requestfailed | | [HTTPRequest](./puppeteer.httprequest.md) | | |
| requestfinished | | [HTTPRequest](./puppeteer.httprequest.md) | | | | requestfinished | | [HTTPRequest](./puppeteer.httprequest.md) | | |

View File

@ -9,10 +9,10 @@ This class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web
#### Signature: #### Signature:
```typescript ```typescript
export declare class WebWorker extends EventEmitter export declare class WebWorker extends EventEmitter<Record<EventType, unknown>>
``` ```
**Extends:** [EventEmitter](./puppeteer.eventemitter.md) **Extends:** [EventEmitter](./puppeteer.eventemitter.md)&lt;Record&lt;EventType, unknown&gt;&gt;
## Remarks ## Remarks

32
package-lock.json generated
View File

@ -3434,11 +3434,12 @@
} }
}, },
"node_modules/chromium-bidi": { "node_modules/chromium-bidi": {
"version": "0.4.26", "version": "0.4.27",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.26.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.27.tgz",
"integrity": "sha512-lukBGfogAI4T0y3acc86RaacqgKQve47/8pV2c+Hr1PjcICj2K4OkL3qfX3qrqxxnd4ddurFC0WBA3VCQqYeUQ==", "integrity": "sha512-8Irq0FbKYN8Xmj8M62kta6wk5MyDKeYIFtNavxQ2M3xf2v5MCC4ntf+FxitQu1iHaQvGU6t5O+Nrep0RNNS0EQ==",
"dependencies": { "dependencies": {
"mitt": "3.0.1" "mitt": "3.0.1",
"urlpattern-polyfill": "9.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"devtools-protocol": "*" "devtools-protocol": "*"
@ -10668,6 +10669,11 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/urlpattern-polyfill": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz",
"integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g=="
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"license": "MIT" "license": "MIT"
@ -11108,7 +11114,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@puppeteer/browsers": "1.7.1", "@puppeteer/browsers": "1.7.1",
"chromium-bidi": "0.4.26", "chromium-bidi": "0.4.27",
"cross-fetch": "4.0.0", "cross-fetch": "4.0.0",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1159816", "devtools-protocol": "0.0.1159816",
@ -13450,11 +13456,12 @@
"dev": true "dev": true
}, },
"chromium-bidi": { "chromium-bidi": {
"version": "0.4.26", "version": "0.4.27",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.26.tgz", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.27.tgz",
"integrity": "sha512-lukBGfogAI4T0y3acc86RaacqgKQve47/8pV2c+Hr1PjcICj2K4OkL3qfX3qrqxxnd4ddurFC0WBA3VCQqYeUQ==", "integrity": "sha512-8Irq0FbKYN8Xmj8M62kta6wk5MyDKeYIFtNavxQ2M3xf2v5MCC4ntf+FxitQu1iHaQvGU6t5O+Nrep0RNNS0EQ==",
"requires": { "requires": {
"mitt": "3.0.1" "mitt": "3.0.1",
"urlpattern-polyfill": "9.0.0"
} }
}, },
"ci-info": { "ci-info": {
@ -17241,7 +17248,7 @@
"version": "file:packages/puppeteer-core", "version": "file:packages/puppeteer-core",
"requires": { "requires": {
"@puppeteer/browsers": "1.7.1", "@puppeteer/browsers": "1.7.1",
"chromium-bidi": "0.4.26", "chromium-bidi": "0.4.27",
"cross-fetch": "4.0.0", "cross-fetch": "4.0.0",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1159816", "devtools-protocol": "0.0.1159816",
@ -18455,6 +18462,11 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"urlpattern-polyfill": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-9.0.0.tgz",
"integrity": "sha512-WHN8KDQblxd32odxeIgo83rdVDE2bvdkb86it7bMhYZwWKJz0+O0RK/eZiHYnM+zgt/U7hAHOlCQGfjjvSkw2g=="
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2" "version": "1.0.2"
}, },

View File

@ -141,7 +141,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@puppeteer/browsers": "1.7.1", "@puppeteer/browsers": "1.7.1",
"chromium-bidi": "0.4.26", "chromium-bidi": "0.4.27",
"cross-fetch": "4.0.0", "cross-fetch": "4.0.0",
"debug": "4.3.4", "debug": "4.3.4",
"devtools-protocol": "0.0.1159816", "devtools-protocol": "0.0.1159816",

View File

@ -21,7 +21,7 @@ import {ChildProcess} from 'child_process';
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {Symbol} from '../../third_party/disposablestack/disposablestack.js'; import {Symbol} from '../../third_party/disposablestack/disposablestack.js';
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter, EventType} from '../common/EventEmitter.js';
import {debugError, waitWithTimeout} from '../common/util.js'; import {debugError, waitWithTimeout} from '../common/util.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
@ -130,7 +130,7 @@ export interface WaitForTargetOptions {
* *
* @public * @public
*/ */
export const enum BrowserEmittedEvents { export const enum BrowserEvent {
/** /**
* Emitted when Puppeteer gets disconnected from the browser instance. This * Emitted when Puppeteer gets disconnected from the browser instance. This
* might happen because of one of the following: * might happen because of one of the following:
@ -140,7 +140,6 @@ export const enum BrowserEmittedEvents {
* - The {@link Browser.disconnect | browser.disconnect } method was called. * - The {@link Browser.disconnect | browser.disconnect } method was called.
*/ */
Disconnected = 'disconnected', Disconnected = 'disconnected',
/** /**
* Emitted when the url of a target changes. Contains a {@link Target} instance. * Emitted when the url of a target changes. Contains a {@link Target} instance.
* *
@ -149,7 +148,6 @@ export const enum BrowserEmittedEvents {
* Note that this includes target changes in incognito browser contexts. * Note that this includes target changes in incognito browser contexts.
*/ */
TargetChanged = 'targetchanged', TargetChanged = 'targetchanged',
/** /**
* Emitted when a target is created, for example when a new page is opened by * Emitted when a target is created, for example when a new page is opened by
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open} * {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open}
@ -171,6 +169,31 @@ export const enum BrowserEmittedEvents {
* Note that this includes target destructions in incognito browser contexts. * Note that this includes target destructions in incognito browser contexts.
*/ */
TargetDestroyed = 'targetdestroyed', TargetDestroyed = 'targetdestroyed',
/**
* @internal
*/
TargetDiscovered = 'targetdiscovered',
}
export {
/**
* @deprecated Use {@link BrowserEvent}.
*/
BrowserEvent as BrowserEmittedEvents,
};
/**
* @public
*/
export interface BrowserEvents extends Record<EventType, unknown> {
[BrowserEvent.Disconnected]: undefined;
[BrowserEvent.TargetCreated]: Target;
[BrowserEvent.TargetDestroyed]: Target;
[BrowserEvent.TargetChanged]: Target;
/**
* @internal
*/
[BrowserEvent.TargetDiscovered]: Protocol.Target.TargetInfo;
} }
/** /**
@ -180,7 +203,7 @@ export const enum BrowserEmittedEvents {
* @remarks * @remarks
* *
* The Browser class extends from Puppeteer's {@link EventEmitter} class and will * The Browser class extends from Puppeteer's {@link EventEmitter} class and will
* emit various events which are documented in the {@link BrowserEmittedEvents} enum. * emit various events which are documented in the {@link BrowserEvent} enum.
* *
* @example * @example
* An example of using a {@link Browser} to create a {@link Page}: * An example of using a {@link Browser} to create a {@link Page}:
@ -219,7 +242,7 @@ export const enum BrowserEmittedEvents {
* @public * @public
*/ */
export class Browser export class Browser
extends EventEmitter extends EventEmitter<BrowserEvents>
implements AsyncDisposable, Disposable implements AsyncDisposable, Disposable
{ {
/** /**
@ -389,8 +412,8 @@ export class Browser
const {timeout = 30000} = options; const {timeout = 30000} = options;
const targetDeferred = Deferred.create<Target | PromiseLike<Target>>(); const targetDeferred = Deferred.create<Target | PromiseLike<Target>>();
this.on(BrowserEmittedEvents.TargetCreated, check); this.on(BrowserEvent.TargetCreated, check);
this.on(BrowserEmittedEvents.TargetChanged, check); this.on(BrowserEvent.TargetChanged, check);
try { try {
this.targets().forEach(check); this.targets().forEach(check);
if (!timeout) { if (!timeout) {
@ -402,8 +425,8 @@ export class Browser
timeout timeout
); );
} finally { } finally {
this.off(BrowserEmittedEvents.TargetCreated, check); this.off(BrowserEvent.TargetCreated, check);
this.off(BrowserEmittedEvents.TargetChanged, check); this.off(BrowserEvent.TargetChanged, check);
} }
async function check(target: Target): Promise<void> { async function check(target: Target): Promise<void> {
@ -491,28 +514,3 @@ export class Browser
return this.close(); return this.close();
} }
} }
/**
* @public
*/
export const enum BrowserContextEmittedEvents {
/**
* Emitted when the url of a target inside the browser context changes.
* Contains a {@link Target} instance.
*/
TargetChanged = 'targetchanged',
/**
* Emitted when a target is created within the browser context, for example
* when a new page is opened by
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open}
* or by {@link BrowserContext.newPage | browserContext.newPage}
*
* Contains a {@link Target} instance.
*/
TargetCreated = 'targetcreated',
/**
* Emitted when a target is destroyed within the browser context, for example
* when a page is closed. Contains a {@link Target} instance.
*/
TargetDestroyed = 'targetdestroyed',
}

View File

@ -14,12 +14,54 @@
* limitations under the License. * limitations under the License.
*/ */
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter, EventType} from '../common/EventEmitter.js';
import type {Permission, Browser} from './Browser.js'; import type {Browser, Permission} from './Browser.js';
import {Page} from './Page.js'; import {Page} from './Page.js';
import type {Target} from './Target.js'; import type {Target} from './Target.js';
/**
* @public
*/
export const enum BrowserContextEvent {
/**
* Emitted when the url of a target inside the browser context changes.
* Contains a {@link Target} instance.
*/
TargetChanged = 'targetchanged',
/**
* Emitted when a target is created within the browser context, for example
* when a new page is opened by
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Window/open | window.open}
* or by {@link BrowserContext.newPage | browserContext.newPage}
*
* Contains a {@link Target} instance.
*/
TargetCreated = 'targetcreated',
/**
* Emitted when a target is destroyed within the browser context, for example
* when a page is closed. Contains a {@link Target} instance.
*/
TargetDestroyed = 'targetdestroyed',
}
export {
/**
* @deprecated Use {@link BrowserContextEvent}
*/
BrowserContextEvent as BrowserContextEmittedEvents,
};
/**
* @public
*/
export interface BrowserContextEvents extends Record<EventType, unknown> {
[BrowserContextEvent.TargetChanged]: Target;
[BrowserContextEvent.TargetCreated]: Target;
[BrowserContextEvent.TargetDestroyed]: Target;
}
/** /**
* BrowserContexts provide a way to operate multiple independent browser * BrowserContexts provide a way to operate multiple independent browser
* sessions. When a browser is launched, it has a single BrowserContext used by * sessions. When a browser is launched, it has a single BrowserContext used by
@ -30,7 +72,7 @@ import type {Target} from './Target.js';
* *
* The Browser class extends from Puppeteer's {@link EventEmitter} class and * The Browser class extends from Puppeteer's {@link EventEmitter} class and
* will emit various events which are documented in the * will emit various events which are documented in the
* {@link BrowserContextEmittedEvents} enum. * {@link BrowserContextEvents} enum.
* *
* If a page opens another page, e.g. with a `window.open` call, the popup will * If a page opens another page, e.g. with a `window.open` call, the popup will
* belong to the parent page's browser context. * belong to the parent page's browser context.
@ -55,7 +97,7 @@ import type {Target} from './Target.js';
* @public * @public
*/ */
export class BrowserContext extends EventEmitter { export class BrowserContext extends EventEmitter<BrowserContextEvents> {
/** /**
* @internal * @internal
*/ */

View File

@ -0,0 +1,122 @@
import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import type {Connection} from '../common/Connection.js';
import {EventEmitter, EventType} from '../common/EventEmitter.js';
/**
* @public
*/
export type CDPEvents = {
[Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0];
};
/**
* Internal events that the CDPSession class emits.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace CDPSessionEvent {
export const Disconnected = Symbol('CDPSession.Disconnected');
export const Swapped = Symbol('CDPSession.Swapped');
/**
* Emitted when the session is ready to be configured during the auto-attach
* process. Right after the event is handled, the session will be resumed.
*/
export const Ready = Symbol('CDPSession.Ready');
export const SessionAttached = Symbol('CDPSession.SessionAttached');
export const SessionDetached = Symbol('CDPSession.SessionDetached');
}
/**
* @public
*/
export interface CDPSessionEvents
extends CDPEvents,
Record<EventType, unknown> {
/** @internal */
[CDPSessionEvent.Disconnected]: undefined;
/** @internal */
[CDPSessionEvent.Swapped]: CDPSession;
/** @internal */
[CDPSessionEvent.Ready]: CDPSession;
/** @internal */
[CDPSessionEvent.SessionAttached]: CDPSession;
/** @internal */
[CDPSessionEvent.SessionDetached]: CDPSession;
}
/**
* The `CDPSession` instances are used to talk raw Chrome Devtools Protocol.
*
* @remarks
*
* Protocol methods can be called with {@link CDPSession.send} method and protocol
* events can be subscribed to with `CDPSession.on` method.
*
* Useful links: {@link https://chromedevtools.github.io/devtools-protocol/ | DevTools Protocol Viewer}
* and {@link https://github.com/aslushnikov/getting-started-with-cdp/blob/HEAD/README.md | Getting Started with DevTools Protocol}.
*
* @example
*
* ```ts
* const client = await page.target().createCDPSession();
* await client.send('Animation.enable');
* client.on('Animation.animationCreated', () =>
* console.log('Animation created!')
* );
* const response = await client.send('Animation.getPlaybackRate');
* console.log('playback rate is ' + response.playbackRate);
* await client.send('Animation.setPlaybackRate', {
* playbackRate: response.playbackRate / 2,
* });
* ```
*
* @public
*/
export abstract class CDPSession extends EventEmitter<CDPSessionEvents> {
/**
* @internal
*/
constructor() {
super();
}
connection(): Connection | undefined {
throw new Error('Not implemented');
}
/**
* Parent session in terms of CDP's auto-attach mechanism.
*
* @internal
*/
parentSession(): CDPSession | undefined {
return undefined;
}
send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']>;
send<T extends keyof ProtocolMapping.Commands>(): Promise<
ProtocolMapping.Commands[T]['returnType']
> {
throw new Error('Not implemented');
}
/**
* Detaches the cdpSession from the target. Once detached, the cdpSession object
* won't emit any events and can't be used to send messages.
*/
async detach(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Returns the session's id.
*/
id(): string {
throw new Error('Not implemented');
}
}

View File

@ -14,8 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import {CDPSession} from '../common/Connection.js'; import {CDPSession} from './CDPSession.js';
import {Realm} from './Realm.js'; import {Realm} from './Realm.js';
/** /**

View File

@ -14,12 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
import Protocol from 'devtools-protocol';
import {ClickOptions, ElementHandle} from '../api/ElementHandle.js'; import {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {Page, WaitTimeoutOptions} from '../api/Page.js'; import {Page, WaitTimeoutOptions} from '../api/Page.js';
import {CDPSession} from '../common/Connection.js';
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js'; import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
import {EventEmitter} from '../common/EventEmitter.js'; import {EventEmitter, EventType} from '../common/EventEmitter.js';
import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js';
import {transposeIterableHandle} from '../common/HandleIterator.js'; import {transposeIterableHandle} from '../common/HandleIterator.js';
import { import {
@ -43,6 +44,7 @@ import {
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {throwIfDisposed} from '../util/decorators.js'; import {throwIfDisposed} from '../util/decorators.js';
import {CDPSession} from './CDPSession.js';
import {KeyboardTypeOptions} from './Input.js'; import {KeyboardTypeOptions} from './Input.js';
import {FunctionLocator, Locator, NodeLocator} from './locators/locators.js'; import {FunctionLocator, Locator, NodeLocator} from './locators/locators.js';
import {Realm} from './Realm.js'; import {Realm} from './Realm.js';
@ -127,6 +129,44 @@ export interface FrameAddStyleTagOptions {
content?: string; content?: string;
} }
/**
* @public
*/
export interface FrameEvents extends Record<EventType, unknown> {
/** @internal */
[FrameEvent.FrameNavigated]: Protocol.Page.NavigationType;
/** @internal */
[FrameEvent.FrameSwapped]: undefined;
/** @internal */
[FrameEvent.LifecycleEvent]: undefined;
/** @internal */
[FrameEvent.FrameNavigatedWithinDocument]: undefined;
/** @internal */
[FrameEvent.FrameDetached]: Frame;
/** @internal */
[FrameEvent.FrameSwappedByActivation]: undefined;
}
/**
* We use symbols to prevent external parties listening to these events.
* They are internal to Puppeteer.
*
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace FrameEvent {
export const FrameNavigated = Symbol('Frame.FrameNavigated');
export const FrameSwapped = Symbol('Frame.FrameSwapped');
export const LifecycleEvent = Symbol('Frame.LifecycleEvent');
export const FrameNavigatedWithinDocument = Symbol(
'Frame.FrameNavigatedWithinDocument'
);
export const FrameDetached = Symbol('Frame.FrameDetached');
export const FrameSwappedByActivation = Symbol(
'Frame.FrameSwappedByActivation'
);
}
/** /**
* @internal * @internal
*/ */
@ -181,13 +221,13 @@ export const throwIfDetached = throwIfDisposed<Frame>(frame => {
* Frame lifecycles are controlled by three events that are all dispatched on * Frame lifecycles are controlled by three events that are all dispatched on
* the parent {@link Frame.page | page}: * the parent {@link Frame.page | page}:
* *
* - {@link PageEmittedEvents.FrameAttached} * - {@link PageEvent.FrameAttached}
* - {@link PageEmittedEvents.FrameNavigated} * - {@link PageEvent.FrameNavigated}
* - {@link PageEmittedEvents.FrameDetached} * - {@link PageEvent.FrameDetached}
* *
* @public * @public
*/ */
export abstract class Frame extends EventEmitter { export abstract class Frame extends EventEmitter<FrameEvents> {
/** /**
* @internal * @internal
*/ */

View File

@ -15,8 +15,7 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../common/Connection.js'; import {CDPSession} from './CDPSession.js';
import {Frame} from './Frame.js'; import {Frame} from './Frame.js';
import {HTTPResponse} from './HTTPResponse.js'; import {HTTPResponse} from './HTTPResponse.js';

View File

@ -19,6 +19,8 @@ import type {Readable} from 'stream';
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import { import {
delay,
filter,
filterAsync, filterAsync,
first, first,
firstValueFrom, firstValueFrom,
@ -27,30 +29,34 @@ import {
map, map,
merge, merge,
Observable, Observable,
raceWith,
delay,
filter,
of, of,
switchMap, raceWith,
startWith, startWith,
switchMap,
} from '../../third_party/rxjs/rxjs.js'; } from '../../third_party/rxjs/rxjs.js';
import type {HTTPRequest} from '../api/HTTPRequest.js'; import type {HTTPRequest} from '../api/HTTPRequest.js';
import type {HTTPResponse} from '../api/HTTPResponse.js'; import type {HTTPResponse} from '../api/HTTPResponse.js';
import type {Accessibility} from '../common/Accessibility.js'; import type {Accessibility} from '../common/Accessibility.js';
import type {CDPSession} from '../common/Connection.js'; import type {BidiNetworkManager} from '../common/bidi/NetworkManager.js';
import type {ConsoleMessage} from '../common/ConsoleMessage.js'; import type {ConsoleMessage} from '../common/ConsoleMessage.js';
import type {Coverage} from '../common/Coverage.js'; import type {Coverage} from '../common/Coverage.js';
import {Device} from '../common/Device.js'; import {Device} from '../common/Device.js';
import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js'; import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js';
import {TargetCloseError} from '../common/Errors.js'; import {TargetCloseError} from '../common/Errors.js';
import {EventEmitter, Handler} from '../common/EventEmitter.js'; import {
EventEmitter,
EventsWithWildcard,
EventType,
Handler,
} from '../common/EventEmitter.js';
import type {FileChooser} from '../common/FileChooser.js'; import type {FileChooser} from '../common/FileChooser.js';
import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js'; import type {WaitForSelectorOptions} from '../common/IsolatedWorld.js';
import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js'; import type {PuppeteerLifeCycleEvent} from '../common/LifecycleWatcher.js';
import { import {
NetworkManager as CdpNetworkManager,
Credentials, Credentials,
NetworkConditions, NetworkConditions,
NetworkManagerEmittedEvents, NetworkManagerEvent,
} from '../common/NetworkManager.js'; } from '../common/NetworkManager.js';
import { import {
LowerCasePaperFormat, LowerCasePaperFormat,
@ -81,6 +87,7 @@ import {Deferred} from '../util/Deferred.js';
import type {Browser} from './Browser.js'; import type {Browser} from './Browser.js';
import type {BrowserContext} from './BrowserContext.js'; import type {BrowserContext} from './BrowserContext.js';
import type {CDPSession} from './CDPSession.js';
import type {Dialog} from './Dialog.js'; import type {Dialog} from './Dialog.js';
import type {ClickOptions, ElementHandle} from './ElementHandle.js'; import type {ClickOptions, ElementHandle} from './ElementHandle.js';
import type { import type {
@ -249,7 +256,7 @@ export interface ScreenshotOptions {
* *
* @public * @public
*/ */
export const enum PageEmittedEvents { export const enum PageEvent {
/** /**
* Emitted when the page closes. * Emitted when the page closes.
*/ */
@ -396,36 +403,52 @@ export const enum PageEmittedEvents {
WorkerDestroyed = 'workerdestroyed', WorkerDestroyed = 'workerdestroyed',
} }
export {
/**
* All the events that a page instance may emit.
*
* @deprecated Use {@link PageEvent}.
*/
PageEvent as PageEmittedEvents,
};
/** /**
* Denotes the objects received by callback functions for page events. * Denotes the objects received by callback functions for page events.
* *
* See {@link PageEmittedEvents} for more detail on the events and when they are * See {@link PageEvent} for more detail on the events and when they are
* emitted. * emitted.
* *
* @public * @public
*/ */
export interface PageEventObject { export interface PageEvents extends Record<EventType, unknown> {
close: never; [PageEvent.Close]: undefined;
console: ConsoleMessage; [PageEvent.Console]: ConsoleMessage;
dialog: Dialog; [PageEvent.Dialog]: Dialog;
domcontentloaded: never; [PageEvent.DOMContentLoaded]: undefined;
error: Error; [PageEvent.Error]: Error;
frameattached: Frame; [PageEvent.FrameAttached]: Frame;
framedetached: Frame; [PageEvent.FrameDetached]: Frame;
framenavigated: Frame; [PageEvent.FrameNavigated]: Frame;
load: never; [PageEvent.Load]: undefined;
metrics: {title: string; metrics: Metrics}; [PageEvent.Metrics]: {title: string; metrics: Metrics};
pageerror: Error; [PageEvent.PageError]: Error;
popup: Page; [PageEvent.Popup]: Page | null;
request: HTTPRequest; [PageEvent.Request]: HTTPRequest;
response: HTTPResponse; [PageEvent.Response]: HTTPResponse;
requestfailed: HTTPRequest; [PageEvent.RequestFailed]: HTTPRequest;
requestfinished: HTTPRequest; [PageEvent.RequestFinished]: HTTPRequest;
requestservedfromcache: HTTPRequest; [PageEvent.RequestServedFromCache]: HTTPRequest;
workercreated: WebWorker; [PageEvent.WorkerCreated]: WebWorker;
workerdestroyed: WebWorker; [PageEvent.WorkerDestroyed]: WebWorker;
} }
export {
/**
* @deprecated Use {@link PageEvents}.
*/
PageEvents as PageEventObject,
};
/** /**
* @public * @public
*/ */
@ -460,7 +483,7 @@ export interface NewDocumentScriptEvaluation {
* ``` * ```
* *
* The Page class extends from Puppeteer's {@link EventEmitter} class and will * The Page class extends from Puppeteer's {@link EventEmitter} class and will
* emit various events which are documented in the {@link PageEmittedEvents} enum. * emit various events which are documented in the {@link PageEvent} enum.
* *
* @example * @example
* This example logs a message for a single page `load` event: * This example logs a message for a single page `load` event:
@ -469,7 +492,7 @@ export interface NewDocumentScriptEvaluation {
* page.once('load', () => console.log('Page loaded!')); * page.once('load', () => console.log('Page loaded!'));
* ``` * ```
* *
* To unsubscribe from events use the {@link Page.off} method: * To unsubscribe from events use the {@link EventEmitter.off} method:
* *
* ```ts * ```ts
* function logRequest(interceptedRequest) { * function logRequest(interceptedRequest) {
@ -483,10 +506,10 @@ export interface NewDocumentScriptEvaluation {
* @public * @public
*/ */
export abstract class Page export abstract class Page
extends EventEmitter extends EventEmitter<PageEvents>
implements AsyncDisposable, Disposable implements AsyncDisposable, Disposable
{ {
#handlerMap = new WeakMap<Handler<any>, Handler<any>>(); #requestHandlers = new WeakMap<Handler<HTTPRequest>, Handler<HTTPRequest>>();
/** /**
* @internal * @internal
@ -519,52 +542,56 @@ export abstract class Page
/** /**
* Listen to page events. * Listen to page events.
* *
* :::note * @remarks
*
* This method exists to define event typings and handle proper wireup of * This method exists to define event typings and handle proper wireup of
* cooperative request interception. Actual event listening and dispatching is * cooperative request interception. Actual event listening and dispatching is
* delegated to {@link EventEmitter}. * delegated to {@link EventEmitter}.
* *
* ::: * @internal
*/ */
override on<K extends keyof PageEventObject>( override on<K extends keyof EventsWithWildcard<PageEvents>>(
eventName: K, type: K,
handler: (event: PageEventObject[K]) => void handler: (event: EventsWithWildcard<PageEvents>[K]) => void
): this { ): this {
if (eventName === 'request') { if (type !== PageEvent.Request) {
const wrap = return super.on(type, handler);
this.#handlerMap.get(handler) || }
((event: HTTPRequest) => { let wrapper = this.#requestHandlers.get(
handler as (event: PageEvents[PageEvent.Request]) => void
);
if (wrapper === undefined) {
wrapper = (event: HTTPRequest) => {
event.enqueueInterceptAction(() => { event.enqueueInterceptAction(() => {
return handler(event as PageEventObject[K]); return handler(event as EventsWithWildcard<PageEvents>[K]);
}); });
}); };
this.#requestHandlers.set(
this.#handlerMap.set(handler, wrap); handler as (event: PageEvents[PageEvent.Request]) => void,
wrapper
return super.on(eventName, wrap); );
} }
return super.on(eventName, handler); return super.on(
type,
wrapper as (event: EventsWithWildcard<PageEvents>[K]) => void
);
} }
override once<K extends keyof PageEventObject>( /**
eventName: K, * @internal
handler: (event: PageEventObject[K]) => void */
override off<K extends keyof EventsWithWildcard<PageEvents>>(
type: K,
handler: (event: EventsWithWildcard<PageEvents>[K]) => void
): this { ): this {
// Note: this method only exists to define the types; we delegate the impl if (type === PageEvent.Request) {
// to EventEmitter. handler =
return super.once(eventName, handler); (this.#requestHandlers.get(
handler as (
event: EventsWithWildcard<PageEvents>[PageEvent.Request]
) => void
) as (event: EventsWithWildcard<PageEvents>[K]) => void) || handler;
} }
return super.off(type, handler);
override off<K extends keyof PageEventObject>(
eventName: K,
handler: (event: PageEventObject[K]) => void
): this {
if (eventName === 'request') {
handler = this.#handlerMap.get(handler) || handler;
}
return super.off(eventName, handler);
} }
/** /**
@ -1696,9 +1723,7 @@ export abstract class Page
* @internal * @internal
*/ */
protected async _waitForNetworkIdle( protected async _waitForNetworkIdle(
networkManager: EventEmitter & { networkManager: BidiNetworkManager | CdpNetworkManager,
inFlightRequestsCount: () => number;
},
idleTime: number, idleTime: number,
ms: number, ms: number,
closedDeferred: Deferred<TargetCloseError> closedDeferred: Deferred<TargetCloseError>
@ -1707,15 +1732,15 @@ export abstract class Page
merge( merge(
fromEvent( fromEvent(
networkManager, networkManager,
NetworkManagerEmittedEvents.Request as unknown as string NetworkManagerEvent.Request as unknown as string
), ),
fromEvent( fromEvent(
networkManager, networkManager,
NetworkManagerEmittedEvents.Response as unknown as string NetworkManagerEvent.Response as unknown as string
), ),
fromEvent( fromEvent(
networkManager, networkManager,
NetworkManagerEmittedEvents.RequestFailed as unknown as string NetworkManagerEvent.RequestFailed as unknown as string
) )
).pipe( ).pipe(
startWith(null), startWith(null),
@ -1755,15 +1780,15 @@ export abstract class Page
return await firstValueFrom( return await firstValueFrom(
merge( merge(
fromEvent(this, PageEmittedEvents.FrameAttached) as Observable<Frame>, fromEvent(this, PageEvent.FrameAttached) as Observable<Frame>,
fromEvent(this, PageEmittedEvents.FrameNavigated) as Observable<Frame>, fromEvent(this, PageEvent.FrameNavigated) as Observable<Frame>,
from(this.frames()) from(this.frames())
).pipe( ).pipe(
filterAsync(urlOrPredicate), filterAsync(urlOrPredicate),
first(), first(),
raceWith( raceWith(
timeout(ms), timeout(ms),
fromEvent(this, PageEmittedEvents.Close).pipe( fromEvent(this, PageEvent.Close).pipe(
map(() => { map(() => {
throw new TargetCloseError('Page closed.'); throw new TargetCloseError('Page closed.');
}) })

View File

@ -17,9 +17,10 @@
import type {Browser} from '../api/Browser.js'; import type {Browser} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {Page} from '../api/Page.js'; import {Page} from '../api/Page.js';
import {CDPSession} from '../common/Connection.js';
import {WebWorker} from '../common/WebWorker.js'; import {WebWorker} from '../common/WebWorker.js';
import {CDPSession} from './CDPSession.js';
/** /**
* @public * @public
*/ */

View File

@ -28,3 +28,4 @@ export * from './locators/locators.js';
export * from './Page.js'; export * from './Page.js';
export * from './Realm.js'; export * from './Realm.js';
export * from './Target.js'; export * from './Target.js';
export * from './CDPSession.js';

View File

@ -37,7 +37,7 @@ import {
retry, retry,
tap, tap,
} from '../../../third_party/rxjs/rxjs.js'; } from '../../../third_party/rxjs/rxjs.js';
import {EventEmitter} from '../../common/EventEmitter.js'; import {EventEmitter, EventType} from '../../common/EventEmitter.js';
import {HandleFor} from '../../common/types.js'; import {HandleFor} from '../../common/types.js';
import {debugError, timeout} from '../../common/util.js'; import {debugError, timeout} from '../../common/util.js';
import {BoundingBox, ClickOptions, ElementHandle} from '../ElementHandle.js'; import {BoundingBox, ClickOptions, ElementHandle} from '../ElementHandle.js';
@ -130,20 +130,34 @@ export interface LocatorScrollOptions extends ActionOptions {
* *
* @public * @public
*/ */
export enum LocatorEmittedEvents { export enum LocatorEvent {
/** /**
* Emitted every time before the locator performs an action on the located element(s). * Emitted every time before the locator performs an action on the located element(s).
*/ */
Action = 'action', Action = 'action',
} }
export {
/**
* @deprecated Use {@link LocatorEvent}.
*/
LocatorEvent as LocatorEmittedEvents,
};
/** /**
* @public * @public
*/ */
export interface LocatorEventObject { export interface LocatorEvents extends Record<EventType, unknown> {
[LocatorEmittedEvents.Action]: never; [LocatorEvent.Action]: undefined;
} }
export {
/**
* @deprecated Use {@link LocatorEvents}.
*/
LocatorEvents as LocatorEventObject,
};
/** /**
* Locators describe a strategy of locating objects and performing an action on * Locators describe a strategy of locating objects and performing an action on
* them. If the action fails because the object is not ready for the action, the * them. If the action fails because the object is not ready for the action, the
@ -152,7 +166,7 @@ export interface LocatorEventObject {
* *
* @public * @public
*/ */
export abstract class Locator<T> extends EventEmitter { export abstract class Locator<T> extends EventEmitter<LocatorEvents> {
/** /**
* Creates a race between multiple locators but ensures that only a single one * Creates a race between multiple locators but ensures that only a single one
* acts. * acts.
@ -224,27 +238,6 @@ export abstract class Locator<T> extends EventEmitter {
return this._timeout; return this._timeout;
} }
override on<K extends keyof LocatorEventObject>(
eventName: K,
handler: (event: LocatorEventObject[K]) => void
): this {
return super.on(eventName, handler);
}
override once<K extends keyof LocatorEventObject>(
eventName: K,
handler: (event: LocatorEventObject[K]) => void
): this {
return super.once(eventName, handler);
}
override off<K extends keyof LocatorEventObject>(
eventName: K,
handler: (event: LocatorEventObject[K]) => void
): this {
return super.off(eventName, handler);
}
setTimeout(timeout: number): Locator<T> { setTimeout(timeout: number): Locator<T> {
const locator = this._clone(); const locator = this._clone();
locator._timeout = timeout; locator._timeout = timeout;
@ -426,7 +419,7 @@ export abstract class Locator<T> extends EventEmitter {
signal signal
), ),
tap(() => { tap(() => {
return this.emit(LocatorEmittedEvents.Action); return this.emit(LocatorEvent.Action, undefined);
}), }),
mergeMap(handle => { mergeMap(handle => {
return from(handle.click(options)).pipe( return from(handle.click(options)).pipe(
@ -456,7 +449,7 @@ export abstract class Locator<T> extends EventEmitter {
signal signal
), ),
tap(() => { tap(() => {
return this.emit(LocatorEmittedEvents.Action); return this.emit(LocatorEvent.Action, undefined);
}), }),
mergeMap(handle => { mergeMap(handle => {
return from( return from(
@ -587,7 +580,7 @@ export abstract class Locator<T> extends EventEmitter {
signal signal
), ),
tap(() => { tap(() => {
return this.emit(LocatorEmittedEvents.Action); return this.emit(LocatorEvent.Action, undefined);
}), }),
mergeMap(handle => { mergeMap(handle => {
return from(handle.hover()).pipe( return from(handle.hover()).pipe(
@ -615,7 +608,7 @@ export abstract class Locator<T> extends EventEmitter {
signal signal
), ),
tap(() => { tap(() => {
return this.emit(LocatorEmittedEvents.Action); return this.emit(LocatorEvent.Action, undefined);
}), }),
mergeMap(handle => { mergeMap(handle => {
return from( return from(

View File

@ -16,10 +16,9 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {ElementHandle} from '../api/ElementHandle.js'; import {ElementHandle} from '../api/ElementHandle.js';
import {CDPSession} from './Connection.js';
/** /**
* Represents a Node and the properties of it that are relevant to Accessibility. * Represents a Node and the properties of it that are relevant to Accessibility.
* @public * @public

View File

@ -16,11 +16,11 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {ElementHandle} from '../api/ElementHandle.js'; import {ElementHandle} from '../api/ElementHandle.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js'; import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
import {CDPSession} from './Connection.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {QueryHandler, QuerySelector} from './QueryHandler.js'; import {QueryHandler, QuerySelector} from './QueryHandler.js';
import {AwaitableIterable} from './types.js'; import {AwaitableIterable} from './types.js';

View File

@ -21,33 +21,33 @@ import {Protocol} from 'devtools-protocol';
import { import {
Browser as BrowserBase, Browser as BrowserBase,
BrowserCloseCallback, BrowserCloseCallback,
TargetFilterCallback,
IsPageTargetCallback,
BrowserEmittedEvents,
BrowserContextEmittedEvents,
BrowserContextOptions, BrowserContextOptions,
WEB_PERMISSION_TO_PROTOCOL_PERMISSION, BrowserEvent,
IsPageTargetCallback,
Permission, Permission,
TargetFilterCallback,
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
} from '../api/Browser.js'; } from '../api/Browser.js';
import {BrowserContext} from '../api/BrowserContext.js'; import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {Page} from '../api/Page.js'; import {Page} from '../api/Page.js';
import {Target} from '../api/Target.js'; import {Target} from '../api/Target.js';
import {USE_TAB_TARGET} from '../environment.js'; import {USE_TAB_TARGET} from '../environment.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {ChromeTargetManager} from './ChromeTargetManager.js'; import {ChromeTargetManager} from './ChromeTargetManager.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js'; import {Connection} from './Connection.js';
import {FirefoxTargetManager} from './FirefoxTargetManager.js'; import {FirefoxTargetManager} from './FirefoxTargetManager.js';
import {Viewport} from './PuppeteerViewport.js'; import {Viewport} from './PuppeteerViewport.js';
import { import {
CDPTarget,
DevToolsTarget,
InitializationStatus, InitializationStatus,
OtherTarget, OtherTarget,
PageTarget, PageTarget,
CDPTarget,
WorkerTarget, WorkerTarget,
DevToolsTarget,
} from './Target.js'; } from './Target.js';
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js'; import {TargetManager, TargetManagerEvent} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js'; import {TaskQueue} from './TaskQueue.js';
/** /**
@ -151,52 +151,46 @@ export class CDPBrowser extends BrowserBase {
} }
#emitDisconnected = () => { #emitDisconnected = () => {
this.emit(BrowserEmittedEvents.Disconnected); this.emit(BrowserEvent.Disconnected, undefined);
}; };
override async _attach(): Promise<void> { override async _attach(): Promise<void> {
this.#connection.on( this.#connection.on(CDPSessionEvent.Disconnected, this.#emitDisconnected);
ConnectionEmittedEvents.Disconnected,
this.#emitDisconnected
);
this.#targetManager.on( this.#targetManager.on(
TargetManagerEmittedEvents.TargetAvailable, TargetManagerEvent.TargetAvailable,
this.#onAttachedToTarget this.#onAttachedToTarget
); );
this.#targetManager.on( this.#targetManager.on(
TargetManagerEmittedEvents.TargetGone, TargetManagerEvent.TargetGone,
this.#onDetachedFromTarget this.#onDetachedFromTarget
); );
this.#targetManager.on( this.#targetManager.on(
TargetManagerEmittedEvents.TargetChanged, TargetManagerEvent.TargetChanged,
this.#onTargetChanged this.#onTargetChanged
); );
this.#targetManager.on( this.#targetManager.on(
TargetManagerEmittedEvents.TargetDiscovered, TargetManagerEvent.TargetDiscovered,
this.#onTargetDiscovered this.#onTargetDiscovered
); );
await this.#targetManager.initialize(); await this.#targetManager.initialize();
} }
override _detach(): void { override _detach(): void {
this.#connection.off( this.#connection.off(CDPSessionEvent.Disconnected, this.#emitDisconnected);
ConnectionEmittedEvents.Disconnected,
this.#emitDisconnected
);
this.#targetManager.off( this.#targetManager.off(
TargetManagerEmittedEvents.TargetAvailable, TargetManagerEvent.TargetAvailable,
this.#onAttachedToTarget this.#onAttachedToTarget
); );
this.#targetManager.off( this.#targetManager.off(
TargetManagerEmittedEvents.TargetGone, TargetManagerEvent.TargetGone,
this.#onDetachedFromTarget this.#onDetachedFromTarget
); );
this.#targetManager.off( this.#targetManager.off(
TargetManagerEmittedEvents.TargetChanged, TargetManagerEvent.TargetChanged,
this.#onTargetChanged this.#onTargetChanged
); );
this.#targetManager.off( this.#targetManager.off(
TargetManagerEmittedEvents.TargetDiscovered, TargetManagerEvent.TargetDiscovered,
this.#onTargetDiscovered this.#onTargetDiscovered
); );
} }
@ -367,10 +361,8 @@ export class CDPBrowser extends BrowserBase {
(await target._initializedDeferred.valueOrThrow()) === (await target._initializedDeferred.valueOrThrow()) ===
InitializationStatus.SUCCESS InitializationStatus.SUCCESS
) { ) {
this.emit(BrowserEmittedEvents.TargetCreated, target); this.emit(BrowserEvent.TargetCreated, target);
target target.browserContext().emit(BrowserContextEvent.TargetCreated, target);
.browserContext()
.emit(BrowserContextEmittedEvents.TargetCreated, target);
} }
}; };
@ -381,22 +373,18 @@ export class CDPBrowser extends BrowserBase {
(await target._initializedDeferred.valueOrThrow()) === (await target._initializedDeferred.valueOrThrow()) ===
InitializationStatus.SUCCESS InitializationStatus.SUCCESS
) { ) {
this.emit(BrowserEmittedEvents.TargetDestroyed, target); this.emit(BrowserEvent.TargetDestroyed, target);
target target.browserContext().emit(BrowserContextEvent.TargetDestroyed, target);
.browserContext()
.emit(BrowserContextEmittedEvents.TargetDestroyed, target);
} }
}; };
#onTargetChanged = ({target}: {target: CDPTarget}): void => { #onTargetChanged = ({target}: {target: CDPTarget}): void => {
this.emit(BrowserEmittedEvents.TargetChanged, target); this.emit(BrowserEvent.TargetChanged, target);
target target.browserContext().emit(BrowserContextEvent.TargetChanged, target);
.browserContext()
.emit(BrowserContextEmittedEvents.TargetChanged, target);
}; };
#onTargetDiscovered = (targetInfo: Protocol.Target.TargetInfo): void => { #onTargetDiscovered = (targetInfo: Protocol.Target.TargetInfo): void => {
this.emit('targetdiscovered', targetInfo); this.emit(BrowserEvent.TargetDiscovered, targetInfo);
}; };
/** /**

View File

@ -0,0 +1,154 @@
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {CDPEvents, CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {assert} from '../util/assert.js';
import {
CallbackRegistry,
Connection,
createProtocolErrorMessage,
} from './Connection.js';
import {TargetCloseError} from './Errors.js';
import {CDPTarget} from './Target.js';
/**
* @internal
*/
export class CDPCDPSession extends CDPSession {
#sessionId: string;
#targetType: string;
#callbacks = new CallbackRegistry();
#connection?: Connection;
#parentSessionId?: string;
#target?: CDPTarget;
/**
* @internal
*/
constructor(
connection: Connection,
targetType: string,
sessionId: string,
parentSessionId: string | undefined
) {
super();
this.#connection = connection;
this.#targetType = targetType;
this.#sessionId = sessionId;
this.#parentSessionId = parentSessionId;
}
/**
* Sets the CDPTarget associated with the session instance.
*
* @internal
*/
_setTarget(target: CDPTarget): void {
this.#target = target;
}
/**
* Gets the CDPTarget associated with the session instance.
*
* @internal
*/
_target(): CDPTarget {
assert(this.#target, 'Target must exist');
return this.#target;
}
override connection(): Connection | undefined {
return this.#connection;
}
override parentSession(): CDPSession | undefined {
if (!this.#parentSessionId) {
return;
}
const parent = this.#connection?.session(this.#parentSessionId);
return parent ?? undefined;
}
override send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (!this.#connection) {
return Promise.reject(
new TargetCloseError(
`Protocol error (${method}): Session closed. Most likely the ${
this.#targetType
} has been closed.`
)
);
}
// See the comment in Connection#send explaining why we do this.
const params = paramArgs.length ? paramArgs[0] : undefined;
return this.#connection._rawSend(
this.#callbacks,
method,
params,
this.#sessionId
);
}
/**
* @internal
*/
_onMessage(object: {
id?: number;
method: keyof CDPEvents;
params: CDPEvents[keyof CDPEvents];
error: {message: string; data: any; code: number};
result?: any;
}): void {
if (object.id) {
if (object.error) {
this.#callbacks.reject(
object.id,
createProtocolErrorMessage(object),
object.error.message
);
} else {
this.#callbacks.resolve(object.id, object.result);
}
} else {
assert(!object.id);
this.emit(object.method, object.params);
}
}
/**
* Detaches the cdpSession from the target. Once detached, the cdpSession object
* won't emit any events and can't be used to send messages.
*/
override async detach(): Promise<void> {
if (!this.#connection) {
throw new Error(
`Session already detached. Most likely the ${
this.#targetType
} has been closed.`
);
}
await this.#connection.send('Target.detachFromTarget', {
sessionId: this.#sessionId,
});
}
/**
* @internal
*/
_onClosed(): void {
this.#callbacks.clear();
this.#connection = undefined;
this.emit(CDPSessionEvent.Disconnected, undefined);
}
/**
* Returns the session's id.
*/
override id(): string {
return this.#sessionId;
}
}

View File

@ -17,17 +17,19 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {TargetFilterCallback} from '../api/Browser.js'; import {TargetFilterCallback} from '../api/Browser.js';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {TargetType} from '../api/Target.js'; import {TargetType} from '../api/Target.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession, CDPSessionEmittedEvents, Connection} from './Connection.js'; import {Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {InitializationStatus, CDPTarget} from './Target.js'; import {CDPTarget, InitializationStatus} from './Target.js';
import { import {
TargetFactory, TargetFactory,
TargetManager, TargetManager,
TargetManagerEmittedEvents, TargetManagerEvent,
TargetManagerEvents,
} from './TargetManager.js'; } from './TargetManager.js';
import {debugError} from './util.js'; import {debugError} from './util.js';
@ -49,7 +51,10 @@ function isPageTargetBecomingPrimary(
* *
* @internal * @internal
*/ */
export class ChromeTargetManager extends EventEmitter implements TargetManager { export class ChromeTargetManager
extends EventEmitter<TargetManagerEvents>
implements TargetManager
{
#connection: Connection; #connection: Connection;
/** /**
* Keeps track of the following events: 'Target.targetCreated', * Keeps track of the following events: 'Target.targetCreated',
@ -81,7 +86,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
#attachedToTargetListenersBySession = new WeakMap< #attachedToTargetListenersBySession = new WeakMap<
CDPSession | Connection, CDPSession | Connection,
(event: Protocol.Target.AttachedToTargetEvent) => Promise<void> (event: Protocol.Target.AttachedToTargetEvent) => void
>(); >();
#detachedFromTargetListenersBySession = new WeakMap< #detachedFromTargetListenersBySession = new WeakMap<
CDPSession | Connection, CDPSession | Connection,
@ -116,7 +121,10 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#connection.on('Target.targetCreated', this.#onTargetCreated); this.#connection.on('Target.targetCreated', this.#onTargetCreated);
this.#connection.on('Target.targetDestroyed', this.#onTargetDestroyed); this.#connection.on('Target.targetDestroyed', this.#onTargetDestroyed);
this.#connection.on('Target.targetInfoChanged', this.#onTargetInfoChanged); this.#connection.on('Target.targetInfoChanged', this.#onTargetInfoChanged);
this.#connection.on('sessiondetached', this.#onSessionDetached); this.#connection.on(
CDPSessionEvent.SessionDetached,
this.#onSessionDetached
);
this.#setupAttachmentListeners(this.#connection); this.#setupAttachmentListeners(this.#connection);
this.#connection this.#connection
@ -176,7 +184,10 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#connection.off('Target.targetCreated', this.#onTargetCreated); this.#connection.off('Target.targetCreated', this.#onTargetCreated);
this.#connection.off('Target.targetDestroyed', this.#onTargetDestroyed); this.#connection.off('Target.targetDestroyed', this.#onTargetDestroyed);
this.#connection.off('Target.targetInfoChanged', this.#onTargetInfoChanged); this.#connection.off('Target.targetInfoChanged', this.#onTargetInfoChanged);
this.#connection.off('sessiondetached', this.#onSessionDetached); this.#connection.off(
CDPSessionEvent.SessionDetached,
this.#onSessionDetached
);
this.#removeAttachmentListeners(this.#connection); this.#removeAttachmentListeners(this.#connection);
} }
@ -193,7 +204,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
#setupAttachmentListeners(session: CDPSession | Connection): void { #setupAttachmentListeners(session: CDPSession | Connection): void {
const listener = (event: Protocol.Target.AttachedToTargetEvent) => { const listener = (event: Protocol.Target.AttachedToTargetEvent) => {
return this.#onAttachedToTarget(session, event); void this.#onAttachedToTarget(session, event);
}; };
assert(!this.#attachedToTargetListenersBySession.has(session)); assert(!this.#attachedToTargetListenersBySession.has(session));
this.#attachedToTargetListenersBySession.set(session, listener); this.#attachedToTargetListenersBySession.set(session, listener);
@ -210,11 +221,9 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
} }
#removeAttachmentListeners(session: CDPSession | Connection): void { #removeAttachmentListeners(session: CDPSession | Connection): void {
if (this.#attachedToTargetListenersBySession.has(session)) { const listener = this.#attachedToTargetListenersBySession.get(session);
session.off( if (listener) {
'Target.attachedToTarget', session.off('Target.attachedToTarget', listener);
this.#attachedToTargetListenersBySession.get(session)!
);
this.#attachedToTargetListenersBySession.delete(session); this.#attachedToTargetListenersBySession.delete(session);
} }
@ -237,7 +246,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
event.targetInfo event.targetInfo
); );
this.emit(TargetManagerEmittedEvents.TargetDiscovered, event.targetInfo); this.emit(TargetManagerEvent.TargetDiscovered, event.targetInfo);
// The connection is already attached to the browser target implicitly, // The connection is already attached to the browser target implicitly,
// therefore, no new CDPSession is created and we have special handling // therefore, no new CDPSession is created and we have special handling
@ -263,9 +272,11 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
// Special case for service workers: report TargetGone event when // Special case for service workers: report TargetGone event when
// the worker is destroyed. // the worker is destroyed.
const target = this.#attachedTargetsByTargetId.get(event.targetId); const target = this.#attachedTargetsByTargetId.get(event.targetId);
this.emit(TargetManagerEmittedEvents.TargetGone, target); if (target) {
this.emit(TargetManagerEvent.TargetGone, target);
this.#attachedTargetsByTargetId.delete(event.targetId); this.#attachedTargetsByTargetId.delete(event.targetId);
} }
}
}; };
#onTargetInfoChanged = (event: Protocol.Target.TargetInfoChangedEvent) => { #onTargetInfoChanged = (event: Protocol.Target.TargetInfoChangedEvent) => {
@ -301,14 +312,14 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
session, session,
'Target that is being activated is missing a CDPSession.' 'Target that is being activated is missing a CDPSession.'
); );
session.parentSession()?.emit(CDPSessionEmittedEvents.Swapped, session); session.parentSession()?.emit(CDPSessionEvent.Swapped, session);
} }
target._targetInfoChanged(event.targetInfo); target._targetInfoChanged(event.targetInfo);
if (wasInitialized && previousURL !== target.url()) { if (wasInitialized && previousURL !== target.url()) {
this.emit(TargetManagerEmittedEvents.TargetChanged, { this.emit(TargetManagerEvent.TargetChanged, {
target: target, target,
wasInitialized, wasInitialized,
previousURL, previousURL,
}); });
@ -359,7 +370,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
const target = this.#targetFactory(targetInfo); const target = this.#targetFactory(targetInfo);
target._initialize(); target._initialize();
this.#attachedTargetsByTargetId.set(targetInfo.targetId, target); this.#attachedTargetsByTargetId.set(targetInfo.targetId, target);
this.emit(TargetManagerEmittedEvents.TargetAvailable, target); this.emit(TargetManagerEvent.TargetAvailable, target);
return; return;
} }
@ -398,11 +409,15 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#attachedTargetsBySessionId.set(session.id(), target); this.#attachedTargetsBySessionId.set(session.id(), target);
} }
parentSession.emit(CDPSessionEmittedEvents.Ready, session); if (parentSession instanceof CDPSession) {
parentSession.emit(CDPSessionEvent.Ready, session);
} else {
parentSession.emit(CDPSessionEvent.Ready, session);
}
this.#targetsIdsForInit.delete(target._targetId); this.#targetsIdsForInit.delete(target._targetId);
if (!isExistingTarget && isTargetExposed(target)) { if (!isExistingTarget && isTargetExposed(target)) {
this.emit(TargetManagerEmittedEvents.TargetAvailable, target); this.emit(TargetManagerEvent.TargetAvailable, target);
} }
this.#finishInitializationIfReady(); this.#finishInitializationIfReady();
@ -440,7 +455,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#attachedTargetsByTargetId.delete(target._targetId); this.#attachedTargetsByTargetId.delete(target._targetId);
if (isTargetExposed(target)) { if (isTargetExposed(target)) {
this.emit(TargetManagerEmittedEvents.TargetGone, target); this.emit(TargetManagerEvent.TargetGone, target);
} }
}; };
} }

View File

@ -17,14 +17,18 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {assert} from '../util/assert.js'; import {
CDPSession,
CDPSessionEvent,
CDPSessionEvents,
} from '../api/CDPSession.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPCDPSession} from './CDPSession.js';
import {ConnectionTransport} from './ConnectionTransport.js'; import {ConnectionTransport} from './ConnectionTransport.js';
import {debug} from './Debug.js'; import {debug} from './Debug.js';
import {TargetCloseError, ProtocolError} from './Errors.js'; import {ProtocolError, TargetCloseError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {CDPTarget} from './Target.js';
import {debugError} from './util.js'; import {debugError} from './util.js';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►'); const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
@ -35,15 +39,6 @@ const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
*/ */
export {ConnectionTransport, ProtocolMapping}; export {ConnectionTransport, ProtocolMapping};
/**
* Internal events that the Connection class emits.
*
* @internal
*/
export const ConnectionEmittedEvents = {
Disconnected: Symbol('Connection.Disconnected'),
} as const;
/** /**
* @internal * @internal
*/ */
@ -200,12 +195,12 @@ export class CallbackRegistry {
/** /**
* @public * @public
*/ */
export class Connection extends EventEmitter { export class Connection extends EventEmitter<CDPSessionEvents> {
#url: string; #url: string;
#transport: ConnectionTransport; #transport: ConnectionTransport;
#delay: number; #delay: number;
#timeout: number; #timeout: number;
#sessions = new Map<string, CDPSessionImpl>(); #sessions = new Map<string, CDPCDPSession>();
#closed = false; #closed = false;
#manuallyAttached = new Set<string>(); #manuallyAttached = new Set<string>();
#callbacks = new CallbackRegistry(); #callbacks = new CallbackRegistry();
@ -315,27 +310,27 @@ export class Connection extends EventEmitter {
const object = JSON.parse(message); const object = JSON.parse(message);
if (object.method === 'Target.attachedToTarget') { if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId; const sessionId = object.params.sessionId;
const session = new CDPSessionImpl( const session = new CDPCDPSession(
this, this,
object.params.targetInfo.type, object.params.targetInfo.type,
sessionId, sessionId,
object.sessionId object.sessionId
); );
this.#sessions.set(sessionId, session); this.#sessions.set(sessionId, session);
this.emit('sessionattached', session); this.emit(CDPSessionEvent.SessionAttached, session);
const parentSession = this.#sessions.get(object.sessionId); const parentSession = this.#sessions.get(object.sessionId);
if (parentSession) { if (parentSession) {
parentSession.emit('sessionattached', session); parentSession.emit(CDPSessionEvent.SessionAttached, session);
} }
} else if (object.method === 'Target.detachedFromTarget') { } else if (object.method === 'Target.detachedFromTarget') {
const session = this.#sessions.get(object.params.sessionId); const session = this.#sessions.get(object.params.sessionId);
if (session) { if (session) {
session._onClosed(); session._onClosed();
this.#sessions.delete(object.params.sessionId); this.#sessions.delete(object.params.sessionId);
this.emit('sessiondetached', session); this.emit(CDPSessionEvent.SessionDetached, session);
const parentSession = this.#sessions.get(object.sessionId); const parentSession = this.#sessions.get(object.sessionId);
if (parentSession) { if (parentSession) {
parentSession.emit('sessiondetached', session); parentSession.emit(CDPSessionEvent.SessionDetached, session);
} }
} }
} }
@ -371,7 +366,7 @@ export class Connection extends EventEmitter {
session._onClosed(); session._onClosed();
} }
this.#sessions.clear(); this.#sessions.clear();
this.emit(ConnectionEmittedEvents.Disconnected); this.emit(CDPSessionEvent.Disconnected, undefined);
} }
dispose(): void { dispose(): void {
@ -422,240 +417,7 @@ export class Connection extends EventEmitter {
/** /**
* @internal * @internal
*/ */
export interface CDPSessionOnMessageObject { export function createProtocolErrorMessage(object: {
id?: number;
method: string;
params: Record<string, unknown>;
error: {message: string; data: any; code: number};
result?: any;
}
/**
* Internal events that the CDPSession class emits.
*
* @internal
*/
export const CDPSessionEmittedEvents = {
Disconnected: Symbol('CDPSession.Disconnected'),
Swapped: Symbol('CDPSession.Swapped'),
/**
* Emitted when the session is ready to be configured during the auto-attach
* process. Right after the event is handled, the session will be resumed.
*/
Ready: Symbol('CDPSession.Ready'),
} as const;
/**
* The `CDPSession` instances are used to talk raw Chrome Devtools Protocol.
*
* @remarks
*
* Protocol methods can be called with {@link CDPSession.send} method and protocol
* events can be subscribed to with `CDPSession.on` method.
*
* Useful links: {@link https://chromedevtools.github.io/devtools-protocol/ | DevTools Protocol Viewer}
* and {@link https://github.com/aslushnikov/getting-started-with-cdp/blob/HEAD/README.md | Getting Started with DevTools Protocol}.
*
* @example
*
* ```ts
* const client = await page.target().createCDPSession();
* await client.send('Animation.enable');
* client.on('Animation.animationCreated', () =>
* console.log('Animation created!')
* );
* const response = await client.send('Animation.getPlaybackRate');
* console.log('playback rate is ' + response.playbackRate);
* await client.send('Animation.setPlaybackRate', {
* playbackRate: response.playbackRate / 2,
* });
* ```
*
* @public
*/
export class CDPSession extends EventEmitter {
/**
* @internal
*/
constructor() {
super();
}
connection(): Connection | undefined {
throw new Error('Not implemented');
}
/**
* Parent session in terms of CDP's auto-attach mechanism.
*
* @internal
*/
parentSession(): CDPSession | undefined {
return undefined;
}
send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']>;
send<T extends keyof ProtocolMapping.Commands>(): Promise<
ProtocolMapping.Commands[T]['returnType']
> {
throw new Error('Not implemented');
}
/**
* Detaches the cdpSession from the target. Once detached, the cdpSession object
* won't emit any events and can't be used to send messages.
*/
async detach(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Returns the session's id.
*/
id(): string {
throw new Error('Not implemented');
}
}
/**
* @internal
*/
export class CDPSessionImpl extends CDPSession {
#sessionId: string;
#targetType: string;
#callbacks = new CallbackRegistry();
#connection?: Connection;
#parentSessionId?: string;
#target?: CDPTarget;
/**
* @internal
*/
constructor(
connection: Connection,
targetType: string,
sessionId: string,
parentSessionId: string | undefined
) {
super();
this.#connection = connection;
this.#targetType = targetType;
this.#sessionId = sessionId;
this.#parentSessionId = parentSessionId;
}
/**
* Sets the CDPTarget associated with the session instance.
*
* @internal
*/
_setTarget(target: CDPTarget): void {
this.#target = target;
}
/**
* Gets the CDPTarget associated with the session instance.
*
* @internal
*/
_target(): CDPTarget {
assert(this.#target, 'Target must exist');
return this.#target;
}
override connection(): Connection | undefined {
return this.#connection;
}
override parentSession(): CDPSession | undefined {
if (!this.#parentSessionId) {
return;
}
const parent = this.#connection?.session(this.#parentSessionId);
return parent ?? undefined;
}
override send<T extends keyof ProtocolMapping.Commands>(
method: T,
...paramArgs: ProtocolMapping.Commands[T]['paramsType']
): Promise<ProtocolMapping.Commands[T]['returnType']> {
if (!this.#connection) {
return Promise.reject(
new TargetCloseError(
`Protocol error (${method}): Session closed. Most likely the ${
this.#targetType
} has been closed.`
)
);
}
// See the comment in Connection#send explaining why we do this.
const params = paramArgs.length ? paramArgs[0] : undefined;
return this.#connection._rawSend(
this.#callbacks,
method,
params,
this.#sessionId
);
}
/**
* @internal
*/
_onMessage(object: CDPSessionOnMessageObject): void {
if (object.id) {
if (object.error) {
this.#callbacks.reject(
object.id,
createProtocolErrorMessage(object),
object.error.message
);
} else {
this.#callbacks.resolve(object.id, object.result);
}
} else {
assert(!object.id);
this.emit(object.method, object.params);
}
}
/**
* Detaches the cdpSession from the target. Once detached, the cdpSession object
* won't emit any events and can't be used to send messages.
*/
override async detach(): Promise<void> {
if (!this.#connection) {
throw new Error(
`Session already detached. Most likely the ${
this.#targetType
} has been closed.`
);
}
await this.#connection.send('Target.detachFromTarget', {
sessionId: this.#sessionId,
});
}
/**
* @internal
*/
_onClosed(): void {
this.#callbacks.clear();
this.#connection = undefined;
this.emit(CDPSessionEmittedEvents.Disconnected);
}
/**
* Returns the session's id.
*/
override id(): string {
return this.#sessionId;
}
}
function createProtocolErrorMessage(object: {
error: {message: string; data: any; code: number}; error: {message: string; data: any; code: number};
}): string { }): string {
let message = `${object.error.message}`; let message = `${object.error.message}`;

View File

@ -16,21 +16,11 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js'; import {EventSubscription} from './EventEmitter.js';
import { import {debugError, PuppeteerURL} from './util.js';
addEventListener,
debugError,
PuppeteerEventListener,
PuppeteerURL,
removeEventListeners,
} from './util.js';
/**
* @internal
*/
export {PuppeteerEventListener};
/** /**
* The CoverageEntry class represents one entry of the coverage report. * The CoverageEntry class represents one entry of the coverage report.
@ -211,7 +201,7 @@ export class JSCoverage {
#enabled = false; #enabled = false;
#scriptURLs = new Map<string, string>(); #scriptURLs = new Map<string, string>();
#scriptSources = new Map<string, string>(); #scriptSources = new Map<string, string>();
#eventListeners: PuppeteerEventListener[] = []; #subscriptions?: DisposableStack;
#resetOnNavigation = false; #resetOnNavigation = false;
#reportAnonymousScripts = false; #reportAnonymousScripts = false;
#includeRawScriptCoverage = false; #includeRawScriptCoverage = false;
@ -248,18 +238,21 @@ export class JSCoverage {
this.#enabled = true; this.#enabled = true;
this.#scriptURLs.clear(); this.#scriptURLs.clear();
this.#scriptSources.clear(); this.#scriptSources.clear();
this.#eventListeners = [ this.#subscriptions = new DisposableStack();
addEventListener( this.#subscriptions.use(
new EventSubscription(
this.#client, this.#client,
'Debugger.scriptParsed', 'Debugger.scriptParsed',
this.#onScriptParsed.bind(this) this.#onScriptParsed.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
this.#client, this.#client,
'Runtime.executionContextsCleared', 'Runtime.executionContextsCleared',
this.#onExecutionContextsCleared.bind(this) this.#onExecutionContextsCleared.bind(this)
), )
]; );
await Promise.all([ await Promise.all([
this.#client.send('Profiler.enable'), this.#client.send('Profiler.enable'),
this.#client.send('Profiler.startPreciseCoverage', { this.#client.send('Profiler.startPreciseCoverage', {
@ -313,7 +306,7 @@ export class JSCoverage {
this.#client.send('Debugger.disable'), this.#client.send('Debugger.disable'),
]); ]);
removeEventListeners(this.#eventListeners); this.#subscriptions?.dispose();
const coverage = []; const coverage = [];
const profileResponse = result[0]; const profileResponse = result[0];
@ -350,7 +343,7 @@ export class CSSCoverage {
#enabled = false; #enabled = false;
#stylesheetURLs = new Map<string, string>(); #stylesheetURLs = new Map<string, string>();
#stylesheetSources = new Map<string, string>(); #stylesheetSources = new Map<string, string>();
#eventListeners: PuppeteerEventListener[] = []; #eventListeners?: DisposableStack;
#resetOnNavigation = false; #resetOnNavigation = false;
constructor(client: CDPSession) { constructor(client: CDPSession) {
@ -371,18 +364,21 @@ export class CSSCoverage {
this.#enabled = true; this.#enabled = true;
this.#stylesheetURLs.clear(); this.#stylesheetURLs.clear();
this.#stylesheetSources.clear(); this.#stylesheetSources.clear();
this.#eventListeners = [ this.#eventListeners = new DisposableStack();
addEventListener( this.#eventListeners.use(
new EventSubscription(
this.#client, this.#client,
'CSS.styleSheetAdded', 'CSS.styleSheetAdded',
this.#onStyleSheet.bind(this) this.#onStyleSheet.bind(this)
), )
addEventListener( );
this.#eventListeners.use(
new EventSubscription(
this.#client, this.#client,
'Runtime.executionContextsCleared', 'Runtime.executionContextsCleared',
this.#onExecutionContextsCleared.bind(this) this.#onExecutionContextsCleared.bind(this)
), )
]; );
await Promise.all([ await Promise.all([
this.#client.send('DOM.enable'), this.#client.send('DOM.enable'),
this.#client.send('CSS.enable'), this.#client.send('CSS.enable'),
@ -426,7 +422,7 @@ export class CSSCoverage {
this.#client.send('CSS.disable'), this.#client.send('CSS.disable'),
this.#client.send('DOM.disable'), this.#client.send('DOM.disable'),
]); ]);
removeEventListeners(this.#eventListeners); this.#eventListeners?.dispose();
// aggregate by styleSheetId // aggregate by styleSheetId
const styleSheetIdToCoverage = new Map(); const styleSheetIdToCoverage = new Map();

View File

@ -18,6 +18,8 @@ import {describe, it} from 'node:test';
import expect from 'expect'; import expect from 'expect';
import {CDPSessionEvents} from '../api/CDPSession.js';
import { import {
DeviceRequestPrompt, DeviceRequestPrompt,
DeviceRequestPromptDevice, DeviceRequestPromptDevice,
@ -27,7 +29,7 @@ import {TimeoutError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
class MockCDPSession extends EventEmitter { class MockCDPSession extends EventEmitter<CDPSessionEvents> {
async send(): Promise<any> {} async send(): Promise<any> {}
connection() { connection() {
return undefined; return undefined;

View File

@ -16,11 +16,11 @@
import Protocol from 'devtools-protocol'; import Protocol from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {WaitTimeoutOptions} from '../api/Page.js'; import {WaitTimeoutOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession} from './Connection.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
/** /**

View File

@ -16,10 +16,9 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {Dialog} from '../api/Dialog.js'; import {Dialog} from '../api/Dialog.js';
import {CDPSession} from './Connection.js';
/** /**
* @internal * @internal
*/ */

View File

@ -16,12 +16,12 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {AutofillData, ElementHandle, Point} from '../api/ElementHandle.js'; import {AutofillData, ElementHandle, Point} from '../api/ElementHandle.js';
import {Page, ScreenshotOptions} from '../api/Page.js'; import {Page, ScreenshotOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {throwIfDisposed} from '../util/decorators.js'; import {throwIfDisposed} from '../util/decorators.js';
import {CDPSession} from './Connection.js';
import {CDPFrame} from './Frame.js'; import {CDPFrame} from './Frame.js';
import {FrameManager} from './FrameManager.js'; import {FrameManager} from './FrameManager.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';

View File

@ -15,12 +15,12 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {GeolocationOptions, MediaFeature} from '../api/Page.js'; import {GeolocationOptions, MediaFeature} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {invokeAtMostOnceForArguments} from '../util/decorators.js'; import {invokeAtMostOnceForArguments} from '../util/decorators.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession, CDPSessionEmittedEvents} from './Connection.js';
import {Viewport} from './PuppeteerViewport.js'; import {Viewport} from './PuppeteerViewport.js';
import {debugError} from './util.js'; import {debugError} from './util.js';
@ -168,8 +168,8 @@ export class EmulationManager {
async registerSpeculativeSession(client: CDPSession): Promise<void> { async registerSpeculativeSession(client: CDPSession): Promise<void> {
this.#secondaryClients.add(client); this.#secondaryClients.add(client);
client.once(CDPSessionEmittedEvents.Disconnected, () => { client.once(CDPSessionEvent.Disconnected, () => {
return this.#secondaryClients.delete(client); this.#secondaryClients.delete(client);
}); });
// We don't await here because we want to register all state changes before // We don't await here because we want to register all state changes before
// the target is unpaused. // the target is unpaused.

View File

@ -22,7 +22,7 @@ import sinon from 'sinon';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
describe('EventEmitter', () => { describe('EventEmitter', () => {
let emitter: EventEmitter; let emitter: EventEmitter<Record<string, unknown>>;
beforeEach(() => { beforeEach(() => {
emitter = new EventEmitter(); emitter = new EventEmitter();
@ -33,7 +33,7 @@ describe('EventEmitter', () => {
it(`${methodName}: adds an event listener that is fired when the event is emitted`, () => { it(`${methodName}: adds an event listener that is fired when the event is emitted`, () => {
const listener = sinon.spy(); const listener = sinon.spy();
emitter[methodName]('foo', listener); emitter[methodName]('foo', listener);
emitter.emit('foo'); emitter.emit('foo', undefined);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
}); });
@ -62,10 +62,10 @@ describe('EventEmitter', () => {
it(`${methodName}: removes the listener so it is no longer called`, () => { it(`${methodName}: removes the listener so it is no longer called`, () => {
const listener = sinon.spy(); const listener = sinon.spy();
emitter.on('foo', listener); emitter.on('foo', listener);
emitter.emit('foo'); emitter.emit('foo', undefined);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
emitter.off('foo', listener); emitter.off('foo', listener);
emitter.emit('foo'); emitter.emit('foo', undefined);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
}); });
@ -85,9 +85,9 @@ describe('EventEmitter', () => {
it('only calls the listener once and then removes it', () => { it('only calls the listener once and then removes it', () => {
const listener = sinon.spy(); const listener = sinon.spy();
emitter.once('foo', listener); emitter.once('foo', listener);
emitter.emit('foo'); emitter.emit('foo', undefined);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
emitter.emit('foo'); emitter.emit('foo', undefined);
expect(listener.callCount).toEqual(1); expect(listener.callCount).toEqual(1);
}); });
@ -105,7 +105,7 @@ describe('EventEmitter', () => {
const listener3 = sinon.spy(); const listener3 = sinon.spy();
emitter.on('foo', listener1).on('foo', listener2).on('bar', listener3); emitter.on('foo', listener1).on('foo', listener2).on('bar', listener3);
emitter.emit('foo'); emitter.emit('foo', undefined);
expect(listener1.callCount).toEqual(1); expect(listener1.callCount).toEqual(1);
expect(listener2.callCount).toEqual(1); expect(listener2.callCount).toEqual(1);
@ -125,13 +125,13 @@ describe('EventEmitter', () => {
it('returns true if the event has listeners', () => { it('returns true if the event has listeners', () => {
const listener = sinon.spy(); const listener = sinon.spy();
emitter.on('foo', listener); emitter.on('foo', listener);
expect(emitter.emit('foo')).toBe(true); expect(emitter.emit('foo', undefined)).toBe(true);
}); });
it('returns false if the event has listeners', () => { it('returns false if the event has listeners', () => {
const listener = sinon.spy(); const listener = sinon.spy();
emitter.on('foo', listener); emitter.on('foo', listener);
expect(emitter.emit('notFoo')).toBe(false); expect(emitter.emit('notFoo', undefined)).toBe(false);
}); });
}); });
@ -151,8 +151,8 @@ describe('EventEmitter', () => {
emitter.on('foo', () => {}).on('bar', () => {}); emitter.on('foo', () => {}).on('bar', () => {});
emitter.removeAllListeners(); emitter.removeAllListeners();
expect(emitter.emit('foo')).toBe(false); expect(emitter.emit('foo', undefined)).toBe(false);
expect(emitter.emit('bar')).toBe(false); expect(emitter.emit('bar', undefined)).toBe(false);
}); });
it('returns the emitter for chaining', () => { it('returns the emitter for chaining', () => {
@ -166,8 +166,8 @@ describe('EventEmitter', () => {
.on('bar', () => {}); .on('bar', () => {});
emitter.removeAllListeners('bar'); emitter.removeAllListeners('bar');
expect(emitter.emit('foo')).toBe(true); expect(emitter.emit('foo', undefined)).toBe(true);
expect(emitter.emit('bar')).toBe(false); expect(emitter.emit('bar', undefined)).toBe(false);
}); });
}); });
}); });

View File

@ -14,12 +14,20 @@
* limitations under the License. * limitations under the License.
*/ */
import mitt, {Emitter, EventHandlerMap} from '../../third_party/mitt/index.js'; import {Symbol} from '../../third_party/disposablestack/disposablestack.js';
import mitt, {
Emitter,
EventHandlerMap,
EventType,
} from '../../third_party/mitt/index.js';
export {
/** /**
* @public * @public
*/ */
export type EventType = string | symbol; EventType,
} from '../../third_party/mitt/index.js';
/** /**
* @public * @public
*/ */
@ -28,22 +36,42 @@ export type Handler<T = unknown> = (event: T) => void;
/** /**
* @public * @public
*/ */
export interface CommonEventEmitter { export interface CommonEventEmitter<Events extends Record<EventType, unknown>> {
on(event: EventType, handler: Handler): this; on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): this;
off(event: EventType, handler: Handler): this; off<Key extends keyof Events>(
type: Key,
handler?: Handler<Events[Key]>
): this;
emit<Key extends keyof Events>(type: Key, event: Events[Key]): boolean;
/* To maintain parity with the built in NodeJS event emitter which uses removeListener /* To maintain parity with the built in NodeJS event emitter which uses removeListener
* rather than `off`. * rather than `off`.
* If you're implementing new code you should use `off`. * If you're implementing new code you should use `off`.
*/ */
addListener(event: EventType, handler: Handler): this; addListener<Key extends keyof Events>(
removeListener(event: EventType, handler: Handler): this; type: Key,
emit(event: EventType, eventData?: unknown): boolean; handler: Handler<Events[Key]>
once(event: EventType, handler: Handler): this; ): this;
listenerCount(event: string): number; removeListener<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
once<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
listenerCount(event: keyof Events): number;
removeAllListeners(event?: EventType): this; removeAllListeners(event?: keyof Events): this;
} }
/**
* @public
*/
export type EventsWithWildcard<Events extends Record<EventType, unknown>> =
Events & {
'*': Events[keyof Events];
};
/** /**
* The EventEmitter class that many Puppeteer classes extend. * The EventEmitter class that many Puppeteer classes extend.
* *
@ -56,110 +84,153 @@ export interface CommonEventEmitter {
* *
* @public * @public
*/ */
export class EventEmitter implements CommonEventEmitter { export class EventEmitter<Events extends Record<EventType, unknown>>
private emitter: Emitter<Record<string | symbol, any>>; implements CommonEventEmitter<EventsWithWildcard<Events>>
private eventsMap: EventHandlerMap<Record<string | symbol, any>> = new Map(); {
#emitter: Emitter<Events & {'*': Events[keyof Events]}>;
#handlers: EventHandlerMap<Events & {'*': Events[keyof Events]}> = new Map();
/** /**
* @internal * @internal
*/ */
constructor() { constructor() {
this.emitter = mitt(this.eventsMap); this.#emitter = mitt(this.#handlers);
} }
/** /**
* Bind an event listener to fire when an event occurs. * Bind an event listener to fire when an event occurs.
* @param event - the event type you'd like to listen to. Can be a string or symbol. * @param type - the event type you'd like to listen to. Can be a string or symbol.
* @param handler - the function to be called when the event occurs. * @param handler - the function to be called when the event occurs.
* @returns `this` to enable you to chain method calls. * @returns `this` to enable you to chain method calls.
*/ */
on(event: EventType, handler: Handler<any>): this { on<Key extends keyof EventsWithWildcard<Events>>(
this.emitter.on(event, handler); type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.#emitter.on(type, handler);
return this; return this;
} }
/** /**
* Remove an event listener from firing. * Remove an event listener from firing.
* @param event - the event type you'd like to stop listening to. * @param type - the event type you'd like to stop listening to.
* @param handler - the function that should be removed. * @param handler - the function that should be removed.
* @returns `this` to enable you to chain method calls. * @returns `this` to enable you to chain method calls.
*/ */
off(event: EventType, handler: Handler<any>): this { off<Key extends keyof EventsWithWildcard<Events>>(
this.emitter.off(event, handler); type: Key,
handler?: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.#emitter.off(type, handler);
return this; return this;
} }
/** /**
* Remove an event listener. * Remove an event listener.
*
* @deprecated please use {@link EventEmitter.off} instead. * @deprecated please use {@link EventEmitter.off} instead.
*/ */
removeListener(event: EventType, handler: Handler<any>): this { removeListener<Key extends keyof EventsWithWildcard<Events>>(
this.off(event, handler); type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.off(type, handler);
return this; return this;
} }
/** /**
* Add an event listener. * Add an event listener.
*
* @deprecated please use {@link EventEmitter.on} instead. * @deprecated please use {@link EventEmitter.on} instead.
*/ */
addListener(event: EventType, handler: Handler<any>): this { addListener<Key extends keyof EventsWithWildcard<Events>>(
this.on(event, handler); type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.on(type, handler);
return this; return this;
} }
/** /**
* Emit an event and call any associated listeners. * Emit an event and call any associated listeners.
* *
* @param event - the event you'd like to emit * @param type - the event you'd like to emit
* @param eventData - any data you'd like to emit with the event * @param eventData - any data you'd like to emit with the event
* @returns `true` if there are any listeners, `false` if there are not. * @returns `true` if there are any listeners, `false` if there are not.
*/ */
emit(event: EventType, eventData?: unknown): boolean { emit<Key extends keyof EventsWithWildcard<Events>>(
this.emitter.emit(event, eventData); type: Key,
return this.eventListenersCount(event) > 0; event: EventsWithWildcard<Events>[Key]
): boolean {
this.#emitter.emit(type, event);
return this.listenerCount(type) > 0;
} }
/** /**
* Like `on` but the listener will only be fired once and then it will be removed. * Like `on` but the listener will only be fired once and then it will be removed.
* @param event - the event you'd like to listen to * @param type - the event you'd like to listen to
* @param handler - the handler function to run when the event occurs * @param handler - the handler function to run when the event occurs
* @returns `this` to enable you to chain method calls. * @returns `this` to enable you to chain method calls.
*/ */
once(event: EventType, handler: Handler<any>): this { once<Key extends keyof EventsWithWildcard<Events>>(
const onceHandler: Handler<any> = eventData => { type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
const onceHandler: Handler<EventsWithWildcard<Events>[Key]> = eventData => {
handler(eventData); handler(eventData);
this.off(event, onceHandler); this.off(type, onceHandler);
}; };
return this.on(event, onceHandler); return this.on(type, onceHandler);
} }
/** /**
* Gets the number of listeners for a given event. * Gets the number of listeners for a given event.
* *
* @param event - the event to get the listener count for * @param type - the event to get the listener count for
* @returns the number of listeners bound to the given event * @returns the number of listeners bound to the given event
*/ */
listenerCount(event: EventType): number { listenerCount(type: keyof EventsWithWildcard<Events>): number {
return this.eventListenersCount(event); return this.#handlers.get(type)?.length || 0;
} }
/** /**
* Removes all listeners. If given an event argument, it will remove only * Removes all listeners. If given an event argument, it will remove only
* listeners for that event. * listeners for that event.
* @param event - the event to remove listeners for. *
* @param type - the event to remove listeners for.
* @returns `this` to enable you to chain method calls. * @returns `this` to enable you to chain method calls.
*/ */
removeAllListeners(event?: EventType): this { removeAllListeners(type?: keyof EventsWithWildcard<Events>): this {
if (event) { if (type === undefined || type === '*') {
this.eventsMap.delete(event); this.#handlers.clear();
} else { } else {
this.eventsMap.clear(); this.#handlers.delete(type);
} }
return this; return this;
} }
}
private eventListenersCount(event: EventType): number { /**
return this.eventsMap.get(event)?.length || 0; * @internal
*/
export class EventSubscription<
Target extends CommonEventEmitter<Record<Type, Event>>,
Type extends EventType = EventType,
Event = unknown,
> {
#target: Target;
#type: Type;
#handler: Handler<Event>;
constructor(target: Target, type: Type, handler: Handler<Event>) {
this.#target = target;
this.#type = type;
this.#handler = handler;
this.#target.on(this.#type, this.#handler);
}
[Symbol.dispose](): void {
this.#target.off(this.#type, this.#handler);
} }
} }

View File

@ -16,6 +16,7 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import type {ElementHandle} from '../api/ElementHandle.js'; import type {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
import type PuppeteerUtil from '../injected/injected.js'; import type PuppeteerUtil from '../injected/injected.js';
@ -24,7 +25,6 @@ import {stringifyFunction} from '../util/Function.js';
import {ARIAQueryHandler} from './AriaQueryHandler.js'; import {ARIAQueryHandler} from './AriaQueryHandler.js';
import {Binding} from './Binding.js'; import {Binding} from './Binding.js';
import {CDPSession} from './Connection.js';
import {CDPElementHandle} from './ElementHandle.js'; import {CDPElementHandle} from './ElementHandle.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {CDPJSHandle} from './JSHandle.js'; import {CDPJSHandle} from './JSHandle.js';
@ -47,25 +47,6 @@ const getSourceUrlComment = (url: string) => {
}; };
/** /**
* Represents a context for JavaScript execution.
*
* @example
* 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.realm} 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}.
*
* @internal * @internal
*/ */
export class ExecutionContext { export class ExecutionContext {

View File

@ -17,16 +17,18 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {TargetFilterCallback} from '../api/Browser.js'; import {TargetFilterCallback} from '../api/Browser.js';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession, CDPSessionEmittedEvents, Connection} from './Connection.js'; import {Connection} from './Connection.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {CDPTarget} from './Target.js'; import {CDPTarget} from './Target.js';
import { import {
TargetFactory, TargetFactory,
TargetManagerEmittedEvents, TargetManagerEvent,
TargetManager, TargetManager,
TargetManagerEvents,
} from './TargetManager.js'; } from './TargetManager.js';
/** /**
@ -44,7 +46,7 @@ import {
* @internal * @internal
*/ */
export class FirefoxTargetManager export class FirefoxTargetManager
extends EventEmitter extends EventEmitter<TargetManagerEvents>
implements TargetManager implements TargetManager
{ {
#connection: Connection; #connection: Connection;
@ -98,7 +100,10 @@ export class FirefoxTargetManager
this.#connection.on('Target.targetCreated', this.#onTargetCreated); this.#connection.on('Target.targetCreated', this.#onTargetCreated);
this.#connection.on('Target.targetDestroyed', this.#onTargetDestroyed); this.#connection.on('Target.targetDestroyed', this.#onTargetDestroyed);
this.#connection.on('sessiondetached', this.#onSessionDetached); this.#connection.on(
CDPSessionEvent.SessionDetached,
this.#onSessionDetached
);
this.setupAttachmentListeners(this.#connection); this.setupAttachmentListeners(this.#connection);
} }
@ -172,7 +177,7 @@ export class FirefoxTargetManager
} }
target._initialize(); target._initialize();
this.#availableTargetsByTargetId.set(event.targetInfo.targetId, target); this.#availableTargetsByTargetId.set(event.targetInfo.targetId, target);
this.emit(TargetManagerEmittedEvents.TargetAvailable, target); this.emit(TargetManagerEvent.TargetAvailable, target);
this.#finishInitializationIfReady(target._targetId); this.#finishInitializationIfReady(target._targetId);
}; };
@ -181,7 +186,7 @@ export class FirefoxTargetManager
this.#finishInitializationIfReady(event.targetId); this.#finishInitializationIfReady(event.targetId);
const target = this.#availableTargetsByTargetId.get(event.targetId); const target = this.#availableTargetsByTargetId.get(event.targetId);
if (target) { if (target) {
this.emit(TargetManagerEmittedEvents.TargetGone, target); this.emit(TargetManagerEvent.TargetGone, target);
this.#availableTargetsByTargetId.delete(event.targetId); this.#availableTargetsByTargetId.delete(event.targetId);
} }
}; };
@ -207,7 +212,7 @@ export class FirefoxTargetManager
this.#availableTargetsByTargetId.get(targetInfo.targetId)! this.#availableTargetsByTargetId.get(targetInfo.targetId)!
); );
parentSession.emit(CDPSessionEmittedEvents.Ready, session); parentSession.emit(CDPSessionEvent.Ready, session);
}; };
#finishInitializationIfReady(targetId: string): void { #finishInitializationIfReady(targetId: string): void {

View File

@ -16,14 +16,14 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {Frame, throwIfDetached} from '../api/Frame.js'; import {CDPSession} from '../api/CDPSession.js';
import {Frame, FrameEvent, throwIfDetached} from '../api/Frame.js';
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {Page, WaitTimeoutOptions} from '../api/Page.js'; import {Page, WaitTimeoutOptions} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
import { import {
DeviceRequestPrompt, DeviceRequestPrompt,
DeviceRequestPromptManager, DeviceRequestPromptManager,
@ -34,21 +34,6 @@ import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js'; import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {setPageContent} from './util.js'; import {setPageContent} from './util.js';
/**
* We use symbols to prevent external parties listening to these events.
* They are internal to Puppeteer.
*
* @internal
*/
export const FrameEmittedEvents = {
FrameNavigated: Symbol('Frame.FrameNavigated'),
FrameSwapped: Symbol('Frame.FrameSwapped'),
LifecycleEvent: Symbol('Frame.LifecycleEvent'),
FrameNavigatedWithinDocument: Symbol('Frame.FrameNavigatedWithinDocument'),
FrameDetached: Symbol('Frame.FrameDetached'),
FrameSwappedByActivation: Symbol('Frame.FrameSwappedByActivation'),
};
/** /**
* @internal * @internal
*/ */
@ -80,7 +65,7 @@ export class CDPFrame extends Frame {
this.updateClient(client); this.updateClient(client);
this.on(FrameEmittedEvents.FrameSwappedByActivation, () => { this.on(FrameEvent.FrameSwappedByActivation, () => {
// Emulate loading process for swapped frames. // Emulate loading process for swapped frames.
this._onLoadingStarted(); this._onLoadingStarted();
this._onLoadingStopped(); this._onLoadingStopped();

View File

@ -16,21 +16,19 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {FrameEvent} from '../api/Frame.js';
import {Page} from '../api/Page.js'; import {Page} from '../api/Page.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import { import {CDPCDPSession} from './CDPSession.js';
CDPSession, import {isTargetClosedError} from './Connection.js';
CDPSessionEmittedEvents,
CDPSessionImpl,
isTargetClosedError,
} from './Connection.js';
import {DeviceRequestPromptManager} from './DeviceRequestPrompt.js'; import {DeviceRequestPromptManager} from './DeviceRequestPrompt.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter, EventType} from './EventEmitter.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {CDPFrame, FrameEmittedEvents} from './Frame.js'; import {CDPFrame} from './Frame.js';
import {FrameTree} from './FrameTree.js'; import {FrameTree} from './FrameTree.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
@ -50,16 +48,30 @@ export const UTILITY_WORLD_NAME = '__puppeteer_utility_world__';
* *
* @internal * @internal
*/ */
export const FrameManagerEmittedEvents = { // eslint-disable-next-line @typescript-eslint/no-namespace
FrameAttached: Symbol('FrameManager.FrameAttached'), export namespace FrameManagerEvent {
FrameNavigated: Symbol('FrameManager.FrameNavigated'), export const FrameAttached = Symbol('FrameManager.FrameAttached');
FrameDetached: Symbol('FrameManager.FrameDetached'), export const FrameNavigated = Symbol('FrameManager.FrameNavigated');
FrameSwapped: Symbol('FrameManager.FrameSwapped'), export const FrameDetached = Symbol('FrameManager.FrameDetached');
LifecycleEvent: Symbol('FrameManager.LifecycleEvent'), export const FrameSwapped = Symbol('FrameManager.FrameSwapped');
FrameNavigatedWithinDocument: Symbol( export const LifecycleEvent = Symbol('FrameManager.LifecycleEvent');
export const FrameNavigatedWithinDocument = Symbol(
'FrameManager.FrameNavigatedWithinDocument' 'FrameManager.FrameNavigatedWithinDocument'
), );
}; }
/**
* @internal
*/
export interface FrameManagerEvents extends Record<EventType, unknown> {
[FrameManagerEvent.FrameAttached]: CDPFrame;
[FrameManagerEvent.FrameNavigated]: CDPFrame;
[FrameManagerEvent.FrameDetached]: CDPFrame;
[FrameManagerEvent.FrameSwapped]: CDPFrame;
[FrameManagerEvent.LifecycleEvent]: CDPFrame;
[FrameManagerEvent.FrameNavigatedWithinDocument]: CDPFrame;
}
const TIME_FOR_WAITING_FOR_SWAP = 100; // ms. const TIME_FOR_WAITING_FOR_SWAP = 100; // ms.
@ -68,7 +80,7 @@ const TIME_FOR_WAITING_FOR_SWAP = 100; // ms.
* *
* @internal * @internal
*/ */
export class FrameManager extends EventEmitter { export class FrameManager extends EventEmitter<FrameManagerEvents> {
#page: Page; #page: Page;
#networkManager: NetworkManager; #networkManager: NetworkManager;
#timeoutSettings: TimeoutSettings; #timeoutSettings: TimeoutSettings;
@ -114,7 +126,7 @@ export class FrameManager extends EventEmitter {
this.#networkManager = new NetworkManager(ignoreHTTPSErrors, this); this.#networkManager = new NetworkManager(ignoreHTTPSErrors, this);
this.#timeoutSettings = timeoutSettings; this.#timeoutSettings = timeoutSettings;
this.setupEventListeners(this.#client); this.setupEventListeners(this.#client);
client.once(CDPSessionEmittedEvents.Disconnected, () => { client.once(CDPSessionEvent.Disconnected, () => {
this.#onClientDisconnect().catch(debugError); this.#onClientDisconnect().catch(debugError);
}); });
} }
@ -136,7 +148,7 @@ export class FrameManager extends EventEmitter {
timeout: TIME_FOR_WAITING_FOR_SWAP, timeout: TIME_FOR_WAITING_FOR_SWAP,
message: 'Frame was not swapped', message: 'Frame was not swapped',
}); });
mainFrame.once(FrameEmittedEvents.FrameSwappedByActivation, () => { mainFrame.once(FrameEvent.FrameSwappedByActivation, () => {
swapped.resolve(); swapped.resolve();
}); });
try { try {
@ -156,7 +168,7 @@ export class FrameManager extends EventEmitter {
this.#client = client; this.#client = client;
assert( assert(
this.#client instanceof CDPSessionImpl, this.#client instanceof CDPCDPSession,
'CDPSession is not an instance of CDPSessionImpl.' 'CDPSession is not an instance of CDPSessionImpl.'
); );
const frame = this._frameTree.getMainFrame(); const frame = this._frameTree.getMainFrame();
@ -170,17 +182,17 @@ export class FrameManager extends EventEmitter {
frame.updateClient(client, true); frame.updateClient(client, true);
} }
this.setupEventListeners(client); this.setupEventListeners(client);
client.once(CDPSessionEmittedEvents.Disconnected, () => { client.once(CDPSessionEvent.Disconnected, () => {
this.#onClientDisconnect().catch(debugError); this.#onClientDisconnect().catch(debugError);
}); });
await this.initialize(client); await this.initialize(client);
await this.#networkManager.addClient(client); await this.#networkManager.addClient(client);
if (frame) { if (frame) {
frame.emit(FrameEmittedEvents.FrameSwappedByActivation); frame.emit(FrameEvent.FrameSwappedByActivation, undefined);
} }
} }
async registerSpeculativeSession(client: CDPSessionImpl): Promise<void> { async registerSpeculativeSession(client: CDPCDPSession): Promise<void> {
await this.#networkManager.addClient(client); await this.#networkManager.addClient(client);
} }
@ -312,8 +324,8 @@ export class FrameManager extends EventEmitter {
return; return;
} }
frame._onLifecycleEvent(event.loaderId, event.name); frame._onLifecycleEvent(event.loaderId, event.name);
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame); this.emit(FrameManagerEvent.LifecycleEvent, frame);
frame.emit(FrameEmittedEvents.LifecycleEvent); frame.emit(FrameEvent.LifecycleEvent, undefined);
} }
#onFrameStartedLoading(frameId: string): void { #onFrameStartedLoading(frameId: string): void {
@ -330,8 +342,8 @@ export class FrameManager extends EventEmitter {
return; return;
} }
frame._onLoadingStopped(); frame._onLoadingStopped();
this.emit(FrameManagerEmittedEvents.LifecycleEvent, frame); this.emit(FrameManagerEvent.LifecycleEvent, frame);
frame.emit(FrameEmittedEvents.LifecycleEvent); frame.emit(FrameEvent.LifecycleEvent, undefined);
} }
#handleFrameTree( #handleFrameTree(
@ -378,7 +390,7 @@ export class FrameManager extends EventEmitter {
frame = new CDPFrame(this, frameId, parentFrameId, session); frame = new CDPFrame(this, frameId, parentFrameId, session);
this._frameTree.addFrame(frame); this._frameTree.addFrame(frame);
this.emit(FrameManagerEmittedEvents.FrameAttached, frame); this.emit(FrameManagerEvent.FrameAttached, frame);
} }
async #onFrameNavigated( async #onFrameNavigated(
@ -412,8 +424,8 @@ export class FrameManager extends EventEmitter {
frame = await this._frameTree.waitForFrame(frameId); frame = await this._frameTree.waitForFrame(frameId);
frame._navigated(framePayload); frame._navigated(framePayload);
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame); this.emit(FrameManagerEvent.FrameNavigated, frame);
frame.emit(FrameEmittedEvents.FrameNavigated, navigationType); frame.emit(FrameEvent.FrameNavigated, navigationType);
} }
async #createIsolatedWorld(session: CDPSession, name: string): Promise<void> { async #createIsolatedWorld(session: CDPSession, name: string): Promise<void> {
@ -455,10 +467,10 @@ export class FrameManager extends EventEmitter {
return; return;
} }
frame._navigatedWithinDocument(url); frame._navigatedWithinDocument(url);
this.emit(FrameManagerEmittedEvents.FrameNavigatedWithinDocument, frame); this.emit(FrameManagerEvent.FrameNavigatedWithinDocument, frame);
frame.emit(FrameEmittedEvents.FrameNavigatedWithinDocument); frame.emit(FrameEvent.FrameNavigatedWithinDocument, undefined);
this.emit(FrameManagerEmittedEvents.FrameNavigated, frame); this.emit(FrameManagerEvent.FrameNavigated, frame);
frame.emit(FrameEmittedEvents.FrameNavigated); frame.emit(FrameEvent.FrameNavigated, 'Navigation');
} }
#onFrameDetached( #onFrameDetached(
@ -466,16 +478,20 @@ export class FrameManager extends EventEmitter {
reason: Protocol.Page.FrameDetachedEventReason reason: Protocol.Page.FrameDetachedEventReason
): void { ): void {
const frame = this.frame(frameId); const frame = this.frame(frameId);
if (reason === 'remove') { if (!frame) {
return;
}
switch (reason) {
case 'remove':
// Only remove the frame if the reason for the detached event is // Only remove the frame if the reason for the detached event is
// an actual removement of the frame. // an actual removement of the frame.
// For frames that become OOP iframes, the reason would be 'swap'. // For frames that become OOP iframes, the reason would be 'swap'.
if (frame) {
this.#removeFramesRecursively(frame); this.#removeFramesRecursively(frame);
} break;
} else if (reason === 'swap') { case 'swap':
this.emit(FrameManagerEmittedEvents.FrameSwapped, frame); this.emit(FrameManagerEvent.FrameSwapped, frame);
frame?.emit(FrameEmittedEvents.FrameSwapped); frame.emit(FrameEvent.FrameSwapped, undefined);
break;
} }
} }
@ -555,7 +571,7 @@ export class FrameManager extends EventEmitter {
} }
frame[Symbol.dispose](); frame[Symbol.dispose]();
this._frameTree.removeFrame(frame); this._frameTree.removeFrame(frame);
this.emit(FrameManagerEmittedEvents.FrameDetached, frame); this.emit(FrameManagerEvent.FrameDetached, frame);
frame.emit(FrameEmittedEvents.FrameDetached, frame); frame.emit(FrameEvent.FrameDetached, frame);
} }
} }

View File

@ -15,12 +15,13 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {Frame} from '../api/Frame.js'; import {Frame} from '../api/Frame.js';
import { import {
ContinueRequestOverrides, ContinueRequestOverrides,
ErrorCode, ErrorCode,
headersArray, headersArray,
HTTPRequest as BaseHTTPRequest, HTTPRequest,
InterceptResolutionAction, InterceptResolutionAction,
InterceptResolutionState, InterceptResolutionState,
ResourceType, ResourceType,
@ -30,20 +31,19 @@ import {
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {ProtocolError} from './Errors.js'; import {ProtocolError} from './Errors.js';
import {debugError, isString} from './util.js'; import {debugError, isString} from './util.js';
/** /**
* @internal * @internal
*/ */
export class HTTPRequest extends BaseHTTPRequest { export class CDPHTTPRequest extends HTTPRequest {
override _requestId: string; override _requestId: string;
override _interceptionId: string | undefined; override _interceptionId: string | undefined;
override _failureText: string | null = null; override _failureText: string | null = null;
override _response: HTTPResponse | null = null; override _response: HTTPResponse | null = null;
override _fromMemoryCache = false; override _fromMemoryCache = false;
override _redirectChain: HTTPRequest[]; override _redirectChain: CDPHTTPRequest[];
#client: CDPSession; #client: CDPSession;
#isNavigationRequest: boolean; #isNavigationRequest: boolean;
@ -100,7 +100,7 @@ export class HTTPRequest extends BaseHTTPRequest {
*/ */
type?: Protocol.Network.ResourceType; type?: Protocol.Network.ResourceType;
}, },
redirectChain: HTTPRequest[] redirectChain: CDPHTTPRequest[]
) { ) {
super(); super();
this.#client = client; this.#client = client;
@ -213,7 +213,7 @@ export class HTTPRequest extends BaseHTTPRequest {
return this.#initiator; return this.#initiator;
} }
override redirectChain(): HTTPRequest[] { override redirectChain(): CDPHTTPRequest[] {
return this._redirectChain.slice(); return this._redirectChain.slice();
} }

View File

@ -15,24 +15,21 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {Frame} from '../api/Frame.js'; import {Frame} from '../api/Frame.js';
import { import {HTTPResponse, RemoteAddress} from '../api/HTTPResponse.js';
HTTPResponse as BaseHTTPResponse,
RemoteAddress,
} from '../api/HTTPResponse.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession} from './Connection.js';
import {ProtocolError} from './Errors.js'; import {ProtocolError} from './Errors.js';
import {HTTPRequest} from './HTTPRequest.js'; import {CDPHTTPRequest} from './HTTPRequest.js';
import {SecurityDetails} from './SecurityDetails.js'; import {SecurityDetails} from './SecurityDetails.js';
/** /**
* @internal * @internal
*/ */
export class HTTPResponse extends BaseHTTPResponse { export class CDPHTTPResponse extends HTTPResponse {
#client: CDPSession; #client: CDPSession;
#request: HTTPRequest; #request: CDPHTTPRequest;
#contentPromise: Promise<Buffer> | null = null; #contentPromise: Promise<Buffer> | null = null;
#bodyLoadedDeferred = Deferred.create<Error | void>(); #bodyLoadedDeferred = Deferred.create<Error | void>();
#remoteAddress: RemoteAddress; #remoteAddress: RemoteAddress;
@ -47,7 +44,7 @@ export class HTTPResponse extends BaseHTTPResponse {
constructor( constructor(
client: CDPSession, client: CDPSession,
request: HTTPRequest, request: CDPHTTPRequest,
responsePayload: Protocol.Network.Response, responsePayload: Protocol.Network.Response,
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
) { ) {
@ -171,7 +168,7 @@ export class HTTPResponse extends BaseHTTPResponse {
return this.#contentPromise; return this.#contentPromise;
} }
override request(): HTTPRequest { override request(): CDPHTTPRequest {
return this.#request; return this.#request;
} }

View File

@ -16,6 +16,7 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {Point} from '../api/ElementHandle.js'; import {Point} from '../api/ElementHandle.js';
import { import {
Keyboard, Keyboard,
@ -32,7 +33,6 @@ import {
} from '../api/Input.js'; } from '../api/Input.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession} from './Connection.js';
import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js'; import {_keyDefinitions, KeyDefinition, KeyInput} from './USKeyboardLayout.js';
type KeyDescription = Required< type KeyDescription = Required<

View File

@ -16,12 +16,12 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
import {Realm} from '../api/Realm.js'; import {Realm} from '../api/Realm.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {Binding} from './Binding.js'; import {Binding} from './Binding.js';
import {CDPSession} from './Connection.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {CDPFrame} from './Frame.js'; import {CDPFrame} from './Frame.js';
import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';

View File

@ -16,9 +16,9 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {JSHandle} from '../api/JSHandle.js'; import {JSHandle} from '../api/JSHandle.js';
import {CDPSession} from './Connection.js';
import type {CDPElementHandle} from './ElementHandle.js'; import type {CDPElementHandle} from './ElementHandle.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {releaseObject, valueFromRemoteObject} from './util.js'; import {releaseObject, valueFromRemoteObject} from './util.js';

View File

@ -16,20 +16,17 @@
import Protocol from 'devtools-protocol'; import Protocol from 'devtools-protocol';
import {Frame, FrameEvent} from '../api/Frame.js';
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {TimeoutError} from './Errors.js'; import {TimeoutError} from './Errors.js';
import {CDPFrame, FrameEmittedEvents} from './Frame.js'; import {EventSubscription} from './EventEmitter.js';
import {FrameManagerEmittedEvents} from './FrameManager.js'; import {CDPFrame} from './Frame.js';
import {HTTPRequest} from './HTTPRequest.js'; import {FrameManagerEvent} from './FrameManager.js';
import {NetworkManager, NetworkManagerEmittedEvents} from './NetworkManager.js'; import {CDPHTTPRequest} from './HTTPRequest.js';
import { import {NetworkManager, NetworkManagerEvent} from './NetworkManager.js';
addEventListener,
PuppeteerEventListener,
removeEventListeners,
} from './util.js';
/** /**
* @public * @public
*/ */
@ -65,8 +62,8 @@ export class LifecycleWatcher {
#expectedLifecycle: ProtocolLifeCycleEvent[]; #expectedLifecycle: ProtocolLifeCycleEvent[];
#frame: CDPFrame; #frame: CDPFrame;
#timeout: number; #timeout: number;
#navigationRequest: HTTPRequest | null = null; #navigationRequest: CDPHTTPRequest | null = null;
#eventListeners: PuppeteerEventListener[]; #subscriptions = new DisposableStack();
#initialLoaderId: string; #initialLoaderId: string;
#terminationDeferred: Deferred<Error>; #terminationDeferred: Deferred<Error>;
@ -99,55 +96,70 @@ export class LifecycleWatcher {
this.#frame = frame; this.#frame = frame;
this.#timeout = timeout; this.#timeout = timeout;
this.#eventListeners = [ this.#subscriptions.use(
addEventListener(
// Revert if TODO #1 is done // Revert if TODO #1 is done
new EventSubscription(
frame._frameManager, frame._frameManager,
FrameManagerEmittedEvents.LifecycleEvent, FrameManagerEvent.LifecycleEvent,
this.#checkLifecycleComplete.bind(this) this.#checkLifecycleComplete.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
frame, frame,
FrameEmittedEvents.FrameNavigatedWithinDocument, FrameEvent.FrameNavigatedWithinDocument,
this.#navigatedWithinDocument.bind(this) this.#navigatedWithinDocument.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
frame, frame,
FrameEmittedEvents.FrameNavigated, FrameEvent.FrameNavigated,
this.#navigated.bind(this) this.#navigated.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
frame, frame,
FrameEmittedEvents.FrameSwapped, FrameEvent.FrameSwapped,
this.#frameSwapped.bind(this) this.#frameSwapped.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
frame, frame,
FrameEmittedEvents.FrameSwappedByActivation, FrameEvent.FrameSwappedByActivation,
this.#frameSwapped.bind(this) this.#frameSwapped.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
frame, frame,
FrameEmittedEvents.FrameDetached, FrameEvent.FrameDetached,
this.#onFrameDetached.bind(this) this.#onFrameDetached.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
networkManager, networkManager,
NetworkManagerEmittedEvents.Request, NetworkManagerEvent.Request,
this.#onRequest.bind(this) this.#onRequest.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
networkManager, networkManager,
NetworkManagerEmittedEvents.Response, NetworkManagerEvent.Response,
this.#onResponse.bind(this) this.#onResponse.bind(this)
), )
addEventListener( );
this.#subscriptions.use(
new EventSubscription(
networkManager, networkManager,
NetworkManagerEmittedEvents.RequestFailed, NetworkManagerEvent.RequestFailed,
this.#onRequestFailed.bind(this) this.#onRequestFailed.bind(this)
), )
]; );
this.#terminationDeferred = Deferred.create<Error>({ this.#terminationDeferred = Deferred.create<Error>({
timeout: this.#timeout, timeout: this.#timeout,
message: `Navigation timeout of ${this.#timeout} ms exceeded`, message: `Navigation timeout of ${this.#timeout} ms exceeded`,
@ -156,7 +168,7 @@ export class LifecycleWatcher {
this.#checkLifecycleComplete(); this.#checkLifecycleComplete();
} }
#onRequest(request: HTTPRequest): void { #onRequest(request: CDPHTTPRequest): void {
if (request.frame() !== this.#frame || !request.isNavigationRequest()) { if (request.frame() !== this.#frame || !request.isNavigationRequest()) {
return; return;
} }
@ -171,7 +183,7 @@ export class LifecycleWatcher {
} }
} }
#onRequestFailed(request: HTTPRequest): void { #onRequestFailed(request: CDPHTTPRequest): void {
if (this.#navigationRequest?._requestId !== request._requestId) { if (this.#navigationRequest?._requestId !== request._requestId) {
return; return;
} }
@ -185,7 +197,7 @@ export class LifecycleWatcher {
this.#navigationResponseReceived?.resolve(); this.#navigationResponseReceived?.resolve();
} }
#onFrameDetached(frame: CDPFrame): void { #onFrameDetached(frame: Frame): void {
if (this.#frame === frame) { if (this.#frame === frame) {
this.#terminationDeferred.resolve( this.#terminationDeferred.resolve(
new Error('Navigating frame was detached') new Error('Navigating frame was detached')
@ -273,7 +285,7 @@ export class LifecycleWatcher {
} }
dispose(): void { dispose(): void {
removeEventListeners(this.#eventListeners); this.#subscriptions.dispose();
this.#terminationDeferred.resolve(new Error('LifecycleWatcher disposed')); this.#terminationDeferred.resolve(new Error('LifecycleWatcher disposed'));
} }
} }

View File

@ -16,7 +16,7 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {HTTPRequest} from './HTTPRequest.js'; import {CDPHTTPRequest} from './HTTPRequest.js';
/** /**
* @internal * @internal
@ -92,7 +92,7 @@ export class NetworkEventManager {
NetworkRequestId, NetworkRequestId,
Protocol.Fetch.RequestPausedEvent Protocol.Fetch.RequestPausedEvent
>(); >();
#httpRequestsMap = new Map<NetworkRequestId, HTTPRequest>(); #httpRequestsMap = new Map<NetworkRequestId, CDPHTTPRequest>();
/* /*
* The below maps are used to reconcile Network.responseReceivedExtraInfo * The below maps are used to reconcile Network.responseReceivedExtraInfo
@ -193,11 +193,14 @@ export class NetworkEventManager {
this.#requestPausedMap.set(networkRequestId, event); this.#requestPausedMap.set(networkRequestId, event);
} }
getRequest(networkRequestId: NetworkRequestId): HTTPRequest | undefined { getRequest(networkRequestId: NetworkRequestId): CDPHTTPRequest | undefined {
return this.#httpRequestsMap.get(networkRequestId); return this.#httpRequestsMap.get(networkRequestId);
} }
storeRequest(networkRequestId: NetworkRequestId, request: HTTPRequest): void { storeRequest(
networkRequestId: NetworkRequestId,
request: CDPHTTPRequest
): void {
this.#httpRequestsMap.set(networkRequestId, request); this.#httpRequestsMap.set(networkRequestId, request);
} }

View File

@ -18,17 +18,18 @@ import {describe, it} from 'node:test';
import expect from 'expect'; import expect from 'expect';
import {CDPSessionEvents} from '../api/CDPSession.js';
import {HTTPRequest} from '../api/HTTPRequest.js'; import {HTTPRequest} from '../api/HTTPRequest.js';
import {HTTPResponse} from '../api/HTTPResponse.js'; import {HTTPResponse} from '../api/HTTPResponse.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter} from './EventEmitter.js';
import {CDPFrame} from './Frame.js'; import {CDPFrame} from './Frame.js';
import {NetworkManager, NetworkManagerEmittedEvents} from './NetworkManager.js'; import {NetworkManager, NetworkManagerEvent} from './NetworkManager.js';
// TODO: develop a helper to generate fake network events for attributes that // TODO: develop a helper to generate fake network events for attributes that
// are not relevant for the network manager to make tests shorter. // are not relevant for the network manager to make tests shorter.
class MockCDPSession extends EventEmitter { class MockCDPSession extends EventEmitter<CDPSessionEvents> {
async send(): Promise<any> {} async send(): Promise<any> {}
connection() { connection() {
return undefined; return undefined;
@ -153,6 +154,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 162, encodedDataLength: 162,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 2111.557593, requestTime: 2111.557593,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -241,6 +243,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 162, encodedDataLength: 162,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 2111.559346, requestTime: 2111.559346,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -344,6 +347,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 178, encodedDataLength: 178,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 2111.560482, requestTime: 2111.560482,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -448,6 +452,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 197, encodedDataLength: 197,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 2111.561759, requestTime: 2111.561759,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -486,13 +491,10 @@ describe('NetworkManager', () => {
await manager.setRequestInterception(true); await manager.setRequestInterception(true);
const requests: HTTPRequest[] = []; const requests: HTTPRequest[] = [];
manager.on( manager.on(NetworkManagerEvent.Request, async (request: HTTPRequest) => {
NetworkManagerEmittedEvents.Request,
async (request: HTTPRequest) => {
requests.push(request); requests.push(request);
await request.continue(); await request.continue();
} });
);
/** /**
* This sequence was taken from an actual CDP session produced by the following * This sequence was taken from an actual CDP session produced by the following
@ -519,7 +521,7 @@ describe('NetworkManager', () => {
request: { request: {
url: 'https://www.google.com/', url: 'https://www.google.com/',
method: 'GET', method: 'GET',
headers: [Object], headers: {},
mixedContentType: 'none', mixedContentType: 'none',
initialPriority: 'VeryHigh', initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin', referrerPolicy: 'strict-origin-when-cross-origin',
@ -538,7 +540,7 @@ describe('NetworkManager', () => {
request: { request: {
url: 'https://www.google.com/', url: 'https://www.google.com/',
method: 'GET', method: 'GET',
headers: [Object], headers: {},
initialPriority: 'VeryHigh', initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin', referrerPolicy: 'strict-origin-when-cross-origin',
}, },
@ -551,7 +553,7 @@ describe('NetworkManager', () => {
request: { request: {
url: 'https://www.google.com/', url: 'https://www.google.com/',
method: 'GET', method: 'GET',
headers: [Object], headers: {},
initialPriority: 'VeryHigh', initialPriority: 'VeryHigh',
referrerPolicy: 'strict-origin-when-cross-origin', referrerPolicy: 'strict-origin-when-cross-origin',
}, },
@ -572,12 +574,9 @@ describe('NetworkManager', () => {
await manager.addClient(mockCDPSession); await manager.addClient(mockCDPSession);
const requests: HTTPRequest[] = []; const requests: HTTPRequest[] = [];
manager.on( manager.on(NetworkManagerEvent.RequestFinished, (request: HTTPRequest) => {
NetworkManagerEmittedEvents.RequestFinished,
(request: HTTPRequest) => {
requests.push(request); requests.push(request);
} });
);
mockCDPSession.emit('Network.requestWillBeSent', { mockCDPSession.emit('Network.requestWillBeSent', {
requestId: '1360.2', requestId: '1360.2',
@ -633,6 +632,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 66, encodedDataLength: 66,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 10959.023904, requestTime: 10959.023904,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -692,14 +692,11 @@ describe('NetworkManager', () => {
const finishedRequests: HTTPRequest[] = []; const finishedRequests: HTTPRequest[] = [];
const pendingRequests: HTTPRequest[] = []; const pendingRequests: HTTPRequest[] = [];
manager.on( manager.on(NetworkManagerEvent.RequestFinished, (request: HTTPRequest) => {
NetworkManagerEmittedEvents.RequestFinished,
(request: HTTPRequest) => {
finishedRequests.push(request); finishedRequests.push(request);
} });
);
manager.on(NetworkManagerEmittedEvents.Request, (request: HTTPRequest) => { manager.on(NetworkManagerEvent.Request, (request: HTTPRequest) => {
pendingRequests.push(request); pendingRequests.push(request);
}); });
@ -756,6 +753,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 197, encodedDataLength: 197,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 671.232585, requestTime: 671.232585,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -845,14 +843,11 @@ describe('NetworkManager', () => {
const responses: HTTPResponse[] = []; const responses: HTTPResponse[] = [];
const requests: HTTPRequest[] = []; const requests: HTTPRequest[] = [];
manager.on( manager.on(NetworkManagerEvent.Response, (response: HTTPResponse) => {
NetworkManagerEmittedEvents.Response,
(response: HTTPResponse) => {
responses.push(response); responses.push(response);
} });
);
manager.on(NetworkManagerEmittedEvents.Request, (request: HTTPRequest) => { manager.on(NetworkManagerEvent.Request, (request: HTTPRequest) => {
requests.push(request); requests.push(request);
}); });
@ -966,6 +961,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 197, encodedDataLength: 197,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 504904.000422, requestTime: 504904.000422,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -1009,14 +1005,11 @@ describe('NetworkManager', () => {
const responses: HTTPResponse[] = []; const responses: HTTPResponse[] = [];
const requests: HTTPRequest[] = []; const requests: HTTPRequest[] = [];
manager.on( manager.on(NetworkManagerEvent.Response, (response: HTTPResponse) => {
NetworkManagerEmittedEvents.Response,
(response: HTTPResponse) => {
responses.push(response); responses.push(response);
} });
);
manager.on(NetworkManagerEmittedEvents.Request, (request: HTTPRequest) => { manager.on(NetworkManagerEvent.Request, (request: HTTPRequest) => {
requests.push(request); requests.push(request);
}); });
@ -1072,6 +1065,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 197, encodedDataLength: 197,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 510294.106734, requestTime: 510294.106734,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -1156,14 +1150,11 @@ describe('NetworkManager', () => {
const responses: HTTPResponse[] = []; const responses: HTTPResponse[] = [];
const requests: HTTPRequest[] = []; const requests: HTTPRequest[] = [];
manager.on( manager.on(NetworkManagerEvent.Response, (response: HTTPResponse) => {
NetworkManagerEmittedEvents.Response,
(response: HTTPResponse) => {
responses.push(response); responses.push(response);
} });
);
manager.on(NetworkManagerEmittedEvents.Request, (request: HTTPRequest) => { manager.on(NetworkManagerEvent.Request, (request: HTTPRequest) => {
requests.push(request); requests.push(request);
}); });
@ -1260,6 +1251,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 197, encodedDataLength: 197,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 31949.959838, requestTime: 31949.959838,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -1432,6 +1424,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 182, encodedDataLength: 182,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 31949.983605, requestTime: 31949.983605,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,
@ -1492,6 +1485,7 @@ describe('NetworkManager', () => {
fromPrefetchCache: false, fromPrefetchCache: false,
encodedDataLength: 0, encodedDataLength: 0,
timing: { timing: {
receiveHeadersStart: 0,
requestTime: 31949.988855, requestTime: 31949.988855,
proxyStart: -1, proxyStart: -1,
proxyEnd: -1, proxyEnd: -1,

View File

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
import {Protocol} from 'devtools-protocol'; import type {Protocol} from 'devtools-protocol';
import {Frame} from '../api/Frame.js'; import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import type {Frame} from '../api/Frame.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {CDPSession, CDPSessionEmittedEvents} from './Connection.js'; import {EventEmitter, EventSubscription, EventType} from './EventEmitter.js';
import {EventEmitter, Handler} from './EventEmitter.js'; import {CDPHTTPRequest} from './HTTPRequest.js';
import {HTTPRequest} from './HTTPRequest.js'; import {CDPHTTPResponse} from './HTTPResponse.js';
import {HTTPResponse} from './HTTPResponse.js';
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js'; import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
import {debugError, isString} from './util.js'; import {debugError, isString} from './util.js';
@ -58,13 +58,27 @@ export interface InternalNetworkConditions extends NetworkConditions {
* *
* @internal * @internal
*/ */
export const NetworkManagerEmittedEvents = { // eslint-disable-next-line @typescript-eslint/no-namespace
Request: Symbol('NetworkManager.Request'), export namespace NetworkManagerEvent {
RequestServedFromCache: Symbol('NetworkManager.RequestServedFromCache'), export const Request = Symbol('NetworkManager.Request');
Response: Symbol('NetworkManager.Response'), export const RequestServedFromCache = Symbol(
RequestFailed: Symbol('NetworkManager.RequestFailed'), 'NetworkManager.RequestServedFromCache'
RequestFinished: Symbol('NetworkManager.RequestFinished'), );
} as const; export const Response = Symbol('NetworkManager.Response');
export const RequestFailed = Symbol('NetworkManager.RequestFailed');
export const RequestFinished = Symbol('NetworkManager.RequestFinished');
}
/**
* @internal
*/
export interface NetworkManagerEvents extends Record<EventType, unknown> {
[NetworkManagerEvent.Request]: CDPHTTPRequest;
[NetworkManagerEvent.RequestServedFromCache]: CDPHTTPRequest | undefined;
[NetworkManagerEvent.Response]: CDPHTTPResponse;
[NetworkManagerEvent.RequestFailed]: CDPHTTPRequest;
[NetworkManagerEvent.RequestFinished]: CDPHTTPRequest;
}
/** /**
* @internal * @internal
@ -76,7 +90,7 @@ export interface FrameProvider {
/** /**
* @internal * @internal
*/ */
export class NetworkManager extends EventEmitter { export class NetworkManager extends EventEmitter<NetworkManagerEvents> {
#ignoreHTTPSErrors: boolean; #ignoreHTTPSErrors: boolean;
#frameManager: FrameProvider; #frameManager: FrameProvider;
#networkEventManager = new NetworkEventManager(); #networkEventManager = new NetworkEventManager();
@ -90,7 +104,7 @@ export class NetworkManager extends EventEmitter {
#userAgent?: string; #userAgent?: string;
#userAgentMetadata?: Protocol.Emulation.UserAgentMetadata; #userAgentMetadata?: Protocol.Emulation.UserAgentMetadata;
#handlers = new Map<string, Function>([ #handlers = Object.freeze([
['Fetch.requestPaused', this.#onRequestPaused], ['Fetch.requestPaused', this.#onRequestPaused],
['Fetch.authRequired', this.#onAuthRequired], ['Fetch.authRequired', this.#onAuthRequired],
['Network.requestWillBeSent', this.#onRequestWillBeSent], ['Network.requestWillBeSent', this.#onRequestWillBeSent],
@ -99,12 +113,10 @@ export class NetworkManager extends EventEmitter {
['Network.loadingFinished', this.#onLoadingFinished], ['Network.loadingFinished', this.#onLoadingFinished],
['Network.loadingFailed', this.#onLoadingFailed], ['Network.loadingFailed', this.#onLoadingFailed],
['Network.responseReceivedExtraInfo', this.#onResponseReceivedExtraInfo], ['Network.responseReceivedExtraInfo', this.#onResponseReceivedExtraInfo],
]); [CDPSessionEvent.Disconnected, this.#removeClient],
] as const);
#clients = new Map< #clients = new Map<CDPSession, DisposableStack>();
CDPSession,
Array<{event: string | symbol; handler: Handler}>
>();
constructor(ignoreHTTPSErrors: boolean, frameManager: FrameProvider) { constructor(ignoreHTTPSErrors: boolean, frameManager: FrameProvider) {
super(); super();
@ -116,20 +128,16 @@ export class NetworkManager extends EventEmitter {
if (this.#clients.has(client)) { if (this.#clients.has(client)) {
return; return;
} }
const listeners: Array<{event: string | symbol; handler: Handler}> = []; const subscriptions = new DisposableStack();
this.#clients.set(client, listeners); this.#clients.set(client, subscriptions);
for (const [event, handler] of this.#handlers) { for (const [event, handler] of this.#handlers) {
listeners.push({ subscriptions.use(
event, // TODO: Remove any here.
handler: handler.bind(this, client), new EventSubscription(client, event, (arg: any) => {
}); return handler.bind(this)(client, arg);
client.on(event, listeners.at(-1)!.handler); })
);
} }
listeners.push({
event: CDPSessionEmittedEvents.Disconnected,
handler: this.#removeClient.bind(this, client),
});
client.on(CDPSessionEmittedEvents.Disconnected, listeners.at(-1)!.handler);
await Promise.all([ await Promise.all([
this.#ignoreHTTPSErrors this.#ignoreHTTPSErrors
? client.send('Security.setIgnoreCertificateErrors', { ? client.send('Security.setIgnoreCertificateErrors', {
@ -146,10 +154,7 @@ export class NetworkManager extends EventEmitter {
} }
async #removeClient(client: CDPSession) { async #removeClient(client: CDPSession) {
const listeners = this.#clients.get(client); this.#clients.get(client)?.dispose();
for (const {event, handler} of listeners || []) {
client.off(event, handler);
}
this.#clients.delete(client); this.#clients.delete(client);
} }
@ -447,7 +452,7 @@ export class NetworkManager extends EventEmitter {
? this.#frameManager.frame(event.frameId) ? this.#frameManager.frame(event.frameId)
: null; : null;
const request = new HTTPRequest( const request = new CDPHTTPRequest(
client, client,
frame, frame,
event.requestId, event.requestId,
@ -455,7 +460,7 @@ export class NetworkManager extends EventEmitter {
event, event,
[] []
); );
this.emit(NetworkManagerEmittedEvents.Request, request); this.emit(NetworkManagerEvent.Request, request);
void request.finalizeInterceptions(); void request.finalizeInterceptions();
} }
@ -464,7 +469,7 @@ export class NetworkManager extends EventEmitter {
event: Protocol.Network.RequestWillBeSentEvent, event: Protocol.Network.RequestWillBeSentEvent,
fetchRequestId?: FetchRequestId fetchRequestId?: FetchRequestId
): void { ): void {
let redirectChain: HTTPRequest[] = []; let redirectChain: CDPHTTPRequest[] = [];
if (event.redirectResponse) { if (event.redirectResponse) {
// We want to emit a response and requestfinished for the // We want to emit a response and requestfinished for the
// redirectResponse, but we can't do so unless we have a // redirectResponse, but we can't do so unless we have a
@ -504,7 +509,7 @@ export class NetworkManager extends EventEmitter {
? this.#frameManager.frame(event.frameId) ? this.#frameManager.frame(event.frameId)
: null; : null;
const request = new HTTPRequest( const request = new CDPHTTPRequest(
client, client,
frame, frame,
fetchRequestId, fetchRequestId,
@ -513,7 +518,7 @@ export class NetworkManager extends EventEmitter {
redirectChain redirectChain
); );
this.#networkEventManager.storeRequest(event.requestId, request); this.#networkEventManager.storeRequest(event.requestId, request);
this.emit(NetworkManagerEmittedEvents.Request, request); this.emit(NetworkManagerEvent.Request, request);
void request.finalizeInterceptions(); void request.finalizeInterceptions();
} }
@ -525,16 +530,16 @@ export class NetworkManager extends EventEmitter {
if (request) { if (request) {
request._fromMemoryCache = true; request._fromMemoryCache = true;
} }
this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request); this.emit(NetworkManagerEvent.RequestServedFromCache, request);
} }
#handleRequestRedirect( #handleRequestRedirect(
client: CDPSession, client: CDPSession,
request: HTTPRequest, request: CDPHTTPRequest,
responsePayload: Protocol.Network.Response, responsePayload: Protocol.Network.Response,
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
): void { ): void {
const response = new HTTPResponse( const response = new CDPHTTPResponse(
client, client,
request, request,
responsePayload, responsePayload,
@ -546,8 +551,8 @@ export class NetworkManager extends EventEmitter {
new Error('Response body is unavailable for redirect responses') new Error('Response body is unavailable for redirect responses')
); );
this.#forgetRequest(request, false); this.#forgetRequest(request, false);
this.emit(NetworkManagerEmittedEvents.Response, response); this.emit(NetworkManagerEvent.Response, response);
this.emit(NetworkManagerEmittedEvents.RequestFinished, request); this.emit(NetworkManagerEvent.RequestFinished, request);
} }
#emitResponseEvent( #emitResponseEvent(
@ -582,14 +587,14 @@ export class NetworkManager extends EventEmitter {
extraInfo = null; extraInfo = null;
} }
const response = new HTTPResponse( const response = new CDPHTTPResponse(
client, client,
request, request,
responseReceived.response, responseReceived.response,
extraInfo extraInfo
); );
request._response = response; request._response = response;
this.emit(NetworkManagerEmittedEvents.Response, response); this.emit(NetworkManagerEvent.Response, response);
} }
#onResponseReceived( #onResponseReceived(
@ -654,7 +659,7 @@ export class NetworkManager extends EventEmitter {
this.#networkEventManager.responseExtraInfo(event.requestId).push(event); this.#networkEventManager.responseExtraInfo(event.requestId).push(event);
} }
#forgetRequest(request: HTTPRequest, events: boolean): void { #forgetRequest(request: CDPHTTPRequest, events: boolean): void {
const requestId = request._requestId; const requestId = request._requestId;
const interceptionId = request._interceptionId; const interceptionId = request._interceptionId;
@ -697,7 +702,7 @@ export class NetworkManager extends EventEmitter {
request.response()?._resolveBody(null); request.response()?._resolveBody(null);
} }
this.#forgetRequest(request, true); this.#forgetRequest(request, true);
this.emit(NetworkManagerEmittedEvents.RequestFinished, request); this.emit(NetworkManagerEvent.RequestFinished, request);
} }
#onLoadingFailed( #onLoadingFailed(
@ -729,6 +734,6 @@ export class NetworkManager extends EventEmitter {
response._resolveBody(null); response._resolveBody(null);
} }
this.#forgetRequest(request, true); this.#forgetRequest(request, true);
this.emit(NetworkManagerEmittedEvents.RequestFailed, request); this.emit(NetworkManagerEvent.RequestFailed, request);
} }
} }

View File

@ -20,6 +20,7 @@ import {Protocol} from 'devtools-protocol';
import type {Browser} from '../api/Browser.js'; import type {Browser} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {ElementHandle} from '../api/ElementHandle.js'; import {ElementHandle} from '../api/ElementHandle.js';
import {Frame} from '../api/Frame.js'; import {Frame} from '../api/Frame.js';
import {HTTPRequest} from '../api/HTTPRequest.js'; import {HTTPRequest} from '../api/HTTPRequest.js';
@ -31,7 +32,7 @@ import {
Metrics, Metrics,
NewDocumentScriptEvaluation, NewDocumentScriptEvaluation,
Page, Page,
PageEmittedEvents, PageEvent,
ScreenshotClip, ScreenshotClip,
ScreenshotOptions, ScreenshotOptions,
WaitForOptions, WaitForOptions,
@ -43,33 +44,28 @@ import {isErrorLike} from '../util/ErrorLike.js';
import {Accessibility} from './Accessibility.js'; import {Accessibility} from './Accessibility.js';
import {Binding} from './Binding.js'; import {Binding} from './Binding.js';
import { import {CDPCDPSession} from './CDPSession.js';
CDPSession, import {isTargetClosedError} from './Connection.js';
CDPSessionEmittedEvents,
CDPSessionImpl,
isTargetClosedError,
} from './Connection.js';
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js'; import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
import {Coverage} from './Coverage.js'; import {Coverage} from './Coverage.js';
import {DeviceRequestPrompt} from './DeviceRequestPrompt.js'; import {DeviceRequestPrompt} from './DeviceRequestPrompt.js';
import {CDPDialog} from './Dialog.js'; import {CDPDialog} from './Dialog.js';
import {EmulationManager} from './EmulationManager.js'; import {EmulationManager} from './EmulationManager.js';
import {TargetCloseError} from './Errors.js'; import {TargetCloseError} from './Errors.js';
import {Handler} from './EventEmitter.js';
import {FileChooser} from './FileChooser.js'; import {FileChooser} from './FileChooser.js';
import {CDPFrame} from './Frame.js'; import {CDPFrame} from './Frame.js';
import {FrameManager, FrameManagerEmittedEvents} from './FrameManager.js'; import {FrameManager, FrameManagerEvent} from './FrameManager.js';
import {CDPKeyboard, CDPMouse, CDPTouchscreen} from './Input.js'; import {CDPKeyboard, CDPMouse, CDPTouchscreen} from './Input.js';
import {MAIN_WORLD} from './IsolatedWorlds.js'; import {MAIN_WORLD} from './IsolatedWorlds.js';
import { import {
Credentials, Credentials,
NetworkConditions, NetworkConditions,
NetworkManagerEmittedEvents, NetworkManagerEvent,
} from './NetworkManager.js'; } from './NetworkManager.js';
import {PDFOptions} from './PDFOptions.js'; import {PDFOptions} from './PDFOptions.js';
import {Viewport} from './PuppeteerViewport.js'; import {Viewport} from './PuppeteerViewport.js';
import {CDPTarget} from './Target.js'; import {CDPTarget} from './Target.js';
import {TargetManagerEmittedEvents} from './TargetManager.js'; import {TargetManagerEvent} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js'; import {TaskQueue} from './TaskQueue.js';
import {TimeoutSettings} from './TimeoutSettings.js'; import {TimeoutSettings} from './TimeoutSettings.js';
import {Tracing} from './Tracing.js'; import {Tracing} from './Tracing.js';
@ -146,58 +142,81 @@ export class CDPPage extends Page {
#serviceWorkerBypassed = false; #serviceWorkerBypassed = false;
#userDragInterceptionEnabled = false; #userDragInterceptionEnabled = false;
#frameManagerHandlers = new Map<symbol, Handler<any>>([ #frameManagerHandlers = Object.freeze([
[ [
FrameManagerEmittedEvents.FrameAttached, FrameManagerEvent.FrameAttached,
this.emit.bind(this, PageEmittedEvents.FrameAttached), (frame: CDPFrame) => {
this.emit(PageEvent.FrameAttached, frame);
},
], ],
[ [
FrameManagerEmittedEvents.FrameDetached, FrameManagerEvent.FrameDetached,
this.emit.bind(this, PageEmittedEvents.FrameDetached), (frame: CDPFrame) => {
this.emit(PageEvent.FrameDetached, frame);
},
], ],
[ [
FrameManagerEmittedEvents.FrameNavigated, FrameManagerEvent.FrameNavigated,
this.emit.bind(this, PageEmittedEvents.FrameNavigated), (frame: CDPFrame) => {
this.emit(PageEvent.FrameNavigated, frame);
},
], ],
]); ] as const);
#networkManagerHandlers = new Map<symbol, Handler<any>>([ #networkManagerHandlers = Object.freeze([
[ [
NetworkManagerEmittedEvents.Request, NetworkManagerEvent.Request,
this.emit.bind(this, PageEmittedEvents.Request), (request: HTTPRequest) => {
this.emit(PageEvent.Request, request);
},
], ],
[ [
NetworkManagerEmittedEvents.RequestServedFromCache, NetworkManagerEvent.RequestServedFromCache,
this.emit.bind(this, PageEmittedEvents.RequestServedFromCache), (request: HTTPRequest) => {
this.emit(PageEvent.RequestServedFromCache, request);
},
], ],
[ [
NetworkManagerEmittedEvents.Response, NetworkManagerEvent.Response,
this.emit.bind(this, PageEmittedEvents.Response), (response: HTTPResponse) => {
this.emit(PageEvent.Response, response);
},
], ],
[ [
NetworkManagerEmittedEvents.RequestFailed, NetworkManagerEvent.RequestFailed,
this.emit.bind(this, PageEmittedEvents.RequestFailed), (request: HTTPRequest) => {
this.emit(PageEvent.RequestFailed, request);
},
], ],
[ [
NetworkManagerEmittedEvents.RequestFinished, NetworkManagerEvent.RequestFinished,
this.emit.bind(this, PageEmittedEvents.RequestFinished), (request: HTTPRequest) => {
this.emit(PageEvent.RequestFinished, request);
},
], ],
]); ] as const);
#sessionHandlers = new Map<symbol | string, Handler<any>>([ #sessionHandlers = Object.freeze([
[ [
CDPSessionEmittedEvents.Disconnected, CDPSessionEvent.Disconnected,
() => { () => {
return this.#sessionCloseDeferred.resolve( this.#sessionCloseDeferred.resolve(
new TargetCloseError('Target closed') new TargetCloseError('Target closed')
); );
}, },
], ],
[ [
'Page.domContentEventFired', 'Page.domContentEventFired',
this.emit.bind(this, PageEmittedEvents.DOMContentLoaded), () => {
return this.emit(PageEvent.DOMContentLoaded, undefined);
},
],
[
'Page.loadEventFired',
() => {
return this.emit(PageEvent.Load, undefined);
},
], ],
['Page.loadEventFired', this.emit.bind(this, PageEmittedEvents.Load)],
['Runtime.consoleAPICalled', this.#onConsoleAPI.bind(this)], ['Runtime.consoleAPICalled', this.#onConsoleAPI.bind(this)],
['Runtime.bindingCalled', this.#onBindingCalled.bind(this)], ['Runtime.bindingCalled', this.#onBindingCalled.bind(this)],
['Page.javascriptDialogOpening', this.#onDialog.bind(this)], ['Page.javascriptDialogOpening', this.#onDialog.bind(this)],
@ -206,7 +225,7 @@ export class CDPPage extends Page {
['Performance.metrics', this.#emitMetrics.bind(this)], ['Performance.metrics', this.#emitMetrics.bind(this)],
['Log.entryAdded', this.#onLogEntryAdded.bind(this)], ['Log.entryAdded', this.#onLogEntryAdded.bind(this)],
['Page.fileChooserOpened', this.#onFileChooser.bind(this)], ['Page.fileChooserOpened', this.#onFileChooser.bind(this)],
]); ] as const);
constructor( constructor(
client: CDPSession, client: CDPSession,
@ -236,10 +255,10 @@ export class CDPPage extends Page {
this.#setupEventListeners(); this.#setupEventListeners();
this.#tabSession?.on(CDPSessionEmittedEvents.Swapped, async newSession => { this.#tabSession?.on(CDPSessionEvent.Swapped, async newSession => {
this.#client = newSession; this.#client = newSession;
assert( assert(
this.#client instanceof CDPSessionImpl, this.#client instanceof CDPCDPSession,
'CDPSession is not instance of CDPSessionImpl' 'CDPSession is not instance of CDPSessionImpl'
); );
this.#target = this.#client._target(); this.#target = this.#client._target();
@ -254,57 +273,49 @@ export class CDPPage extends Page {
await this.#frameManager.swapFrameTree(newSession); await this.#frameManager.swapFrameTree(newSession);
this.#setupEventListeners(); this.#setupEventListeners();
}); });
this.#tabSession?.on( this.#tabSession?.on(CDPSessionEvent.Ready, session => {
CDPSessionEmittedEvents.Ready, assert(session instanceof CDPCDPSession);
(session: CDPSessionImpl) => {
if (session._target()._subtype() !== 'prerender') { if (session._target()._subtype() !== 'prerender') {
return; return;
} }
this.#frameManager this.#frameManager.registerSpeculativeSession(session).catch(debugError);
.registerSpeculativeSession(session)
.catch(debugError);
this.#emulationManager this.#emulationManager
.registerSpeculativeSession(session) .registerSpeculativeSession(session)
.catch(debugError); .catch(debugError);
} });
);
} }
#setupEventListeners() { #setupEventListeners() {
this.#client.on(CDPSessionEmittedEvents.Ready, this.#onAttachedToTarget); this.#client.on(CDPSessionEvent.Ready, this.#onAttachedToTarget);
this.#target this.#target
._targetManager() ._targetManager()
.on(TargetManagerEmittedEvents.TargetGone, this.#onDetachedFromTarget); .on(TargetManagerEvent.TargetGone, this.#onDetachedFromTarget);
for (const [eventName, handler] of this.#frameManagerHandlers) { for (const [eventName, handler] of this.#frameManagerHandlers) {
this.#frameManager.on(eventName, handler); this.#frameManager.on(eventName, handler);
} }
for (const [eventName, handler] of this.#networkManagerHandlers) { for (const [eventName, handler] of this.#networkManagerHandlers) {
this.#frameManager.networkManager.on(eventName, handler); // TODO: Remove any.
this.#frameManager.networkManager.on(eventName, handler as any);
} }
for (const [eventName, handler] of this.#sessionHandlers) { for (const [eventName, handler] of this.#sessionHandlers) {
this.#client.on(eventName, handler); // TODO: Remove any.
this.#client.on(eventName, handler as any);
} }
this.#target._isClosedDeferred this.#target._isClosedDeferred
.valueOrThrow() .valueOrThrow()
.then(() => { .then(() => {
this.#client.off( this.#client.off(CDPSessionEvent.Ready, this.#onAttachedToTarget);
CDPSessionEmittedEvents.Ready,
this.#onAttachedToTarget
);
this.#target this.#target
._targetManager() ._targetManager()
.off( .off(TargetManagerEvent.TargetGone, this.#onDetachedFromTarget);
TargetManagerEmittedEvents.TargetGone,
this.#onDetachedFromTarget
);
this.emit(PageEmittedEvents.Close); this.emit(PageEvent.Close, undefined);
this.#closed = true; this.#closed = true;
}) })
.catch(debugError); .catch(debugError);
@ -317,10 +328,11 @@ export class CDPPage extends Page {
return; return;
} }
this.#workers.delete(sessionId!); this.#workers.delete(sessionId!);
this.emit(PageEmittedEvents.WorkerDestroyed, worker); this.emit(PageEvent.WorkerDestroyed, worker);
}; };
#onAttachedToTarget = (session: CDPSessionImpl) => { #onAttachedToTarget = (session: CDPSession) => {
assert(session instanceof CDPCDPSession);
this.#frameManager.onAttachedToTarget(session._target()); this.#frameManager.onAttachedToTarget(session._target());
if (session._target()._getTargetInfo().type === 'worker') { if (session._target()._getTargetInfo().type === 'worker') {
const worker = new WebWorker( const worker = new WebWorker(
@ -330,9 +342,9 @@ export class CDPPage extends Page {
this.#handleException.bind(this) this.#handleException.bind(this)
); );
this.#workers.set(session.id(), worker); this.#workers.set(session.id(), worker);
this.emit(PageEmittedEvents.WorkerCreated, worker); this.emit(PageEvent.WorkerCreated, worker);
} }
session.on(CDPSessionEmittedEvents.Ready, this.#onAttachedToTarget); session.on(CDPSessionEvent.Ready, this.#onAttachedToTarget);
}; };
async #initialize(): Promise<void> { async #initialize(): Promise<void> {
@ -434,7 +446,7 @@ export class CDPPage extends Page {
} }
#onTargetCrashed(): void { #onTargetCrashed(): void {
this.emit('error', new Error('Page crashed!')); this.emit(PageEvent.Error, new Error('Page crashed!'));
} }
#onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void { #onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void {
@ -446,7 +458,7 @@ export class CDPPage extends Page {
} }
if (source !== 'worker') { if (source !== 'worker') {
this.emit( this.emit(
PageEmittedEvents.Console, PageEvent.Console,
new ConsoleMessage(level, text, [], [{url, lineNumber}]) new ConsoleMessage(level, text, [], [{url, lineNumber}])
); );
} }
@ -703,7 +715,7 @@ export class CDPPage extends Page {
} }
#emitMetrics(event: Protocol.Performance.MetricsEvent): void { #emitMetrics(event: Protocol.Performance.MetricsEvent): void {
this.emit(PageEmittedEvents.Metrics, { this.emit(PageEvent.Metrics, {
title: event.title, title: event.title,
metrics: this.#buildMetricsObject(event.metrics), metrics: this.#buildMetricsObject(event.metrics),
}); });
@ -724,7 +736,7 @@ export class CDPPage extends Page {
#handleException(exception: Protocol.Runtime.ExceptionThrownEvent): void { #handleException(exception: Protocol.Runtime.ExceptionThrownEvent): void {
this.emit( this.emit(
PageEmittedEvents.PageError, PageEvent.PageError,
createClientError(exception.exceptionDetails) createClientError(exception.exceptionDetails)
); );
} }
@ -801,7 +813,7 @@ export class CDPPage extends Page {
args: JSHandle[], args: JSHandle[],
stackTrace?: Protocol.Runtime.StackTrace stackTrace?: Protocol.Runtime.StackTrace
): void { ): void {
if (!this.listenerCount(PageEmittedEvents.Console)) { if (!this.listenerCount(PageEvent.Console)) {
args.forEach(arg => { args.forEach(arg => {
return arg.dispose(); return arg.dispose();
}); });
@ -834,7 +846,7 @@ export class CDPPage extends Page {
args, args,
stackTraceLocations stackTraceLocations
); );
this.emit(PageEmittedEvents.Console, message); this.emit(PageEvent.Console, message);
} }
#onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void { #onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void {
@ -845,7 +857,7 @@ export class CDPPage extends Page {
event.message, event.message,
event.defaultPrompt event.defaultPrompt
); );
this.emit(PageEmittedEvents.Dialog, dialog); this.emit(PageEvent.Dialog, dialog);
} }
override async reload( override async reload(
@ -870,7 +882,7 @@ export class CDPPage extends Page {
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this.#timeoutSettings.timeout()} = options;
return await waitForEvent( return await waitForEvent(
this.#frameManager.networkManager, this.#frameManager.networkManager,
NetworkManagerEmittedEvents.Request, NetworkManagerEvent.Request,
async request => { async request => {
if (isString(urlOrPredicate)) { if (isString(urlOrPredicate)) {
return urlOrPredicate === request.url(); return urlOrPredicate === request.url();
@ -894,7 +906,7 @@ export class CDPPage extends Page {
const {timeout = this.#timeoutSettings.timeout()} = options; const {timeout = this.#timeoutSettings.timeout()} = options;
return await waitForEvent( return await waitForEvent(
this.#frameManager.networkManager, this.#frameManager.networkManager,
NetworkManagerEmittedEvents.Response, NetworkManagerEvent.Response,
async response => { async response => {
if (isString(urlOrPredicate)) { if (isString(urlOrPredicate)) {
return urlOrPredicate === response.url(); return urlOrPredicate === response.url();

View File

@ -18,11 +18,12 @@ import {Protocol} from 'devtools-protocol';
import type {Browser} from '../api/Browser.js'; import type {Browser} from '../api/Browser.js';
import type {BrowserContext} from '../api/BrowserContext.js'; import type {BrowserContext} from '../api/BrowserContext.js';
import {Page, PageEmittedEvents} from '../api/Page.js'; import {CDPSession} from '../api/CDPSession.js';
import {Page, PageEvent} from '../api/Page.js';
import {Target, TargetType} from '../api/Target.js'; import {Target, TargetType} from '../api/Target.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {CDPSession, CDPSessionImpl} from './Connection.js'; import {CDPCDPSession} from './CDPSession.js';
import {CDPPage} from './Page.js'; import {CDPPage} from './Page.js';
import {Viewport} from './PuppeteerViewport.js'; import {Viewport} from './PuppeteerViewport.js';
import {TargetManager} from './TargetManager.js'; import {TargetManager} from './TargetManager.js';
@ -75,7 +76,7 @@ export class CDPTarget extends Target {
this.#browserContext = browserContext; this.#browserContext = browserContext;
this._targetId = targetInfo.targetId; this._targetId = targetInfo.targetId;
this.#sessionFactory = sessionFactory; this.#sessionFactory = sessionFactory;
if (this.#session && this.#session instanceof CDPSessionImpl) { if (this.#session && this.#session instanceof CDPCDPSession) {
this.#session._setTarget(this); this.#session._setTarget(this);
} }
} }
@ -102,7 +103,7 @@ export class CDPTarget extends Target {
throw new Error('sessionFactory is not initialized'); throw new Error('sessionFactory is not initialized');
} }
return this.#sessionFactory(false).then(session => { return this.#sessionFactory(false).then(session => {
(session as CDPSessionImpl)._setTarget(this); (session as CDPCDPSession)._setTarget(this);
return session; return session;
}); });
} }
@ -222,11 +223,11 @@ export class PageTarget extends CDPTarget {
return true; return true;
} }
const openerPage = await opener.pagePromise; const openerPage = await opener.pagePromise;
if (!openerPage.listenerCount(PageEmittedEvents.Popup)) { if (!openerPage.listenerCount(PageEvent.Popup)) {
return true; return true;
} }
const popupPage = await this.page(); const popupPage = await this.page();
openerPage.emit(PageEmittedEvents.Popup, popupPage); openerPage.emit(PageEvent.Popup, popupPage);
return true; return true;
}) })
.catch(debugError); .catch(debugError);

View File

@ -16,8 +16,9 @@
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from './Connection.js'; import {CDPSession} from '../api/CDPSession.js';
import {EventEmitter} from './EventEmitter.js';
import {EventEmitter, EventType} from './EventEmitter.js';
import {CDPTarget} from './Target.js'; import {CDPTarget} from './Target.js';
/** /**
@ -29,6 +30,33 @@ export type TargetFactory = (
parentSession?: CDPSession parentSession?: CDPSession
) => CDPTarget; ) => CDPTarget;
/**
* @internal
*/
export const enum TargetManagerEvent {
TargetDiscovered = 'targetDiscovered',
TargetAvailable = 'targetAvailable',
TargetGone = 'targetGone',
/**
* Emitted after a target has been initialized and whenever its URL changes.
*/
TargetChanged = 'targetChanged',
}
/**
* @internal
*/
export interface TargetManagerEvents extends Record<EventType, unknown> {
[TargetManagerEvent.TargetAvailable]: CDPTarget;
[TargetManagerEvent.TargetDiscovered]: Protocol.Target.TargetInfo;
[TargetManagerEvent.TargetGone]: CDPTarget;
[TargetManagerEvent.TargetChanged]: {
target: CDPTarget;
wasInitialized: true;
previousURL: string;
};
}
/** /**
* TargetManager encapsulates all interactions with CDP targets and is * TargetManager encapsulates all interactions with CDP targets and is
* responsible for coordinating the configuration of targets with the rest of * responsible for coordinating the configuration of targets with the rest of
@ -40,21 +68,8 @@ export type TargetFactory = (
* *
* @internal * @internal
*/ */
export interface TargetManager extends EventEmitter { export interface TargetManager extends EventEmitter<TargetManagerEvents> {
getAvailableTargets(): Map<string, CDPTarget>; getAvailableTargets(): Map<string, CDPTarget>;
initialize(): Promise<void>; initialize(): Promise<void>;
dispose(): void; dispose(): void;
} }
/**
* @internal
*/
export const enum TargetManagerEmittedEvents {
TargetDiscovered = 'targetDiscovered',
TargetAvailable = 'targetAvailable',
TargetGone = 'targetGone',
/**
* Emitted after a target has been initialized and whenever its URL changes.
*/
TargetChanged = 'targetChanged',
}

View File

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import {CDPSession} from '../api/CDPSession.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js'; import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js'; import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js'; import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
/** /**
@ -126,6 +126,7 @@ export class Tracing {
const contentDeferred = Deferred.create<Buffer | undefined>(); const contentDeferred = Deferred.create<Buffer | undefined>();
this.#client.once('Tracing.tracingComplete', async event => { this.#client.once('Tracing.tracingComplete', async event => {
try { try {
assert(event.stream, 'Missing "stream"');
const readable = await getReadableFromProtocolStream( const readable = await getReadableFromProtocolStream(
this.#client, this.#client,
event.stream event.stream

View File

@ -15,11 +15,11 @@
*/ */
import {Protocol} from 'devtools-protocol'; import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {Realm} from '../api/Realm.js'; import {Realm} from '../api/Realm.js';
import {CDPSession} from './Connection.js';
import {ConsoleMessageType} from './ConsoleMessage.js'; import {ConsoleMessageType} from './ConsoleMessage.js';
import {EventEmitter} from './EventEmitter.js'; import {EventEmitter, EventType} from './EventEmitter.js';
import {ExecutionContext} from './ExecutionContext.js'; import {ExecutionContext} from './ExecutionContext.js';
import {IsolatedWorld} from './IsolatedWorld.js'; import {IsolatedWorld} from './IsolatedWorld.js';
import {CDPJSHandle} from './JSHandle.js'; import {CDPJSHandle} from './JSHandle.js';
@ -33,7 +33,7 @@ import {debugError, withSourcePuppeteerURLIfNone} from './util.js';
export type ConsoleAPICalledCallback = ( export type ConsoleAPICalledCallback = (
eventType: ConsoleMessageType, eventType: ConsoleMessageType,
handles: CDPJSHandle[], handles: CDPJSHandle[],
trace: Protocol.Runtime.StackTrace trace?: Protocol.Runtime.StackTrace
) => void; ) => void;
/** /**
@ -69,7 +69,7 @@ export type ExceptionThrownCallback = (
* *
* @public * @public
*/ */
export class WebWorker extends EventEmitter { export class WebWorker extends EventEmitter<Record<EventType, unknown>> {
/** /**
* @internal * @internal
*/ */

View File

@ -18,22 +18,19 @@ import * as BidiMapper from 'chromium-bidi/lib/cjs/bidiMapper/bidiMapper.js';
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js'; import type {ProtocolMapping} from 'devtools-protocol/types/protocol-mapping.js';
import {CDPSession, Connection as CDPPPtrConnection} from '../Connection.js'; import {CDPEvents, CDPSession} from '../../api/CDPSession.js';
import {Connection as CDPConnection} from '../Connection.js';
import {TargetCloseError} from '../Errors.js'; import {TargetCloseError} from '../Errors.js';
import {EventEmitter, Handler} from '../EventEmitter.js'; import {Handler} from '../EventEmitter.js';
import {Connection as BidiPPtrConnection} from './Connection.js'; import {BidiConnection} from './Connection.js';
type CdpEvents = {
[Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0];
};
/** /**
* @internal * @internal
*/ */
export async function connectBidiOverCDP( export async function connectBidiOverCDP(
cdp: CDPPPtrConnection cdp: CDPConnection
): Promise<BidiPPtrConnection> { ): Promise<BidiConnection> {
const transportBiDi = new NoOpTransport(); const transportBiDi = new NoOpTransport();
const cdpConnectionAdapter = new CDPConnectionAdapter(cdp); const cdpConnectionAdapter = new CDPConnectionAdapter(cdp);
const pptrTransport = { const pptrTransport = {
@ -53,7 +50,7 @@ export async function connectBidiOverCDP(
// Forwards a BiDi event sent by BidiServer to Puppeteer. // Forwards a BiDi event sent by BidiServer to Puppeteer.
pptrTransport.onmessage(JSON.stringify(message)); pptrTransport.onmessage(JSON.stringify(message));
}); });
const pptrBiDiConnection = new BidiPPtrConnection(cdp.url(), pptrTransport); const pptrBiDiConnection = new BidiConnection(cdp.url(), pptrTransport);
const bidiServer = await BidiMapper.BidiServer.createAndStart( const bidiServer = await BidiMapper.BidiServer.createAndStart(
transportBiDi, transportBiDi,
cdpConnectionAdapter, cdpConnectionAdapter,
@ -67,16 +64,16 @@ export async function connectBidiOverCDP(
* @internal * @internal
*/ */
class CDPConnectionAdapter { class CDPConnectionAdapter {
#cdp: CDPPPtrConnection; #cdp: CDPConnection;
#adapters = new Map<CDPSession, CDPClientAdapter<CDPSession>>(); #adapters = new Map<CDPSession, CDPClientAdapter<CDPSession>>();
#browser: CDPClientAdapter<CDPPPtrConnection>; #browser: CDPClientAdapter<CDPConnection>;
constructor(cdp: CDPPPtrConnection) { constructor(cdp: CDPConnection) {
this.#cdp = cdp; this.#cdp = cdp;
this.#browser = new CDPClientAdapter(cdp); this.#browser = new CDPClientAdapter(cdp);
} }
browserClient(): CDPClientAdapter<CDPPPtrConnection> { browserClient(): CDPClientAdapter<CDPConnection> {
return this.#browser; return this.#browser;
} }
@ -107,8 +104,8 @@ class CDPConnectionAdapter {
* *
* @internal * @internal
*/ */
class CDPClientAdapter<T extends EventEmitter & Pick<CDPPPtrConnection, 'send'>> class CDPClientAdapter<T extends CDPSession | CDPConnection>
extends BidiMapper.EventEmitter<CdpEvents> extends BidiMapper.EventEmitter<CDPEvents>
implements BidiMapper.CdpClient implements BidiMapper.CdpClient
{ {
#closed = false; #closed = false;
@ -132,9 +129,9 @@ class CDPClientAdapter<T extends EventEmitter & Pick<CDPPPtrConnection, 'send'>>
return this.#browserClient!; return this.#browserClient!;
} }
#forwardMessage = <T extends keyof CdpEvents>( #forwardMessage = <T extends keyof CDPEvents>(
method: T, method: T,
event: CdpEvents[T] event: CDPEvents[T]
) => { ) => {
this.emit(method, event); this.emit(method, event);
}; };

View File

@ -21,21 +21,18 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import { import {
Browser, Browser,
BrowserCloseCallback, BrowserCloseCallback,
BrowserContextEmittedEvents,
BrowserContextOptions, BrowserContextOptions,
BrowserEmittedEvents, BrowserEvent,
} from '../../api/Browser.js'; } from '../../api/Browser.js';
import {BrowserContextEvent} from '../../api/BrowserContext.js';
import {Page} from '../../api/Page.js'; import {Page} from '../../api/Page.js';
import {Target} from '../../api/Target.js'; import {Target} from '../../api/Target.js';
import {Handler} from '../EventEmitter.js'; import {Handler} from '../EventEmitter.js';
import {Viewport} from '../PuppeteerViewport.js'; import {Viewport} from '../PuppeteerViewport.js';
import {BidiBrowserContext} from './BrowserContext.js'; import {BidiBrowserContext} from './BrowserContext.js';
import { import {BrowsingContext, BrowsingContextEvent} from './BrowsingContext.js';
BrowsingContext, import {BidiConnection} from './Connection.js';
BrowsingContextEmittedEvents,
} from './BrowsingContext.js';
import {Connection} from './Connection.js';
import { import {
BiDiBrowserTarget, BiDiBrowserTarget,
BiDiBrowsingContextTarget, BiDiBrowsingContextTarget,
@ -44,6 +41,17 @@ import {
} from './Target.js'; } from './Target.js';
import {debugError} from './utils.js'; import {debugError} from './utils.js';
/**
* @internal
*/
interface Options {
process?: ChildProcess;
closeCallback?: BrowserCloseCallback;
connection: BidiConnection;
defaultViewport: Viewport | null;
ignoreHTTPSErrors?: boolean;
}
/** /**
* @internal * @internal
*/ */
@ -108,7 +116,7 @@ export class BidiBrowser extends Browser {
#browserVersion = ''; #browserVersion = '';
#process?: ChildProcess; #process?: ChildProcess;
#closeCallback?: BrowserCloseCallback; #closeCallback?: BrowserCloseCallback;
#connection: Connection; #connection: BidiConnection;
#defaultViewport: Viewport | null; #defaultViewport: Viewport | null;
#defaultContext: BidiBrowserContext; #defaultContext: BidiBrowserContext;
#targets = new Map<string, BidiTarget>(); #targets = new Map<string, BidiTarget>();
@ -142,7 +150,7 @@ export class BidiBrowser extends Browser {
this.#process?.once('close', () => { this.#process?.once('close', () => {
this.#connection.dispose(); this.#connection.dispose();
this.emit(BrowserEmittedEvents.Disconnected); this.emit(BrowserEvent.Disconnected, undefined);
}); });
this.#defaultContext = new BidiBrowserContext(this, { this.#defaultContext = new BidiBrowserContext(this, {
defaultViewport: this.#defaultViewport, defaultViewport: this.#defaultViewport,
@ -159,17 +167,15 @@ export class BidiBrowser extends Browser {
#onContextDomLoaded(event: Bidi.BrowsingContext.Info) { #onContextDomLoaded(event: Bidi.BrowsingContext.Info) {
const target = this.#targets.get(event.context); const target = this.#targets.get(event.context);
if (target) { if (target) {
this.emit(BrowserEmittedEvents.TargetChanged, target); this.emit(BrowserEvent.TargetChanged, target);
} }
} }
#onContextNavigation(event: Bidi.BrowsingContext.NavigationInfo) { #onContextNavigation(event: Bidi.BrowsingContext.NavigationInfo) {
const target = this.#targets.get(event.context); const target = this.#targets.get(event.context);
if (target) { if (target) {
this.emit(BrowserEmittedEvents.TargetChanged, target); this.emit(BrowserEvent.TargetChanged, target);
target target.browserContext().emit(BrowserContextEvent.TargetChanged, target);
.browserContext()
.emit(BrowserContextEmittedEvents.TargetChanged, target);
} }
} }
@ -192,14 +198,12 @@ export class BidiBrowser extends Browser {
: new BiDiBrowsingContextTarget(browserContext, context); : new BiDiBrowsingContextTarget(browserContext, context);
this.#targets.set(event.context, target); this.#targets.set(event.context, target);
this.emit(BrowserEmittedEvents.TargetCreated, target); this.emit(BrowserEvent.TargetCreated, target);
target target.browserContext().emit(BrowserContextEvent.TargetCreated, target);
.browserContext()
.emit(BrowserContextEmittedEvents.TargetCreated, target);
if (context.parent) { if (context.parent) {
const topLevel = this.#connection.getTopLevelContext(context.parent); const topLevel = this.#connection.getTopLevelContext(context.parent);
topLevel.emit(BrowsingContextEmittedEvents.Created, context); topLevel.emit(BrowsingContextEvent.Created, context);
} }
} }
@ -215,20 +219,18 @@ export class BidiBrowser extends Browser {
) { ) {
const context = this.#connection.getBrowsingContext(event.context); const context = this.#connection.getBrowsingContext(event.context);
const topLevelContext = this.#connection.getTopLevelContext(event.context); const topLevelContext = this.#connection.getTopLevelContext(event.context);
topLevelContext.emit(BrowsingContextEmittedEvents.Destroyed, context); topLevelContext.emit(BrowsingContextEvent.Destroyed, context);
const target = this.#targets.get(event.context); const target = this.#targets.get(event.context);
const page = await target?.page(); const page = await target?.page();
await page?.close().catch(debugError); await page?.close().catch(debugError);
this.#targets.delete(event.context); this.#targets.delete(event.context);
if (target) { if (target) {
this.emit(BrowserEmittedEvents.TargetDestroyed, target); this.emit(BrowserEvent.TargetDestroyed, target);
target target.browserContext().emit(BrowserContextEvent.TargetDestroyed, target);
.browserContext()
.emit(BrowserContextEmittedEvents.TargetDestroyed, target);
} }
} }
get connection(): Connection { get connection(): BidiConnection {
return this.#connection; return this.#connection;
} }
@ -321,11 +323,3 @@ export class BidiBrowser extends Browser {
return this.#browserTarget; return this.#browserTarget;
} }
} }
interface Options {
process?: ChildProcess;
closeCallback?: BrowserCloseCallback;
connection: Connection;
defaultViewport: Viewport | null;
ignoreHTTPSErrors?: boolean;
}

View File

@ -22,7 +22,7 @@ import {Target} from '../../api/Target.js';
import {Viewport} from '../PuppeteerViewport.js'; import {Viewport} from '../PuppeteerViewport.js';
import {BidiBrowser} from './Browser.js'; import {BidiBrowser} from './Browser.js';
import {Connection} from './Connection.js'; import {BidiConnection} from './Connection.js';
import {BidiPage} from './Page.js'; import {BidiPage} from './Page.js';
interface BrowserContextOptions { interface BrowserContextOptions {
@ -35,7 +35,7 @@ interface BrowserContextOptions {
*/ */
export class BidiBrowserContext extends BrowserContext { export class BidiBrowserContext extends BrowserContext {
#browser: BidiBrowser; #browser: BidiBrowser;
#connection: Connection; #connection: BidiConnection;
#defaultViewport: Viewport | null; #defaultViewport: Viewport | null;
#isDefault = false; #isDefault = false;
@ -62,7 +62,7 @@ export class BidiBrowserContext extends BrowserContext {
}, options); }, options);
} }
get connection(): Connection { get connection(): BidiConnection {
return this.#connection; return this.#connection;
} }

View File

@ -1,15 +1,17 @@
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js'; import ProtocolMapping from 'devtools-protocol/types/protocol-mapping.js';
import {CDPSession} from '../../api/CDPSession.js';
import {WaitForOptions} from '../../api/Page.js'; import {WaitForOptions} from '../../api/Page.js';
import {assert} from '../../util/assert.js'; import {assert} from '../../util/assert.js';
import {Deferred} from '../../util/Deferred.js'; import {Deferred} from '../../util/Deferred.js';
import {Connection as CDPConnection, CDPSession} from '../Connection.js'; import {Connection as CDPConnection} from '../Connection.js';
import {ProtocolError, TargetCloseError, TimeoutError} from '../Errors.js'; import {ProtocolError, TargetCloseError, TimeoutError} from '../Errors.js';
import {EventType} from '../EventEmitter.js';
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js'; import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
import {setPageContent, waitWithTimeout} from '../util.js'; import {setPageContent, waitWithTimeout} from '../util.js';
import {Connection} from './Connection.js'; import {BidiConnection} from './Connection.js';
import {Realm} from './Realm.js'; import {Realm} from './Realm.js';
import {debugError} from './utils.js'; import {debugError} from './utils.js';
@ -120,17 +122,26 @@ export class CDPSessionWrapper extends CDPSession {
* *
* @internal * @internal
*/ */
export const BrowsingContextEmittedEvents = { // eslint-disable-next-line @typescript-eslint/no-namespace
export namespace BrowsingContextEvent {
/** /**
* Emitted on the top-level context, when a descendant context is created. * Emitted on the top-level context, when a descendant context is created.
*/ */
Created: Symbol('BrowsingContext.created'), export const Created = Symbol('BrowsingContext.created');
/** /**
* Emitted on the top-level context, when a descendant context or the * Emitted on the top-level context, when a descendant context or the
* top-level context itself is destroyed. * top-level context itself is destroyed.
*/ */
Destroyed: Symbol('BrowsingContext.destroyed'), export const Destroyed = Symbol('BrowsingContext.destroyed');
} as const; }
/**
* @internal
*/
export interface BrowsingContextEvents extends Record<EventType, unknown> {
[BrowsingContextEvent.Created]: BrowsingContext;
[BrowsingContextEvent.Destroyed]: BrowsingContext;
}
/** /**
* @internal * @internal
@ -143,7 +154,7 @@ export class BrowsingContext extends Realm {
#browserName = ''; #browserName = '';
constructor( constructor(
connection: Connection, connection: BidiConnection,
info: Bidi.BrowsingContext.Info, info: Bidi.BrowsingContext.Info,
browserName: string browserName: string
) { ) {

View File

@ -20,7 +20,7 @@ import expect from 'expect';
import {ConnectionTransport} from '../ConnectionTransport.js'; import {ConnectionTransport} from '../ConnectionTransport.js';
import {Connection} from './Connection.js'; import {BidiConnection} from './Connection.js';
describe('WebDriver BiDi Connection', () => { describe('WebDriver BiDi Connection', () => {
class TestConnectionTransport implements ConnectionTransport { class TestConnectionTransport implements ConnectionTransport {
@ -38,7 +38,7 @@ describe('WebDriver BiDi Connection', () => {
it('should work', async () => { it('should work', async () => {
const transport = new TestConnectionTransport(); const transport = new TestConnectionTransport();
const connection = new Connection('ws://127.0.0.1', transport); const connection = new BidiConnection('ws://127.0.0.1', transport);
const responsePromise = connection.send('session.new', { const responsePromise = connection.send('session.new', {
capabilities: {}, capabilities: {},
}); });
@ -48,6 +48,7 @@ describe('WebDriver BiDi Connection', () => {
const id = JSON.parse(transport.sent[0]!).id; const id = JSON.parse(transport.sent[0]!).id;
const rawResponse = { const rawResponse = {
id, id,
type: 'success',
result: {ready: false, message: 'already connected'}, result: {ready: false, message: 'already connected'},
}; };
(transport as ConnectionTransport).onmessage?.(JSON.stringify(rawResponse)); (transport as ConnectionTransport).onmessage?.(JSON.stringify(rawResponse));

View File

@ -127,7 +127,17 @@ interface Commands {
/** /**
* @internal * @internal
*/ */
export class Connection extends EventEmitter { export type BidiEvents = {
[K in Bidi.ChromiumBidi.Event['method']]: Extract<
Bidi.ChromiumBidi.Event,
{method: K}
>['params'];
};
/**
* @internal
*/
export class BidiConnection extends EventEmitter<BidiEvents> {
#url: string; #url: string;
#transport: ConnectionTransport; #transport: ConnectionTransport;
#delay: number; #delay: number;
@ -185,35 +195,45 @@ export class Connection extends EventEmitter {
}); });
} }
debugProtocolReceive(message); debugProtocolReceive(message);
const object = JSON.parse(message) as Bidi.ChromiumBidi.Message; const object: Bidi.ChromiumBidi.Message = JSON.parse(message);
if ('type' in object) {
if ('id' in object && object.id) { switch (object.type) {
if ('error' in object) { case 'success':
this.#callbacks.resolve(object.id, object);
return;
case 'error':
if (object.id === null) {
break;
}
this.#callbacks.reject( this.#callbacks.reject(
object.id, object.id,
createProtocolError(object as Bidi.ErrorResponse), createProtocolError(object),
object.message object.message
); );
} else { return;
this.#callbacks.resolve(object.id, object); case 'event':
}
} else {
if ('error' in object || 'id' in object || 'launched' in object) {
debugError(object);
} else {
this.#maybeEmitOnContext(object); this.#maybeEmitOnContext(object);
this.emit(object.method, object.params); // SAFETY: We know the method and parameter still match here.
this.emit(
object.method,
object.params as BidiEvents[keyof BidiEvents]
);
return;
} }
} }
debugError(object);
} }
#maybeEmitOnContext(event: Bidi.ChromiumBidi.Event) { #maybeEmitOnContext(event: Bidi.ChromiumBidi.Event) {
let context: BrowsingContext | undefined; let context: BrowsingContext | undefined;
// Context specific events // Context specific events
if ('context' in event.params && event.params.context) { if ('context' in event.params && event.params.context !== null) {
context = this.#browsingContexts.get(event.params.context); context = this.#browsingContexts.get(event.params.context);
// `log.entryAdded` specific context // `log.entryAdded` specific context
} else if ('source' in event.params && event.params.source.context) { } else if (
'source' in event.params &&
event.params.source.context !== undefined
) {
context = this.#browsingContexts.get(event.params.source.context); context = this.#browsingContexts.get(event.params.source.context);
} else if (isCDPEvent(event)) { } else if (isCDPEvent(event)) {
cdpSessions cdpSessions

View File

@ -21,7 +21,7 @@ import {Deferred} from '../../util/Deferred.js';
import {interpolateFunction, stringifyFunction} from '../../util/Function.js'; import {interpolateFunction, stringifyFunction} from '../../util/Function.js';
import {Awaitable, FlattenHandle} from '../types.js'; import {Awaitable, FlattenHandle} from '../types.js';
import {Connection} from './Connection.js'; import {BidiConnection} from './Connection.js';
import {BidiFrame} from './Frame.js'; import {BidiFrame} from './Frame.js';
import {BidiSerializer} from './Serializer.js'; import {BidiSerializer} from './Serializer.js';
import {debugError} from './utils.js'; import {debugError} from './utils.js';
@ -39,6 +39,9 @@ interface RemotePromiseCallbacks {
reject: Deferred<Bidi.Script.RemoteValue>; reject: Deferred<Bidi.Script.RemoteValue>;
} }
/**
* @internal
*/
export class ExposeableFunction<Args extends unknown[], Ret> { export class ExposeableFunction<Args extends unknown[], Ret> {
readonly #frame; readonly #frame;
@ -200,7 +203,7 @@ export class ExposeableFunction<Args extends unknown[], Ret> {
} }
}; };
get #connection(): Connection { get #connection(): BidiConnection {
return this.#frame.context().connection; return this.#frame.context().connection;
} }

View File

@ -16,9 +16,9 @@
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import {CDPSession} from '../../api/CDPSession.js';
import {Frame, throwIfDetached} from '../../api/Frame.js'; import {Frame, throwIfDetached} from '../../api/Frame.js';
import {Deferred} from '../../util/Deferred.js'; import {Deferred} from '../../util/Deferred.js';
import {CDPSession} from '../Connection.js';
import {UTILITY_WORLD_NAME} from '../FrameManager.js'; import {UTILITY_WORLD_NAME} from '../FrameManager.js';
import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js'; import {PuppeteerLifeCycleEvent} from '../LifecycleWatcher.js';
import {TimeoutSettings} from '../TimeoutSettings.js'; import {TimeoutSettings} from '../TimeoutSettings.js';
@ -31,7 +31,7 @@ import {
lifeCycleToSubscribedEvent, lifeCycleToSubscribedEvent,
} from './BrowsingContext.js'; } from './BrowsingContext.js';
import {ExposeableFunction} from './ExposedFunction.js'; import {ExposeableFunction} from './ExposedFunction.js';
import {HTTPResponse} from './HTTPResponse.js'; import {BidiHTTPResponse} from './HTTPResponse.js';
import {BidiPage} from './Page.js'; import {BidiPage} from './Page.js';
import { import {
MAIN_SANDBOX, MAIN_SANDBOX,
@ -114,7 +114,7 @@ export class BidiFrame extends Frame {
timeout?: number; timeout?: number;
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
} }
): Promise<HTTPResponse | null> { ): Promise<BidiHTTPResponse | null> {
const navigationId = await this.#context.goto(url, { const navigationId = await this.#context.goto(url, {
...options, ...options,
timeout: options?.timeout ?? this.#timeoutSettings.navigationTimeout(), timeout: options?.timeout ?? this.#timeoutSettings.navigationTimeout(),
@ -146,7 +146,7 @@ export class BidiFrame extends Frame {
timeout?: number; timeout?: number;
waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[]; waitUntil?: PuppeteerLifeCycleEvent | PuppeteerLifeCycleEvent[];
} = {} } = {}
): Promise<HTTPResponse | null> { ): Promise<BidiHTTPResponse | null> {
const { const {
waitUntil = 'load', waitUntil = 'load',
timeout = this.#timeoutSettings.navigationTimeout(), timeout = this.#timeoutSettings.navigationTimeout(),

Some files were not shown because too many files have changed in this diff Show More