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
| Enumeration | Description |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [BrowserContextEmittedEvents](./puppeteer.browsercontextemittedevents.md) | |
| [BrowserEmittedEvents](./puppeteer.browseremittedevents.md) | All the events a [browser instance](./puppeteer.browser.md) may emit. |
| --------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [BrowserContextEvent](./puppeteer.browsercontextevent.md) | |
| [BrowserEvent](./puppeteer.browserevent.md) | All the events a [browser instance](./puppeteer.browser.md) may emit. |
| [InterceptResolutionAction](./puppeteer.interceptresolutionaction.md) | |
| [LocatorEmittedEvents](./puppeteer.locatoremittedevents.md) | All the events that a locator instance may emit. |
| [PageEmittedEvents](./puppeteer.pageemittedevents.md) | All the events that a page instance may emit. |
| [LocatorEvent](./puppeteer.locatorevent.md) | All the events that a locator instance may emit. |
| [PageEvent](./puppeteer.pageevent.md) | All the events that a page instance may emit. |
| [TargetType](./puppeteer.targettype.md) | |
## Functions
@ -66,14 +66,17 @@ sidebar_label: API
## Interfaces
| Interface | Description |
| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [ActionOptions](./puppeteer.actionoptions.md) | |
| [AutofillData](./puppeteer.autofilldata.md) | |
| [BoundingBox](./puppeteer.boundingbox.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. |
| [BrowserContextEvents](./puppeteer.browsercontextevents.md) | |
| [BrowserContextOptions](./puppeteer.browsercontextoptions.md) | BrowserContext options. |
| [BrowserEvents](./puppeteer.browserevents.md) | |
| [BrowserLaunchArgumentOptions](./puppeteer.browserlaunchargumentoptions.md) | Launcher options that only apply to Chrome. |
| [CDPSessionEvents](./puppeteer.cdpsessionevents.md) | |
| [ClickOptions](./puppeteer.clickoptions.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> |
@ -88,6 +91,7 @@ sidebar_label: API
| [Device](./puppeteer.device.md) | |
| [FrameAddScriptTagOptions](./puppeteer.frameaddscripttagoptions.md) | |
| [FrameAddStyleTagOptions](./puppeteer.frameaddstyletagoptions.md) | |
| [FrameEvents](./puppeteer.frameevents.md) | |
| [FrameWaitForFunctionOptions](./puppeteer.framewaitforfunctionoptions.md) | |
| [GeolocationOptions](./puppeteer.geolocationoptions.md) | |
| [InterceptResolutionState](./puppeteer.interceptresolutionstate.md) | |
@ -97,7 +101,7 @@ sidebar_label: API
| [KeyboardTypeOptions](./puppeteer.keyboardtypeoptions.md) | |
| [KeyDownOptions](./puppeteer.keydownoptions.md) | |
| [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) | |
| [LocatorScrollOptions](./puppeteer.locatorscrolloptions.md) | |
| [MediaFeature](./puppeteer.mediafeature.md) | |
@ -110,7 +114,7 @@ sidebar_label: API
| [NetworkConditions](./puppeteer.networkconditions.md) | |
| [NewDocumentScriptEvaluation](./puppeteer.newdocumentscriptevaluation.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) | |
| [PDFOptions](./puppeteer.pdfoptions.md) | Valid options to configure PDF generation via [Page.pdf()](./puppeteer.page.pdf.md). |
| [Point](./puppeteer.point.md) | |
@ -155,13 +159,14 @@ sidebar_label: API
| [Awaitable](./puppeteer.awaitable.md) | |
| [AwaitableIterable](./puppeteer.awaitableiterable.md) | |
| [AwaitedLocator](./puppeteer.awaitedlocator.md) | |
| [CDPEvents](./puppeteer.cdpevents.md) | |
| [ChromeReleaseChannel](./puppeteer.chromereleasechannel.md) | |
| [ConsoleMessageType](./puppeteer.consolemessagetype.md) | The supported types for console messages. |
| [ElementFor](./puppeteer.elementfor.md) | |
| [ErrorCode](./puppeteer.errorcode.md) | |
| [EvaluateFunc](./puppeteer.evaluatefunc.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> |
| [FlattenHandle](./puppeteer.flattenhandle.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:
```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
## 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.

View File

@ -9,14 +9,14 @@ BrowserContexts provide a way to operate multiple independent browser sessions.
#### Signature:
```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
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.

View File

@ -1,13 +1,13 @@
---
sidebar_label: BrowserContextEmittedEvents
sidebar_label: BrowserContextEvent
---
# BrowserContextEmittedEvents enum
# BrowserContextEvent enum
#### Signature:
```typescript
export declare const enum BrowserContextEmittedEvents
export declare const enum BrowserContextEvent
```
## 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.
#### Signature:
```typescript
export declare const enum BrowserEmittedEvents
export declare const enum BrowserEvent
```
## 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:
```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

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
interface CommonEventEmitter {
addListener(event: EventType, handler: Handler): this;
addListener<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | |
| handler | [Handler](./puppeteer.handler.md) | |
| --------- | ------------------------------------------------------ | ----------- |
| type | Key | |
| handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | |
**Returns:**

View File

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

View File

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

View File

@ -7,18 +7,18 @@ sidebar_label: CommonEventEmitter
#### Signature:
```typescript
export interface CommonEventEmitter
export interface CommonEventEmitter<Events extends Record<EventType, unknown>>
```
## Methods
| Method | Description |
| ---------------------------------------------------------------------------------- | ----------- |
| [addListener(event, handler)](./puppeteer.commoneventemitter.addlistener.md) | |
| [emit(event, eventData)](./puppeteer.commoneventemitter.emit.md) | |
| --------------------------------------------------------------------------------- | ----------- |
| [addListener(type, handler)](./puppeteer.commoneventemitter.addlistener.md) | |
| [emit(type, event)](./puppeteer.commoneventemitter.emit.md) | |
| [listenerCount(event)](./puppeteer.commoneventemitter.listenercount.md) | |
| [off(event, handler)](./puppeteer.commoneventemitter.off.md) | |
| [on(event, handler)](./puppeteer.commoneventemitter.on.md) | |
| [once(event, handler)](./puppeteer.commoneventemitter.once.md) | |
| [off(type, handler)](./puppeteer.commoneventemitter.off.md) | |
| [on(type, handler)](./puppeteer.commoneventemitter.on.md) | |
| [once(type, handler)](./puppeteer.commoneventemitter.once.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
interface CommonEventEmitter {
off(event: EventType, handler: Handler): this;
off<Key extends keyof Events>(
type: Key,
handler?: Handler<Events[Key]>
): this;
}
```
## Parameters
| Parameter | Type | Description |
| --------- | ------------------------------------- | ----------- |
| event | [EventType](./puppeteer.eventtype.md) | |
| handler | [Handler](./puppeteer.handler.md) | |
| --------- | ------------------------------------------------------ | ------------ |
| type | Key | |
| handler | [Handler](./puppeteer.handler.md)&lt;Events\[Key\]&gt; | _(Optional)_ |
**Returns:**

View File

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

View File

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

View File

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

View File

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

View File

@ -7,10 +7,10 @@ sidebar_label: Connection
#### Signature:
```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

View File

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

View File

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

View File

@ -10,15 +10,15 @@ Gets the number of listeners for a given event.
```typescript
class EventEmitter {
listenerCount(event: EventType): number;
listenerCount(type: keyof EventsWithWildcard<Events>): number;
}
```
## Parameters
| 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:**

View File

@ -9,10 +9,10 @@ The EventEmitter class that many Puppeteer classes extend.
#### Signature:
```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
@ -23,12 +23,12 @@ The constructor for this class is marked as internal. Third-party code should no
## Methods
| Method | Modifiers | Description |
| ---------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| [addListener(event, handler)](./puppeteer.eventemitter.addlistener.md) | | Add an event listener. |
| [emit(event, eventData)](./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. |
| [off(event, 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. |
| [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. |
| [removeAllListeners(event)](./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. |
| --------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| [addListener(type, handler)](./puppeteer.eventemitter.addlistener.md) | | Add an event listener. |
| [emit(type, event)](./puppeteer.eventemitter.emit.md) | | Emit an event and call any associated listeners. |
| [listenerCount(type)](./puppeteer.eventemitter.listenercount.md) | | Gets the number of listeners for a given event. |
| [off(type, handler)](./puppeteer.eventemitter.off.md) | | Remove an event listener from firing. |
| [on(type, handler)](./puppeteer.eventemitter.on.md) | | Bind an event listener to fire when an event occurs. |
| [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(type)](./puppeteer.eventemitter.removealllisteners.md) | | Removes all listeners. If given an event argument, it will remove only listeners for that event. |
| [removeListener(type, handler)](./puppeteer.eventemitter.removelistener.md) | | Remove an event listener. |

View File

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

View File

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

View File

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

View File

@ -10,15 +10,15 @@ Removes all listeners. If given an event argument, it will remove only listeners
```typescript
class EventEmitter {
removeAllListeners(event?: EventType): this;
removeAllListeners(type?: keyof EventsWithWildcard<Events>): this;
}
```
## Parameters
| 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:**

View File

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

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:
```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
@ -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> |
| [hover(this, options)](./puppeteer.locator.hover.md) | | |
| [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. |
| [scroll(this, options)](./puppeteer.locator.scroll.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.
#### Signature:
```typescript
export declare enum LocatorEmittedEvents
export declare enum LocatorEvent
```
## 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:
```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
@ -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
@ -52,7 +52,7 @@ This example logs a message for a single page `load` event:
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
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. |
| [mainFrame()](./puppeteer.page.mainframe.md) | | The page's main frame. |
| [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. |
| [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) | | |

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.
#### Signature:
```typescript
export declare const enum PageEmittedEvents
export declare const enum PageEvent
```
## 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.
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:
```typescript
export interface PageEventObject
export interface PageEvents extends Record<EventType, unknown>
```
**Extends:** Record&lt;EventType, unknown&gt;
## Properties
| Property | Modifiers | Type | Description | Default |
| ---------------------- | --------- | -------------------------------------------------------------- | ----------- | ------- |
| close | | never | | |
| close | | undefined | | |
| console | | [ConsoleMessage](./puppeteer.consolemessage.md) | | |
| dialog | | [Dialog](./puppeteer.dialog.md) | | |
| domcontentloaded | | never | | |
| domcontentloaded | | undefined | | |
| error | | Error | | |
| frameattached | | [Frame](./puppeteer.frame.md) | | |
| framedetached | | [Frame](./puppeteer.frame.md) | | |
| framenavigated | | [Frame](./puppeteer.frame.md) | | |
| load | | never | | |
| load | | undefined | | |
| metrics | | { title: string; metrics: [Metrics](./puppeteer.metrics.md); } | | |
| pageerror | | Error | | |
| popup | | [Page](./puppeteer.page.md) | | |
| popup | | [Page](./puppeteer.page.md) \| null | | |
| request | | [HTTPRequest](./puppeteer.httprequest.md) | | |
| requestfailed | | [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:
```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

32
package-lock.json generated
View File

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

View File

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

View File

@ -21,7 +21,7 @@ import {ChildProcess} from 'child_process';
import {Protocol} from 'devtools-protocol';
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 {Deferred} from '../util/Deferred.js';
@ -130,7 +130,7 @@ export interface WaitForTargetOptions {
*
* @public
*/
export const enum BrowserEmittedEvents {
export const enum BrowserEvent {
/**
* Emitted when Puppeteer gets disconnected from the browser instance. This
* 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.
*/
Disconnected = 'disconnected',
/**
* 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.
*/
TargetChanged = 'targetchanged',
/**
* 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}
@ -171,6 +169,31 @@ export const enum BrowserEmittedEvents {
* Note that this includes target destructions in incognito browser contexts.
*/
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
*
* 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
* An example of using a {@link Browser} to create a {@link Page}:
@ -219,7 +242,7 @@ export const enum BrowserEmittedEvents {
* @public
*/
export class Browser
extends EventEmitter
extends EventEmitter<BrowserEvents>
implements AsyncDisposable, Disposable
{
/**
@ -389,8 +412,8 @@ export class Browser
const {timeout = 30000} = options;
const targetDeferred = Deferred.create<Target | PromiseLike<Target>>();
this.on(BrowserEmittedEvents.TargetCreated, check);
this.on(BrowserEmittedEvents.TargetChanged, check);
this.on(BrowserEvent.TargetCreated, check);
this.on(BrowserEvent.TargetChanged, check);
try {
this.targets().forEach(check);
if (!timeout) {
@ -402,8 +425,8 @@ export class Browser
timeout
);
} finally {
this.off(BrowserEmittedEvents.TargetCreated, check);
this.off(BrowserEmittedEvents.TargetChanged, check);
this.off(BrowserEvent.TargetCreated, check);
this.off(BrowserEvent.TargetChanged, check);
}
async function check(target: Target): Promise<void> {
@ -491,28 +514,3 @@ export class Browser
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.
*/
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 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
* 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
* 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
* belong to the parent page's browser context.
@ -55,7 +97,7 @@ import type {Target} from './Target.js';
* @public
*/
export class BrowserContext extends EventEmitter {
export class BrowserContext extends EventEmitter<BrowserContextEvents> {
/**
* @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.
*/
import {CDPSession} from '../common/Connection.js';
import {CDPSession} from './CDPSession.js';
import {Realm} from './Realm.js';
/**

View File

@ -14,12 +14,13 @@
* limitations under the License.
*/
import Protocol from 'devtools-protocol';
import {ClickOptions, ElementHandle} from '../api/ElementHandle.js';
import {HTTPResponse} from '../api/HTTPResponse.js';
import {Page, WaitTimeoutOptions} from '../api/Page.js';
import {CDPSession} from '../common/Connection.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 {transposeIterableHandle} from '../common/HandleIterator.js';
import {
@ -43,6 +44,7 @@ import {
import {assert} from '../util/assert.js';
import {throwIfDisposed} from '../util/decorators.js';
import {CDPSession} from './CDPSession.js';
import {KeyboardTypeOptions} from './Input.js';
import {FunctionLocator, Locator, NodeLocator} from './locators/locators.js';
import {Realm} from './Realm.js';
@ -127,6 +129,44 @@ export interface FrameAddStyleTagOptions {
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
*/
@ -181,13 +221,13 @@ export const throwIfDetached = throwIfDisposed<Frame>(frame => {
* Frame lifecycles are controlled by three events that are all dispatched on
* the parent {@link Frame.page | page}:
*
* - {@link PageEmittedEvents.FrameAttached}
* - {@link PageEmittedEvents.FrameNavigated}
* - {@link PageEmittedEvents.FrameDetached}
* - {@link PageEvent.FrameAttached}
* - {@link PageEvent.FrameNavigated}
* - {@link PageEvent.FrameDetached}
*
* @public
*/
export abstract class Frame extends EventEmitter {
export abstract class Frame extends EventEmitter<FrameEvents> {
/**
* @internal
*/

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ import {
retry,
tap,
} 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 {debugError, timeout} from '../../common/util.js';
import {BoundingBox, ClickOptions, ElementHandle} from '../ElementHandle.js';
@ -130,20 +130,34 @@ export interface LocatorScrollOptions extends ActionOptions {
*
* @public
*/
export enum LocatorEmittedEvents {
export enum LocatorEvent {
/**
* Emitted every time before the locator performs an action on the located element(s).
*/
Action = 'action',
}
export {
/**
* @deprecated Use {@link LocatorEvent}.
*/
LocatorEvent as LocatorEmittedEvents,
};
/**
* @public
*/
export interface LocatorEventObject {
[LocatorEmittedEvents.Action]: never;
export interface LocatorEvents extends Record<EventType, unknown> {
[LocatorEvent.Action]: undefined;
}
export {
/**
* @deprecated Use {@link LocatorEvents}.
*/
LocatorEvents as LocatorEventObject,
};
/**
* 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
@ -152,7 +166,7 @@ export interface LocatorEventObject {
*
* @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
* acts.
@ -224,27 +238,6 @@ export abstract class Locator<T> extends EventEmitter {
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> {
const locator = this._clone();
locator._timeout = timeout;
@ -426,7 +419,7 @@ export abstract class Locator<T> extends EventEmitter {
signal
),
tap(() => {
return this.emit(LocatorEmittedEvents.Action);
return this.emit(LocatorEvent.Action, undefined);
}),
mergeMap(handle => {
return from(handle.click(options)).pipe(
@ -456,7 +449,7 @@ export abstract class Locator<T> extends EventEmitter {
signal
),
tap(() => {
return this.emit(LocatorEmittedEvents.Action);
return this.emit(LocatorEvent.Action, undefined);
}),
mergeMap(handle => {
return from(
@ -587,7 +580,7 @@ export abstract class Locator<T> extends EventEmitter {
signal
),
tap(() => {
return this.emit(LocatorEmittedEvents.Action);
return this.emit(LocatorEvent.Action, undefined);
}),
mergeMap(handle => {
return from(handle.hover()).pipe(
@ -615,7 +608,7 @@ export abstract class Locator<T> extends EventEmitter {
signal
),
tap(() => {
return this.emit(LocatorEmittedEvents.Action);
return this.emit(LocatorEvent.Action, undefined);
}),
mergeMap(handle => {
return from(

View File

@ -16,10 +16,9 @@
import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.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.
* @public

View File

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

View File

@ -21,33 +21,33 @@ import {Protocol} from 'devtools-protocol';
import {
Browser as BrowserBase,
BrowserCloseCallback,
TargetFilterCallback,
IsPageTargetCallback,
BrowserEmittedEvents,
BrowserContextEmittedEvents,
BrowserContextOptions,
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
BrowserEvent,
IsPageTargetCallback,
Permission,
TargetFilterCallback,
WEB_PERMISSION_TO_PROTOCOL_PERMISSION,
} 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 {Target} from '../api/Target.js';
import {USE_TAB_TARGET} from '../environment.js';
import {assert} from '../util/assert.js';
import {ChromeTargetManager} from './ChromeTargetManager.js';
import {CDPSession, Connection, ConnectionEmittedEvents} from './Connection.js';
import {Connection} from './Connection.js';
import {FirefoxTargetManager} from './FirefoxTargetManager.js';
import {Viewport} from './PuppeteerViewport.js';
import {
CDPTarget,
DevToolsTarget,
InitializationStatus,
OtherTarget,
PageTarget,
CDPTarget,
WorkerTarget,
DevToolsTarget,
} from './Target.js';
import {TargetManager, TargetManagerEmittedEvents} from './TargetManager.js';
import {TargetManager, TargetManagerEvent} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js';
/**
@ -151,52 +151,46 @@ export class CDPBrowser extends BrowserBase {
}
#emitDisconnected = () => {
this.emit(BrowserEmittedEvents.Disconnected);
this.emit(BrowserEvent.Disconnected, undefined);
};
override async _attach(): Promise<void> {
this.#connection.on(
ConnectionEmittedEvents.Disconnected,
this.#emitDisconnected
);
this.#connection.on(CDPSessionEvent.Disconnected, this.#emitDisconnected);
this.#targetManager.on(
TargetManagerEmittedEvents.TargetAvailable,
TargetManagerEvent.TargetAvailable,
this.#onAttachedToTarget
);
this.#targetManager.on(
TargetManagerEmittedEvents.TargetGone,
TargetManagerEvent.TargetGone,
this.#onDetachedFromTarget
);
this.#targetManager.on(
TargetManagerEmittedEvents.TargetChanged,
TargetManagerEvent.TargetChanged,
this.#onTargetChanged
);
this.#targetManager.on(
TargetManagerEmittedEvents.TargetDiscovered,
TargetManagerEvent.TargetDiscovered,
this.#onTargetDiscovered
);
await this.#targetManager.initialize();
}
override _detach(): void {
this.#connection.off(
ConnectionEmittedEvents.Disconnected,
this.#emitDisconnected
);
this.#connection.off(CDPSessionEvent.Disconnected, this.#emitDisconnected);
this.#targetManager.off(
TargetManagerEmittedEvents.TargetAvailable,
TargetManagerEvent.TargetAvailable,
this.#onAttachedToTarget
);
this.#targetManager.off(
TargetManagerEmittedEvents.TargetGone,
TargetManagerEvent.TargetGone,
this.#onDetachedFromTarget
);
this.#targetManager.off(
TargetManagerEmittedEvents.TargetChanged,
TargetManagerEvent.TargetChanged,
this.#onTargetChanged
);
this.#targetManager.off(
TargetManagerEmittedEvents.TargetDiscovered,
TargetManagerEvent.TargetDiscovered,
this.#onTargetDiscovered
);
}
@ -367,10 +361,8 @@ export class CDPBrowser extends BrowserBase {
(await target._initializedDeferred.valueOrThrow()) ===
InitializationStatus.SUCCESS
) {
this.emit(BrowserEmittedEvents.TargetCreated, target);
target
.browserContext()
.emit(BrowserContextEmittedEvents.TargetCreated, target);
this.emit(BrowserEvent.TargetCreated, target);
target.browserContext().emit(BrowserContextEvent.TargetCreated, target);
}
};
@ -381,22 +373,18 @@ export class CDPBrowser extends BrowserBase {
(await target._initializedDeferred.valueOrThrow()) ===
InitializationStatus.SUCCESS
) {
this.emit(BrowserEmittedEvents.TargetDestroyed, target);
target
.browserContext()
.emit(BrowserContextEmittedEvents.TargetDestroyed, target);
this.emit(BrowserEvent.TargetDestroyed, target);
target.browserContext().emit(BrowserContextEvent.TargetDestroyed, target);
}
};
#onTargetChanged = ({target}: {target: CDPTarget}): void => {
this.emit(BrowserEmittedEvents.TargetChanged, target);
target
.browserContext()
.emit(BrowserContextEmittedEvents.TargetChanged, target);
this.emit(BrowserEvent.TargetChanged, target);
target.browserContext().emit(BrowserContextEvent.TargetChanged, target);
};
#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 {TargetFilterCallback} from '../api/Browser.js';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {TargetType} from '../api/Target.js';
import {assert} from '../util/assert.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 {InitializationStatus, CDPTarget} from './Target.js';
import {CDPTarget, InitializationStatus} from './Target.js';
import {
TargetFactory,
TargetManager,
TargetManagerEmittedEvents,
TargetManagerEvent,
TargetManagerEvents,
} from './TargetManager.js';
import {debugError} from './util.js';
@ -49,7 +51,10 @@ function isPageTargetBecomingPrimary(
*
* @internal
*/
export class ChromeTargetManager extends EventEmitter implements TargetManager {
export class ChromeTargetManager
extends EventEmitter<TargetManagerEvents>
implements TargetManager
{
#connection: Connection;
/**
* Keeps track of the following events: 'Target.targetCreated',
@ -81,7 +86,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
#attachedToTargetListenersBySession = new WeakMap<
CDPSession | Connection,
(event: Protocol.Target.AttachedToTargetEvent) => Promise<void>
(event: Protocol.Target.AttachedToTargetEvent) => void
>();
#detachedFromTargetListenersBySession = new WeakMap<
CDPSession | Connection,
@ -116,7 +121,10 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#connection.on('Target.targetCreated', this.#onTargetCreated);
this.#connection.on('Target.targetDestroyed', this.#onTargetDestroyed);
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.#connection
@ -176,7 +184,10 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#connection.off('Target.targetCreated', this.#onTargetCreated);
this.#connection.off('Target.targetDestroyed', this.#onTargetDestroyed);
this.#connection.off('Target.targetInfoChanged', this.#onTargetInfoChanged);
this.#connection.off('sessiondetached', this.#onSessionDetached);
this.#connection.off(
CDPSessionEvent.SessionDetached,
this.#onSessionDetached
);
this.#removeAttachmentListeners(this.#connection);
}
@ -193,7 +204,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
#setupAttachmentListeners(session: CDPSession | Connection): void {
const listener = (event: Protocol.Target.AttachedToTargetEvent) => {
return this.#onAttachedToTarget(session, event);
void this.#onAttachedToTarget(session, event);
};
assert(!this.#attachedToTargetListenersBySession.has(session));
this.#attachedToTargetListenersBySession.set(session, listener);
@ -210,11 +221,9 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
}
#removeAttachmentListeners(session: CDPSession | Connection): void {
if (this.#attachedToTargetListenersBySession.has(session)) {
session.off(
'Target.attachedToTarget',
this.#attachedToTargetListenersBySession.get(session)!
);
const listener = this.#attachedToTargetListenersBySession.get(session);
if (listener) {
session.off('Target.attachedToTarget', listener);
this.#attachedToTargetListenersBySession.delete(session);
}
@ -237,7 +246,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
event.targetInfo
);
this.emit(TargetManagerEmittedEvents.TargetDiscovered, event.targetInfo);
this.emit(TargetManagerEvent.TargetDiscovered, event.targetInfo);
// The connection is already attached to the browser target implicitly,
// 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
// the worker is destroyed.
const target = this.#attachedTargetsByTargetId.get(event.targetId);
this.emit(TargetManagerEmittedEvents.TargetGone, target);
if (target) {
this.emit(TargetManagerEvent.TargetGone, target);
this.#attachedTargetsByTargetId.delete(event.targetId);
}
}
};
#onTargetInfoChanged = (event: Protocol.Target.TargetInfoChangedEvent) => {
@ -301,14 +312,14 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
session,
'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);
if (wasInitialized && previousURL !== target.url()) {
this.emit(TargetManagerEmittedEvents.TargetChanged, {
target: target,
this.emit(TargetManagerEvent.TargetChanged, {
target,
wasInitialized,
previousURL,
});
@ -359,7 +370,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
const target = this.#targetFactory(targetInfo);
target._initialize();
this.#attachedTargetsByTargetId.set(targetInfo.targetId, target);
this.emit(TargetManagerEmittedEvents.TargetAvailable, target);
this.emit(TargetManagerEvent.TargetAvailable, target);
return;
}
@ -398,11 +409,15 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
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);
if (!isExistingTarget && isTargetExposed(target)) {
this.emit(TargetManagerEmittedEvents.TargetAvailable, target);
this.emit(TargetManagerEvent.TargetAvailable, target);
}
this.#finishInitializationIfReady();
@ -440,7 +455,7 @@ export class ChromeTargetManager extends EventEmitter implements TargetManager {
this.#attachedTargetsByTargetId.delete(target._targetId);
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 {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 {CDPCDPSession} from './CDPSession.js';
import {ConnectionTransport} from './ConnectionTransport.js';
import {debug} from './Debug.js';
import {TargetCloseError, ProtocolError} from './Errors.js';
import {ProtocolError, TargetCloseError} from './Errors.js';
import {EventEmitter} from './EventEmitter.js';
import {CDPTarget} from './Target.js';
import {debugError} from './util.js';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
@ -35,15 +39,6 @@ const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
*/
export {ConnectionTransport, ProtocolMapping};
/**
* Internal events that the Connection class emits.
*
* @internal
*/
export const ConnectionEmittedEvents = {
Disconnected: Symbol('Connection.Disconnected'),
} as const;
/**
* @internal
*/
@ -200,12 +195,12 @@ export class CallbackRegistry {
/**
* @public
*/
export class Connection extends EventEmitter {
export class Connection extends EventEmitter<CDPSessionEvents> {
#url: string;
#transport: ConnectionTransport;
#delay: number;
#timeout: number;
#sessions = new Map<string, CDPSessionImpl>();
#sessions = new Map<string, CDPCDPSession>();
#closed = false;
#manuallyAttached = new Set<string>();
#callbacks = new CallbackRegistry();
@ -315,27 +310,27 @@ export class Connection extends EventEmitter {
const object = JSON.parse(message);
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const session = new CDPSessionImpl(
const session = new CDPCDPSession(
this,
object.params.targetInfo.type,
sessionId,
object.sessionId
);
this.#sessions.set(sessionId, session);
this.emit('sessionattached', session);
this.emit(CDPSessionEvent.SessionAttached, session);
const parentSession = this.#sessions.get(object.sessionId);
if (parentSession) {
parentSession.emit('sessionattached', session);
parentSession.emit(CDPSessionEvent.SessionAttached, session);
}
} else if (object.method === 'Target.detachedFromTarget') {
const session = this.#sessions.get(object.params.sessionId);
if (session) {
session._onClosed();
this.#sessions.delete(object.params.sessionId);
this.emit('sessiondetached', session);
this.emit(CDPSessionEvent.SessionDetached, session);
const parentSession = this.#sessions.get(object.sessionId);
if (parentSession) {
parentSession.emit('sessiondetached', session);
parentSession.emit(CDPSessionEvent.SessionDetached, session);
}
}
}
@ -371,7 +366,7 @@ export class Connection extends EventEmitter {
session._onClosed();
}
this.#sessions.clear();
this.emit(ConnectionEmittedEvents.Disconnected);
this.emit(CDPSessionEvent.Disconnected, undefined);
}
dispose(): void {
@ -422,240 +417,7 @@ export class Connection extends EventEmitter {
/**
* @internal
*/
export interface CDPSessionOnMessageObject {
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: {
export function createProtocolErrorMessage(object: {
error: {message: string; data: any; code: number};
}): string {
let message = `${object.error.message}`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,12 +14,20 @@
* 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
*/
export type EventType = string | symbol;
EventType,
} from '../../third_party/mitt/index.js';
/**
* @public
*/
@ -28,22 +36,42 @@ export type Handler<T = unknown> = (event: T) => void;
/**
* @public
*/
export interface CommonEventEmitter {
on(event: EventType, handler: Handler): this;
off(event: EventType, handler: Handler): this;
export interface CommonEventEmitter<Events extends Record<EventType, unknown>> {
on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): 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
* rather than `off`.
* If you're implementing new code you should use `off`.
*/
addListener(event: EventType, handler: Handler): this;
removeListener(event: EventType, handler: Handler): this;
emit(event: EventType, eventData?: unknown): boolean;
once(event: EventType, handler: Handler): this;
listenerCount(event: string): number;
addListener<Key extends keyof Events>(
type: Key,
handler: Handler<Events[Key]>
): this;
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.
*
@ -56,110 +84,153 @@ export interface CommonEventEmitter {
*
* @public
*/
export class EventEmitter implements CommonEventEmitter {
private emitter: Emitter<Record<string | symbol, any>>;
private eventsMap: EventHandlerMap<Record<string | symbol, any>> = new Map();
export class EventEmitter<Events extends Record<EventType, unknown>>
implements CommonEventEmitter<EventsWithWildcard<Events>>
{
#emitter: Emitter<Events & {'*': Events[keyof Events]}>;
#handlers: EventHandlerMap<Events & {'*': Events[keyof Events]}> = new Map();
/**
* @internal
*/
constructor() {
this.emitter = mitt(this.eventsMap);
this.#emitter = mitt(this.#handlers);
}
/**
* 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.
* @returns `this` to enable you to chain method calls.
*/
on(event: EventType, handler: Handler<any>): this {
this.emitter.on(event, handler);
on<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.#emitter.on(type, handler);
return this;
}
/**
* 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.
* @returns `this` to enable you to chain method calls.
*/
off(event: EventType, handler: Handler<any>): this {
this.emitter.off(event, handler);
off<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler?: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.#emitter.off(type, handler);
return this;
}
/**
* Remove an event listener.
*
* @deprecated please use {@link EventEmitter.off} instead.
*/
removeListener(event: EventType, handler: Handler<any>): this {
this.off(event, handler);
removeListener<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.off(type, handler);
return this;
}
/**
* Add an event listener.
*
* @deprecated please use {@link EventEmitter.on} instead.
*/
addListener(event: EventType, handler: Handler<any>): this {
this.on(event, handler);
addListener<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
this.on(type, handler);
return this;
}
/**
* 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
* @returns `true` if there are any listeners, `false` if there are not.
*/
emit(event: EventType, eventData?: unknown): boolean {
this.emitter.emit(event, eventData);
return this.eventListenersCount(event) > 0;
emit<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
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.
* @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
* @returns `this` to enable you to chain method calls.
*/
once(event: EventType, handler: Handler<any>): this {
const onceHandler: Handler<any> = eventData => {
once<Key extends keyof EventsWithWildcard<Events>>(
type: Key,
handler: Handler<EventsWithWildcard<Events>[Key]>
): this {
const onceHandler: Handler<EventsWithWildcard<Events>[Key]> = 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.
*
* @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
*/
listenerCount(event: EventType): number {
return this.eventListenersCount(event);
listenerCount(type: keyof EventsWithWildcard<Events>): number {
return this.#handlers.get(type)?.length || 0;
}
/**
* Removes all listeners. If given an event argument, it will remove only
* 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.
*/
removeAllListeners(event?: EventType): this {
if (event) {
this.eventsMap.delete(event);
removeAllListeners(type?: keyof EventsWithWildcard<Events>): this {
if (type === undefined || type === '*') {
this.#handlers.clear();
} else {
this.eventsMap.clear();
this.#handlers.delete(type);
}
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 {CDPSession} from '../api/CDPSession.js';
import type {ElementHandle} from '../api/ElementHandle.js';
import {JSHandle} from '../api/JSHandle.js';
import type PuppeteerUtil from '../injected/injected.js';
@ -24,7 +25,6 @@ import {stringifyFunction} from '../util/Function.js';
import {ARIAQueryHandler} from './AriaQueryHandler.js';
import {Binding} from './Binding.js';
import {CDPSession} from './Connection.js';
import {CDPElementHandle} from './ElementHandle.js';
import {IsolatedWorld} from './IsolatedWorld.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
*/
export class ExecutionContext {

View File

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

View File

@ -16,14 +16,14 @@
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 {Page, WaitTimeoutOptions} from '../api/Page.js';
import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
import {
DeviceRequestPrompt,
DeviceRequestPromptManager,
@ -34,21 +34,6 @@ import {MAIN_WORLD, PUPPETEER_WORLD} from './IsolatedWorlds.js';
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
import {setPageContent} from './util.js';
/**
* We use symbols to prevent external parties listening to these events.
* 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
*/
@ -80,7 +65,7 @@ export class CDPFrame extends Frame {
this.updateClient(client);
this.on(FrameEmittedEvents.FrameSwappedByActivation, () => {
this.on(FrameEvent.FrameSwappedByActivation, () => {
// Emulate loading process for swapped frames.
this._onLoadingStarted();
this._onLoadingStopped();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,15 +14,15 @@
* 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 {CDPSession, CDPSessionEmittedEvents} from './Connection.js';
import {EventEmitter, Handler} from './EventEmitter.js';
import {HTTPRequest} from './HTTPRequest.js';
import {HTTPResponse} from './HTTPResponse.js';
import {EventEmitter, EventSubscription, EventType} from './EventEmitter.js';
import {CDPHTTPRequest} from './HTTPRequest.js';
import {CDPHTTPResponse} from './HTTPResponse.js';
import {FetchRequestId, NetworkEventManager} from './NetworkEventManager.js';
import {debugError, isString} from './util.js';
@ -58,13 +58,27 @@ export interface InternalNetworkConditions extends NetworkConditions {
*
* @internal
*/
export const NetworkManagerEmittedEvents = {
Request: Symbol('NetworkManager.Request'),
RequestServedFromCache: Symbol('NetworkManager.RequestServedFromCache'),
Response: Symbol('NetworkManager.Response'),
RequestFailed: Symbol('NetworkManager.RequestFailed'),
RequestFinished: Symbol('NetworkManager.RequestFinished'),
} as const;
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace NetworkManagerEvent {
export const Request = Symbol('NetworkManager.Request');
export const RequestServedFromCache = Symbol(
'NetworkManager.RequestServedFromCache'
);
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
@ -76,7 +90,7 @@ export interface FrameProvider {
/**
* @internal
*/
export class NetworkManager extends EventEmitter {
export class NetworkManager extends EventEmitter<NetworkManagerEvents> {
#ignoreHTTPSErrors: boolean;
#frameManager: FrameProvider;
#networkEventManager = new NetworkEventManager();
@ -90,7 +104,7 @@ export class NetworkManager extends EventEmitter {
#userAgent?: string;
#userAgentMetadata?: Protocol.Emulation.UserAgentMetadata;
#handlers = new Map<string, Function>([
#handlers = Object.freeze([
['Fetch.requestPaused', this.#onRequestPaused],
['Fetch.authRequired', this.#onAuthRequired],
['Network.requestWillBeSent', this.#onRequestWillBeSent],
@ -99,12 +113,10 @@ export class NetworkManager extends EventEmitter {
['Network.loadingFinished', this.#onLoadingFinished],
['Network.loadingFailed', this.#onLoadingFailed],
['Network.responseReceivedExtraInfo', this.#onResponseReceivedExtraInfo],
]);
[CDPSessionEvent.Disconnected, this.#removeClient],
] as const);
#clients = new Map<
CDPSession,
Array<{event: string | symbol; handler: Handler}>
>();
#clients = new Map<CDPSession, DisposableStack>();
constructor(ignoreHTTPSErrors: boolean, frameManager: FrameProvider) {
super();
@ -116,20 +128,16 @@ export class NetworkManager extends EventEmitter {
if (this.#clients.has(client)) {
return;
}
const listeners: Array<{event: string | symbol; handler: Handler}> = [];
this.#clients.set(client, listeners);
const subscriptions = new DisposableStack();
this.#clients.set(client, subscriptions);
for (const [event, handler] of this.#handlers) {
listeners.push({
event,
handler: handler.bind(this, client),
});
client.on(event, listeners.at(-1)!.handler);
subscriptions.use(
// TODO: Remove any here.
new EventSubscription(client, event, (arg: any) => {
return handler.bind(this)(client, arg);
})
);
}
listeners.push({
event: CDPSessionEmittedEvents.Disconnected,
handler: this.#removeClient.bind(this, client),
});
client.on(CDPSessionEmittedEvents.Disconnected, listeners.at(-1)!.handler);
await Promise.all([
this.#ignoreHTTPSErrors
? client.send('Security.setIgnoreCertificateErrors', {
@ -146,10 +154,7 @@ export class NetworkManager extends EventEmitter {
}
async #removeClient(client: CDPSession) {
const listeners = this.#clients.get(client);
for (const {event, handler} of listeners || []) {
client.off(event, handler);
}
this.#clients.get(client)?.dispose();
this.#clients.delete(client);
}
@ -447,7 +452,7 @@ export class NetworkManager extends EventEmitter {
? this.#frameManager.frame(event.frameId)
: null;
const request = new HTTPRequest(
const request = new CDPHTTPRequest(
client,
frame,
event.requestId,
@ -455,7 +460,7 @@ export class NetworkManager extends EventEmitter {
event,
[]
);
this.emit(NetworkManagerEmittedEvents.Request, request);
this.emit(NetworkManagerEvent.Request, request);
void request.finalizeInterceptions();
}
@ -464,7 +469,7 @@ export class NetworkManager extends EventEmitter {
event: Protocol.Network.RequestWillBeSentEvent,
fetchRequestId?: FetchRequestId
): void {
let redirectChain: HTTPRequest[] = [];
let redirectChain: CDPHTTPRequest[] = [];
if (event.redirectResponse) {
// We want to emit a response and requestfinished for the
// 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)
: null;
const request = new HTTPRequest(
const request = new CDPHTTPRequest(
client,
frame,
fetchRequestId,
@ -513,7 +518,7 @@ export class NetworkManager extends EventEmitter {
redirectChain
);
this.#networkEventManager.storeRequest(event.requestId, request);
this.emit(NetworkManagerEmittedEvents.Request, request);
this.emit(NetworkManagerEvent.Request, request);
void request.finalizeInterceptions();
}
@ -525,16 +530,16 @@ export class NetworkManager extends EventEmitter {
if (request) {
request._fromMemoryCache = true;
}
this.emit(NetworkManagerEmittedEvents.RequestServedFromCache, request);
this.emit(NetworkManagerEvent.RequestServedFromCache, request);
}
#handleRequestRedirect(
client: CDPSession,
request: HTTPRequest,
request: CDPHTTPRequest,
responsePayload: Protocol.Network.Response,
extraInfo: Protocol.Network.ResponseReceivedExtraInfoEvent | null
): void {
const response = new HTTPResponse(
const response = new CDPHTTPResponse(
client,
request,
responsePayload,
@ -546,8 +551,8 @@ export class NetworkManager extends EventEmitter {
new Error('Response body is unavailable for redirect responses')
);
this.#forgetRequest(request, false);
this.emit(NetworkManagerEmittedEvents.Response, response);
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
this.emit(NetworkManagerEvent.Response, response);
this.emit(NetworkManagerEvent.RequestFinished, request);
}
#emitResponseEvent(
@ -582,14 +587,14 @@ export class NetworkManager extends EventEmitter {
extraInfo = null;
}
const response = new HTTPResponse(
const response = new CDPHTTPResponse(
client,
request,
responseReceived.response,
extraInfo
);
request._response = response;
this.emit(NetworkManagerEmittedEvents.Response, response);
this.emit(NetworkManagerEvent.Response, response);
}
#onResponseReceived(
@ -654,7 +659,7 @@ export class NetworkManager extends EventEmitter {
this.#networkEventManager.responseExtraInfo(event.requestId).push(event);
}
#forgetRequest(request: HTTPRequest, events: boolean): void {
#forgetRequest(request: CDPHTTPRequest, events: boolean): void {
const requestId = request._requestId;
const interceptionId = request._interceptionId;
@ -697,7 +702,7 @@ export class NetworkManager extends EventEmitter {
request.response()?._resolveBody(null);
}
this.#forgetRequest(request, true);
this.emit(NetworkManagerEmittedEvents.RequestFinished, request);
this.emit(NetworkManagerEvent.RequestFinished, request);
}
#onLoadingFailed(
@ -729,6 +734,6 @@ export class NetworkManager extends EventEmitter {
response._resolveBody(null);
}
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 {BrowserContext} from '../api/BrowserContext.js';
import {CDPSession, CDPSessionEvent} from '../api/CDPSession.js';
import {ElementHandle} from '../api/ElementHandle.js';
import {Frame} from '../api/Frame.js';
import {HTTPRequest} from '../api/HTTPRequest.js';
@ -31,7 +32,7 @@ import {
Metrics,
NewDocumentScriptEvaluation,
Page,
PageEmittedEvents,
PageEvent,
ScreenshotClip,
ScreenshotOptions,
WaitForOptions,
@ -43,33 +44,28 @@ import {isErrorLike} from '../util/ErrorLike.js';
import {Accessibility} from './Accessibility.js';
import {Binding} from './Binding.js';
import {
CDPSession,
CDPSessionEmittedEvents,
CDPSessionImpl,
isTargetClosedError,
} from './Connection.js';
import {CDPCDPSession} from './CDPSession.js';
import {isTargetClosedError} from './Connection.js';
import {ConsoleMessage, ConsoleMessageType} from './ConsoleMessage.js';
import {Coverage} from './Coverage.js';
import {DeviceRequestPrompt} from './DeviceRequestPrompt.js';
import {CDPDialog} from './Dialog.js';
import {EmulationManager} from './EmulationManager.js';
import {TargetCloseError} from './Errors.js';
import {Handler} from './EventEmitter.js';
import {FileChooser} from './FileChooser.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 {MAIN_WORLD} from './IsolatedWorlds.js';
import {
Credentials,
NetworkConditions,
NetworkManagerEmittedEvents,
NetworkManagerEvent,
} from './NetworkManager.js';
import {PDFOptions} from './PDFOptions.js';
import {Viewport} from './PuppeteerViewport.js';
import {CDPTarget} from './Target.js';
import {TargetManagerEmittedEvents} from './TargetManager.js';
import {TargetManagerEvent} from './TargetManager.js';
import {TaskQueue} from './TaskQueue.js';
import {TimeoutSettings} from './TimeoutSettings.js';
import {Tracing} from './Tracing.js';
@ -146,58 +142,81 @@ export class CDPPage extends Page {
#serviceWorkerBypassed = false;
#userDragInterceptionEnabled = false;
#frameManagerHandlers = new Map<symbol, Handler<any>>([
#frameManagerHandlers = Object.freeze([
[
FrameManagerEmittedEvents.FrameAttached,
this.emit.bind(this, PageEmittedEvents.FrameAttached),
FrameManagerEvent.FrameAttached,
(frame: CDPFrame) => {
this.emit(PageEvent.FrameAttached, frame);
},
],
[
FrameManagerEmittedEvents.FrameDetached,
this.emit.bind(this, PageEmittedEvents.FrameDetached),
FrameManagerEvent.FrameDetached,
(frame: CDPFrame) => {
this.emit(PageEvent.FrameDetached, frame);
},
],
[
FrameManagerEmittedEvents.FrameNavigated,
this.emit.bind(this, PageEmittedEvents.FrameNavigated),
FrameManagerEvent.FrameNavigated,
(frame: CDPFrame) => {
this.emit(PageEvent.FrameNavigated, frame);
},
],
]);
] as const);
#networkManagerHandlers = new Map<symbol, Handler<any>>([
#networkManagerHandlers = Object.freeze([
[
NetworkManagerEmittedEvents.Request,
this.emit.bind(this, PageEmittedEvents.Request),
NetworkManagerEvent.Request,
(request: HTTPRequest) => {
this.emit(PageEvent.Request, request);
},
],
[
NetworkManagerEmittedEvents.RequestServedFromCache,
this.emit.bind(this, PageEmittedEvents.RequestServedFromCache),
NetworkManagerEvent.RequestServedFromCache,
(request: HTTPRequest) => {
this.emit(PageEvent.RequestServedFromCache, request);
},
],
[
NetworkManagerEmittedEvents.Response,
this.emit.bind(this, PageEmittedEvents.Response),
NetworkManagerEvent.Response,
(response: HTTPResponse) => {
this.emit(PageEvent.Response, response);
},
],
[
NetworkManagerEmittedEvents.RequestFailed,
this.emit.bind(this, PageEmittedEvents.RequestFailed),
NetworkManagerEvent.RequestFailed,
(request: HTTPRequest) => {
this.emit(PageEvent.RequestFailed, request);
},
],
[
NetworkManagerEmittedEvents.RequestFinished,
this.emit.bind(this, PageEmittedEvents.RequestFinished),
NetworkManagerEvent.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')
);
},
],
[
'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.bindingCalled', this.#onBindingCalled.bind(this)],
['Page.javascriptDialogOpening', this.#onDialog.bind(this)],
@ -206,7 +225,7 @@ export class CDPPage extends Page {
['Performance.metrics', this.#emitMetrics.bind(this)],
['Log.entryAdded', this.#onLogEntryAdded.bind(this)],
['Page.fileChooserOpened', this.#onFileChooser.bind(this)],
]);
] as const);
constructor(
client: CDPSession,
@ -236,10 +255,10 @@ export class CDPPage extends Page {
this.#setupEventListeners();
this.#tabSession?.on(CDPSessionEmittedEvents.Swapped, async newSession => {
this.#tabSession?.on(CDPSessionEvent.Swapped, async newSession => {
this.#client = newSession;
assert(
this.#client instanceof CDPSessionImpl,
this.#client instanceof CDPCDPSession,
'CDPSession is not instance of CDPSessionImpl'
);
this.#target = this.#client._target();
@ -254,57 +273,49 @@ export class CDPPage extends Page {
await this.#frameManager.swapFrameTree(newSession);
this.#setupEventListeners();
});
this.#tabSession?.on(
CDPSessionEmittedEvents.Ready,
(session: CDPSessionImpl) => {
this.#tabSession?.on(CDPSessionEvent.Ready, session => {
assert(session instanceof CDPCDPSession);
if (session._target()._subtype() !== 'prerender') {
return;
}
this.#frameManager
.registerSpeculativeSession(session)
.catch(debugError);
this.#frameManager.registerSpeculativeSession(session).catch(debugError);
this.#emulationManager
.registerSpeculativeSession(session)
.catch(debugError);
}
);
});
}
#setupEventListeners() {
this.#client.on(CDPSessionEmittedEvents.Ready, this.#onAttachedToTarget);
this.#client.on(CDPSessionEvent.Ready, this.#onAttachedToTarget);
this.#target
._targetManager()
.on(TargetManagerEmittedEvents.TargetGone, this.#onDetachedFromTarget);
.on(TargetManagerEvent.TargetGone, this.#onDetachedFromTarget);
for (const [eventName, handler] of this.#frameManagerHandlers) {
this.#frameManager.on(eventName, handler);
}
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) {
this.#client.on(eventName, handler);
// TODO: Remove any.
this.#client.on(eventName, handler as any);
}
this.#target._isClosedDeferred
.valueOrThrow()
.then(() => {
this.#client.off(
CDPSessionEmittedEvents.Ready,
this.#onAttachedToTarget
);
this.#client.off(CDPSessionEvent.Ready, this.#onAttachedToTarget);
this.#target
._targetManager()
.off(
TargetManagerEmittedEvents.TargetGone,
this.#onDetachedFromTarget
);
.off(TargetManagerEvent.TargetGone, this.#onDetachedFromTarget);
this.emit(PageEmittedEvents.Close);
this.emit(PageEvent.Close, undefined);
this.#closed = true;
})
.catch(debugError);
@ -317,10 +328,11 @@ export class CDPPage extends Page {
return;
}
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());
if (session._target()._getTargetInfo().type === 'worker') {
const worker = new WebWorker(
@ -330,9 +342,9 @@ export class CDPPage extends Page {
this.#handleException.bind(this)
);
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> {
@ -434,7 +446,7 @@ export class CDPPage extends Page {
}
#onTargetCrashed(): void {
this.emit('error', new Error('Page crashed!'));
this.emit(PageEvent.Error, new Error('Page crashed!'));
}
#onLogEntryAdded(event: Protocol.Log.EntryAddedEvent): void {
@ -446,7 +458,7 @@ export class CDPPage extends Page {
}
if (source !== 'worker') {
this.emit(
PageEmittedEvents.Console,
PageEvent.Console,
new ConsoleMessage(level, text, [], [{url, lineNumber}])
);
}
@ -703,7 +715,7 @@ export class CDPPage extends Page {
}
#emitMetrics(event: Protocol.Performance.MetricsEvent): void {
this.emit(PageEmittedEvents.Metrics, {
this.emit(PageEvent.Metrics, {
title: event.title,
metrics: this.#buildMetricsObject(event.metrics),
});
@ -724,7 +736,7 @@ export class CDPPage extends Page {
#handleException(exception: Protocol.Runtime.ExceptionThrownEvent): void {
this.emit(
PageEmittedEvents.PageError,
PageEvent.PageError,
createClientError(exception.exceptionDetails)
);
}
@ -801,7 +813,7 @@ export class CDPPage extends Page {
args: JSHandle[],
stackTrace?: Protocol.Runtime.StackTrace
): void {
if (!this.listenerCount(PageEmittedEvents.Console)) {
if (!this.listenerCount(PageEvent.Console)) {
args.forEach(arg => {
return arg.dispose();
});
@ -834,7 +846,7 @@ export class CDPPage extends Page {
args,
stackTraceLocations
);
this.emit(PageEmittedEvents.Console, message);
this.emit(PageEvent.Console, message);
}
#onDialog(event: Protocol.Page.JavascriptDialogOpeningEvent): void {
@ -845,7 +857,7 @@ export class CDPPage extends Page {
event.message,
event.defaultPrompt
);
this.emit(PageEmittedEvents.Dialog, dialog);
this.emit(PageEvent.Dialog, dialog);
}
override async reload(
@ -870,7 +882,7 @@ export class CDPPage extends Page {
const {timeout = this.#timeoutSettings.timeout()} = options;
return await waitForEvent(
this.#frameManager.networkManager,
NetworkManagerEmittedEvents.Request,
NetworkManagerEvent.Request,
async request => {
if (isString(urlOrPredicate)) {
return urlOrPredicate === request.url();
@ -894,7 +906,7 @@ export class CDPPage extends Page {
const {timeout = this.#timeoutSettings.timeout()} = options;
return await waitForEvent(
this.#frameManager.networkManager,
NetworkManagerEmittedEvents.Response,
NetworkManagerEvent.Response,
async response => {
if (isString(urlOrPredicate)) {
return urlOrPredicate === response.url();

View File

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

View File

@ -16,8 +16,9 @@
import {Protocol} from 'devtools-protocol';
import {CDPSession} from './Connection.js';
import {EventEmitter} from './EventEmitter.js';
import {CDPSession} from '../api/CDPSession.js';
import {EventEmitter, EventType} from './EventEmitter.js';
import {CDPTarget} from './Target.js';
/**
@ -29,6 +30,33 @@ export type TargetFactory = (
parentSession?: CDPSession
) => 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
* responsible for coordinating the configuration of targets with the rest of
@ -40,21 +68,8 @@ export type TargetFactory = (
*
* @internal
*/
export interface TargetManager extends EventEmitter {
export interface TargetManager extends EventEmitter<TargetManagerEvents> {
getAvailableTargets(): Map<string, CDPTarget>;
initialize(): Promise<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
* limitations under the License.
*/
import {CDPSession} from '../api/CDPSession.js';
import {assert} from '../util/assert.js';
import {Deferred} from '../util/Deferred.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {CDPSession} from './Connection.js';
import {getReadableAsBuffer, getReadableFromProtocolStream} from './util.js';
/**
@ -126,6 +126,7 @@ export class Tracing {
const contentDeferred = Deferred.create<Buffer | undefined>();
this.#client.once('Tracing.tracingComplete', async event => {
try {
assert(event.stream, 'Missing "stream"');
const readable = await getReadableFromProtocolStream(
this.#client,
event.stream

View File

@ -15,11 +15,11 @@
*/
import {Protocol} from 'devtools-protocol';
import {CDPSession} from '../api/CDPSession.js';
import {Realm} from '../api/Realm.js';
import {CDPSession} from './Connection.js';
import {ConsoleMessageType} from './ConsoleMessage.js';
import {EventEmitter} from './EventEmitter.js';
import {EventEmitter, EventType} from './EventEmitter.js';
import {ExecutionContext} from './ExecutionContext.js';
import {IsolatedWorld} from './IsolatedWorld.js';
import {CDPJSHandle} from './JSHandle.js';
@ -33,7 +33,7 @@ import {debugError, withSourcePuppeteerURLIfNone} from './util.js';
export type ConsoleAPICalledCallback = (
eventType: ConsoleMessageType,
handles: CDPJSHandle[],
trace: Protocol.Runtime.StackTrace
trace?: Protocol.Runtime.StackTrace
) => void;
/**
@ -69,7 +69,7 @@ export type ExceptionThrownCallback = (
*
* @public
*/
export class WebWorker extends EventEmitter {
export class WebWorker extends EventEmitter<Record<EventType, unknown>> {
/**
* @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 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 {EventEmitter, Handler} from '../EventEmitter.js';
import {Handler} from '../EventEmitter.js';
import {Connection as BidiPPtrConnection} from './Connection.js';
type CdpEvents = {
[Property in keyof ProtocolMapping.Events]: ProtocolMapping.Events[Property][0];
};
import {BidiConnection} from './Connection.js';
/**
* @internal
*/
export async function connectBidiOverCDP(
cdp: CDPPPtrConnection
): Promise<BidiPPtrConnection> {
cdp: CDPConnection
): Promise<BidiConnection> {
const transportBiDi = new NoOpTransport();
const cdpConnectionAdapter = new CDPConnectionAdapter(cdp);
const pptrTransport = {
@ -53,7 +50,7 @@ export async function connectBidiOverCDP(
// Forwards a BiDi event sent by BidiServer to Puppeteer.
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(
transportBiDi,
cdpConnectionAdapter,
@ -67,16 +64,16 @@ export async function connectBidiOverCDP(
* @internal
*/
class CDPConnectionAdapter {
#cdp: CDPPPtrConnection;
#cdp: CDPConnection;
#adapters = new Map<CDPSession, CDPClientAdapter<CDPSession>>();
#browser: CDPClientAdapter<CDPPPtrConnection>;
#browser: CDPClientAdapter<CDPConnection>;
constructor(cdp: CDPPPtrConnection) {
constructor(cdp: CDPConnection) {
this.#cdp = cdp;
this.#browser = new CDPClientAdapter(cdp);
}
browserClient(): CDPClientAdapter<CDPPPtrConnection> {
browserClient(): CDPClientAdapter<CDPConnection> {
return this.#browser;
}
@ -107,8 +104,8 @@ class CDPConnectionAdapter {
*
* @internal
*/
class CDPClientAdapter<T extends EventEmitter & Pick<CDPPPtrConnection, 'send'>>
extends BidiMapper.EventEmitter<CdpEvents>
class CDPClientAdapter<T extends CDPSession | CDPConnection>
extends BidiMapper.EventEmitter<CDPEvents>
implements BidiMapper.CdpClient
{
#closed = false;
@ -132,9 +129,9 @@ class CDPClientAdapter<T extends EventEmitter & Pick<CDPPPtrConnection, 'send'>>
return this.#browserClient!;
}
#forwardMessage = <T extends keyof CdpEvents>(
#forwardMessage = <T extends keyof CDPEvents>(
method: T,
event: CdpEvents[T]
event: CDPEvents[T]
) => {
this.emit(method, event);
};

View File

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

View File

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

View File

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

View File

@ -127,7 +127,17 @@ interface Commands {
/**
* @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;
#transport: ConnectionTransport;
#delay: number;
@ -185,35 +195,45 @@ export class Connection extends EventEmitter {
});
}
debugProtocolReceive(message);
const object = JSON.parse(message) as Bidi.ChromiumBidi.Message;
if ('id' in object && object.id) {
if ('error' in object) {
const object: Bidi.ChromiumBidi.Message = JSON.parse(message);
if ('type' in object) {
switch (object.type) {
case 'success':
this.#callbacks.resolve(object.id, object);
return;
case 'error':
if (object.id === null) {
break;
}
this.#callbacks.reject(
object.id,
createProtocolError(object as Bidi.ErrorResponse),
createProtocolError(object),
object.message
);
} else {
this.#callbacks.resolve(object.id, object);
}
} else {
if ('error' in object || 'id' in object || 'launched' in object) {
debugError(object);
} else {
return;
case 'event':
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) {
let context: BrowsingContext | undefined;
// 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);
// `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);
} else if (isCDPEvent(event)) {
cdpSessions

View File

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

View File

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

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