chore: migrate away from Node's EventEmitter (#5979)

This commit is contained in:
Jack Franklin 2020-06-15 11:52:19 +01:00 committed by GitHub
parent 6e060ce0fd
commit b659969a38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 752 additions and 32 deletions

View File

@ -336,6 +336,15 @@
* [coverage.stopCSSCoverage()](#coveragestopcsscoverage)
* [coverage.stopJSCoverage()](#coveragestopjscoverage)
- [class: TimeoutError](#class-timeouterror)
- [class: EventEmitter](#class-eventemitter)
* [eventEmitter.addListener(event, handler)](#eventemitteraddlistenerevent-handler)
* [eventEmitter.emit(event, [eventData])](#eventemitteremitevent-eventdata)
* [eventEmitter.listenerCount(event)](#eventemitterlistenercountevent)
* [eventEmitter.off(event, handler)](#eventemitteroffevent-handler)
* [eventEmitter.on(event, handler)](#eventemitteronevent-handler)
* [eventEmitter.once(event, handler)](#eventemitteronceevent-handler)
* [eventEmitter.removeAllListeners([event])](#eventemitterremovealllistenersevent)
* [eventEmitter.removeListener(event, handler)](#eventemitterremovelistenerevent-handler)
<!-- GEN:stop -->
### Overview
@ -657,7 +666,7 @@ The method initiates a GET request to download the revision from the host.
### class: Browser
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
* extends: [EventEmitter](#class-eventemitter)
A Browser is created when Puppeteer connects to a Chromium instance, either through [`puppeteer.launch`](#puppeteerlaunchoptions) or [`puppeteer.connect`](#puppeteerconnectoptions).
@ -819,7 +828,7 @@ You can find the `webSocketDebuggerUrl` from `http://${host}:${port}/json/versio
### class: BrowserContext
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
* extends: [EventEmitter](#class-eventemitter)
BrowserContexts provide a way to operate multiple independent browser sessions. When a browser is launched, it has
a single BrowserContext used by default. The method `browser.newPage()` creates a page in the default browser context.
@ -949,7 +958,7 @@ const newWindowTarget = await browserContext.waitForTarget(target => target.url(
### class: Page
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
* extends: [EventEmitter](#class-eventemitter)
Page provides methods to interact with a single tab or [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One [Browser] instance might have multiple [Page] instances.
@ -966,14 +975,16 @@ const puppeteer = require('puppeteer');
})();
```
The Page class emits various events (described below) which can be handled using any of Node's native [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) methods, such as `on`, `once` or `removeListener`.
The Page class emits various events (described below) which can be handled using
any of the [`EventEmitter`](#class-eventemitter) methods, such as `on`, `once`
or `off`.
This example logs a message for a single page `load` event:
```js
page.once('load', () => console.log('Page loaded!'));
```
To unsubscribe from events use the `removeListener` method:
To unsubscribe from events use the `off` method:
```js
function logRequest(interceptedRequest) {
@ -981,7 +992,7 @@ function logRequest(interceptedRequest) {
}
page.on('request', logRequest);
// Sometime later...
page.removeListener('request', logRequest);
page.off('request', logRequest);
```
#### event: 'close'
@ -3869,7 +3880,7 @@ If the target is not of type `"service_worker"` or `"shared_worker"`, returns `n
### class: CDPSession
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
* extends: [EventEmitter](#class-eventemitter)
The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
- protocol methods can be called with `session.send` method.
@ -3975,7 +3986,51 @@ reported.
TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. [page.waitForSelector(selector[, options])](#pagewaitforselectorselector-options) or [puppeteer.launch([options])](#puppeteerlaunchoptions).
### class: EventEmitter
A small EventEmitter class backed by [Mitt](https://github.com/developit/mitt/).
#### eventEmitter.addListener(event, handler)
- `event` <[string]|[symbol]> the event to remove the handler from.
- `handler` <[Function]> the event listener that will be added.
- returns: `this` so you can chain method calls
This method is identical to `on` and maintained for compatibility with Node's EventEmitter. We recommend using `on` by default.
#### eventEmitter.emit(event, [eventData])
- `event` <[string]|[symbol]> the event to trigger.
- `eventData` <[Object]> additional data to send with the event.
- returns: `boolean`; `true` if there are any listeners for the event, `false` if there are none.
#### eventEmitter.listenerCount(event)
- `event` <[string]|[symbol]> the event to check for listeners.
- returns: <[number]> the number of listeners for the given event.
#### eventEmitter.off(event, handler)
- `event` <[string]|[symbol]> the event to remove the handler from.
- `handler` <[Function]> the event listener that will be removed.
- returns: `this` so you can chain method calls
#### eventEmitter.on(event, handler)
- `event` <[string]|[symbol]> the event to add the handler to.
- `handler` <[Function]> the event listener that will be added.
- returns: `this` so you can chain method calls
#### eventEmitter.once(event, handler)
- `event` <[string]|[symbol]> the event to add the handler to.
- `handler` <[Function]> the event listener that will be added.
- returns: `this` so you can chain method calls
#### eventEmitter.removeAllListeners([event])
- `event` <[string]|[symbol]> optional argument to remove all listeners for the given event. If it's not given this method will remove all listeners for all events.
- returns: `this` so you can chain method calls
#### eventEmitter.removeListener(event, handler)
- `event` <[string]|[symbol]> the event to remove the handler from.
- `handler` <[Function]> the event listener that will be removed.
- returns: `this` so you can chain method calls
This method is identical to `off` and maintained for compatibility with Node's EventEmitter. We recommend using `off` by default.
[AXNode]: #accessibilitysnapshotoptions "AXNode"
[Accessibility]: #class-accessibility "Accessibility"
@ -4024,4 +4079,5 @@ TimeoutError is emitted whenever certain operations are terminated due to timeou
[selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "selector"
[stream.Readable]: https://nodejs.org/api/stream.html#stream_class_stream_readable "stream.Readable"
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type "String"
[symbol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Symbol_type "Symbol"
[xpath]: https://developer.mozilla.org/en-US/docs/Web/XPath "xpath"

View File

@ -0,0 +1,30 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [addListener](./puppeteer.eventemitter.addlistener.md)
## EventEmitter.addListener() method
> Warning: This API is now obsolete.
>
> please use `on` instead.
>
Add an event listener.
<b>Signature:</b>
```typescript
addListener(event: EventType, handler: Handler): EventEmitter;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | |
| handler | Handler | |
<b>Returns:</b>
[EventEmitter](./puppeteer.eventemitter.md)

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [emit](./puppeteer.eventemitter.emit.md)
## EventEmitter.emit() method
Emit an event and call any associated listeners.
<b>Signature:</b>
```typescript
emit(event: EventType, eventData?: any): boolean;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | the event you'd like to emit |
| eventData | any | any data you'd like to emit with the event |
<b>Returns:</b>
boolean
`true` if there are any listeners, `false` if there are not.

View File

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [listenerCount](./puppeteer.eventemitter.listenercount.md)
## EventEmitter.listenerCount() method
Gets the number of listeners for a given event.
<b>Signature:</b>
```typescript
listenerCount(event: EventType): number;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | the event to get the listener count for |
<b>Returns:</b>
number
the number of listeners bound to the given event

View File

@ -0,0 +1,33 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md)
## EventEmitter class
The EventEmitter class that many Puppeteer classes extend.
<b>Signature:</b>
```typescript
export declare class EventEmitter implements CommonEventEmitter
```
## Remarks
This allows you to listen to events that Puppeteer classes fire and act accordingly. Therefore you'll mostly use [on](./puppeteer.eventemitter.on.md) and [off](./puppeteer.eventemitter.off.md) to bind and unbind to event listeners.
The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `EventEmitter` class.
## 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. |

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [off](./puppeteer.eventemitter.off.md)
## EventEmitter.off() method
Remove an event listener from firing.
<b>Signature:</b>
```typescript
off(event: EventType, handler: Handler): EventEmitter;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | the event type you'd like to stop listening to. |
| handler | Handler | the function that should be removed. |
<b>Returns:</b>
[EventEmitter](./puppeteer.eventemitter.md)
`this` to enable you to chain calls.

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [on](./puppeteer.eventemitter.on.md)
## EventEmitter.on() method
Bind an event listener to fire when an event occurs.
<b>Signature:</b>
```typescript
on(event: EventType, handler: Handler): EventEmitter;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | the event type you'd like to listen to. Can be a string or symbol. |
| handler | Handler | the function to be called when the event occurs. |
<b>Returns:</b>
[EventEmitter](./puppeteer.eventemitter.md)
`this` to enable you to chain calls.

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [once](./puppeteer.eventemitter.once.md)
## EventEmitter.once() method
Like `on` but the listener will only be fired once and then it will be removed.
<b>Signature:</b>
```typescript
once(event: EventType, handler: Handler): EventEmitter;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | the event you'd like to listen to |
| handler | Handler | the handler function to run when the event occurs |
<b>Returns:</b>
[EventEmitter](./puppeteer.eventemitter.md)
`this` to enable you to chain calls.

View File

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [removeAllListeners](./puppeteer.eventemitter.removealllisteners.md)
## EventEmitter.removeAllListeners() method
Removes all listeners. If given an event argument, it will remove only listeners for that event.
<b>Signature:</b>
```typescript
removeAllListeners(event?: EventType): EventEmitter;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | the event to remove listeners for. |
<b>Returns:</b>
[EventEmitter](./puppeteer.eventemitter.md)
`this` to enable you to chain calls.

View File

@ -0,0 +1,30 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [puppeteer](./puppeteer.md) &gt; [EventEmitter](./puppeteer.eventemitter.md) &gt; [removeListener](./puppeteer.eventemitter.removelistener.md)
## EventEmitter.removeListener() method
> Warning: This API is now obsolete.
>
> please use `off` instead.
>
Remove an event listener.
<b>Signature:</b>
```typescript
removeListener(event: EventType, handler: Handler): EventEmitter;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| event | EventType | |
| handler | Handler | |
<b>Returns:</b>
[EventEmitter](./puppeteer.eventemitter.md)

View File

@ -18,6 +18,7 @@
| [Coverage](./puppeteer.coverage.md) | |
| [Dialog](./puppeteer.dialog.md) | Dialog instances are dispatched by the [Page](./puppeteer.page.md) via the <code>dialog</code> event. |
| [ElementHandle](./puppeteer.elementhandle.md) | |
| [EventEmitter](./puppeteer.eventemitter.md) | The EventEmitter class that many Puppeteer classes extend. |
| [ExecutionContext](./puppeteer.executioncontext.md) | |
| [FileChooser](./puppeteer.filechooser.md) | |
| [Frame](./puppeteer.frame.md) | |

View File

@ -32,7 +32,7 @@ const puppeteer = require('puppeteer');
})();
```
The Page class emits 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 [PageEmittedEvents](./puppeteer.pageemittedevents.md) enum.
## Example 2

View File

@ -51,6 +51,7 @@
"extract-zip": "^2.0.0",
"https-proxy-agent": "^4.0.0",
"mime": "^2.0.3",
"mitt": "^2.0.1",
"progress": "^2.0.1",
"proxy-from-env": "^1.0.0",
"rimraf": "^3.0.2",

View File

@ -16,7 +16,7 @@
import { helper, assert } from './helper';
import { Target } from './Target';
import * as EventEmitter from 'events';
import { EventEmitter } from './EventEmitter';
import { Events } from './Events';
import Protocol from './protocol';
import { Connection } from './Connection';

View File

@ -22,11 +22,11 @@ import * as childProcess from 'child_process';
import * as https from 'https';
import * as http from 'http';
import * as extractZip from 'extract-zip';
import * as debug from 'debug';
import * as removeRecursive from 'rimraf';
import extractZip from 'extract-zip';
import debug from 'debug';
import removeRecursive from 'rimraf';
import * as URL from 'url';
import * as ProxyAgent from 'https-proxy-agent';
import ProxyAgent from 'https-proxy-agent';
import { getProxyForUrl } from 'proxy-from-env';
import { helper, assert } from './helper';

View File

@ -15,13 +15,13 @@
*/
import { assert } from './helper';
import { Events } from './Events';
import * as debug from 'debug';
import debug from 'debug';
const debugProtocolSend = debug('puppeteer:protocol:SEND ►');
const debugProtocolReceive = debug('puppeteer:protocol:RECV ◀');
import Protocol from './protocol';
import { ConnectionTransport } from './ConnectionTransport';
import * as EventEmitter from 'events';
import { EventEmitter } from './EventEmitter';
interface ConnectionCallback {
resolve: Function;

140
src/EventEmitter.ts Normal file
View File

@ -0,0 +1,140 @@
import mitt, { Emitter, EventType, Handler } from 'mitt';
/**
* @internal
*/
export interface CommonEventEmitter {
on(event: EventType, handler: Handler): CommonEventEmitter;
off(event: EventType, handler: Handler): CommonEventEmitter;
/* 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): CommonEventEmitter;
removeListener(event: EventType, handler: Handler): CommonEventEmitter;
emit(event: EventType, eventData?: any): boolean;
once(event: EventType, handler: Handler): CommonEventEmitter;
listenerCount(event: string): number;
removeAllListeners(event?: EventType): CommonEventEmitter;
}
/**
* The EventEmitter class that many Puppeteer classes extend.
*
* @remarks
*
* This allows you to listen to events that Puppeteer classes fire and act
* accordingly. Therefore you'll mostly use {@link EventEmitter.on | on} and
* {@link EventEmitter.off | off} to bind
* and unbind to event listeners.
*
* @public
*/
export class EventEmitter implements CommonEventEmitter {
private emitter: Emitter;
private eventsMap = new Map<EventType, Handler[]>();
/**
* @internal
*/
constructor() {
this.emitter = mitt(this.eventsMap);
}
/**
* 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 handler - the function to be called when the event occurs.
* @returns `this` to enable you to chain calls.
*/
on(event: EventType, handler: Handler): EventEmitter {
this.emitter.on(event, handler);
return this;
}
/**
* Remove an event listener from firing.
* @param event - 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 calls.
*/
off(event: EventType, handler: Handler): EventEmitter {
this.emitter.off(event, handler);
return this;
}
/**
* Remove an event listener.
* @deprecated please use `off` instead.
*/
removeListener(event: EventType, handler: Handler): EventEmitter {
this.off(event, handler);
return this;
}
/**
* Add an event listener.
* @deprecated please use `on` instead.
*/
addListener(event: EventType, handler: Handler): EventEmitter {
this.on(event, handler);
return this;
}
/**
* Emit an event and call any associated listeners.
*
* @param event - 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?: any): boolean {
this.emitter.emit(event, eventData);
return this.eventListenersCount(event) > 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 handler - the handler function to run when the event occurs
* @returns `this` to enable you to chain calls.
*/
once(event: EventType, handler: Handler): EventEmitter {
const onceHandler: Handler = (eventData) => {
handler(eventData);
this.off(event, onceHandler);
};
return this.on(event, onceHandler);
}
/**
* Gets the number of listeners for a given event.
*
* @param event - 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);
}
/**
* Removes all listeners. If given an event argument, it will remove only
* listeners for that event.
* @param event - the event to remove listeners for.
* @returns `this` to enable you to chain calls.
*/
removeAllListeners(event?: EventType): EventEmitter {
if (event) {
this.eventsMap.delete(event);
} else {
this.eventsMap.clear();
}
return this;
}
private eventListenersCount(event: EventType): number {
return this.eventsMap.has(event) ? this.eventsMap.get(event).length : 0;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import * as EventEmitter from 'events';
import { EventEmitter } from './EventEmitter';
import { helper, assert, debugError } from './helper';
import { Events } from './Events';
import { ExecutionContext, EVALUATION_SCRIPT_URL } from './ExecutionContext';

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as EventEmitter from 'events';
import { EventEmitter } from './EventEmitter';
import { helper, assert, debugError } from './helper';
import Protocol from './protocol';
import { Events } from './Events';

View File

@ -15,7 +15,7 @@
*/
import * as fs from 'fs';
import * as EventEmitter from 'events';
import { EventEmitter } from './EventEmitter';
import * as mime from 'mime';
import { Events } from './Events';
import { Connection, CDPSession } from './Connection';
@ -183,7 +183,7 @@ class ScreenshotTaskQueue {
* })();
* ```
*
* The Page class emits various events which are documented in the {@link PageEmittedEvents} enum.
* The Page class extends from Puppeteer's {@link EventEmitter } class and will emit various events which are documented in the {@link PageEmittedEvents} enum.
*
* @example
* This example logs a message for a single page `load` event:

View File

@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as NodeWebSocket from 'ws';
import { ConnectionTransport } from './ConnectionTransport';
import NodeWebSocket from 'ws';
export class WebSocketTransport implements ConnectionTransport {
static create(url: string): Promise<WebSocketTransport> {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EventEmitter } from 'events';
import { EventEmitter } from './EventEmitter';
import { debugError } from './helper';
import { ExecutionContext } from './ExecutionContext';
import { JSHandle } from './JSHandle';

View File

@ -32,6 +32,7 @@ export * from './Coverage';
export * from './Dialog';
export * from './JSHandle';
export * from './ExecutionContext';
export * from './EventEmitter';
export * from './FileChooser';
export * from './FrameManager';
export * from './JSHandle';

View File

@ -29,6 +29,7 @@ module.exports = {
Dialog: require('./Dialog').Dialog,
ElementHandle: require('./JSHandle').ElementHandle,
ExecutionContext: require('./ExecutionContext').ExecutionContext,
EventEmitter: require('./EventEmitter').EventEmitter,
FileChooser: require('./FileChooser').FileChooser,
Frame: require('./FrameManager').Frame,
JSHandle: require('./JSHandle').JSHandle,

View File

@ -14,11 +14,12 @@
* limitations under the License.
*/
import { TimeoutError } from './Errors';
import * as debug from 'debug';
import debug from 'debug';
import * as fs from 'fs';
import { CDPSession } from './Connection';
import { promisify } from 'util';
import Protocol from './protocol';
import { CommonEventEmitter } from './EventEmitter';
const openAsync = promisify(fs.open);
const writeAsync = promisify(fs.write);
@ -131,13 +132,13 @@ function installAsyncStackHooks(classType: AnyClass): void {
}
export interface PuppeteerEventListener {
emitter: NodeJS.EventEmitter;
emitter: CommonEventEmitter;
eventName: string | symbol;
handler: (...args: any[]) => void;
}
function addEventListener(
emitter: NodeJS.EventEmitter,
emitter: CommonEventEmitter,
eventName: string | symbol,
handler: (...args: any[]) => void
): PuppeteerEventListener {
@ -147,7 +148,7 @@ function addEventListener(
function removeEventListeners(
listeners: Array<{
emitter: NodeJS.EventEmitter;
emitter: CommonEventEmitter;
eventName: string | symbol;
handler: (...args: any[]) => void;
}>
@ -166,7 +167,7 @@ function isNumber(obj: unknown): obj is number {
}
async function waitForEvent<T extends any>(
emitter: NodeJS.EventEmitter,
emitter: CommonEventEmitter,
eventName: string | symbol,
predicate: (event: T) => boolean,
timeout: number,

View File

@ -14,9 +14,9 @@
* limitations under the License.
*/
import * as debug from 'debug';
import debug from 'debug';
import * as removeFolder from 'rimraf';
import removeFolder from 'rimraf';
import * as childProcess from 'child_process';
import { helper, assert, debugError } from '../helper';
import { LaunchOptions } from './LaunchOptions';

154
test/EventEmitter.spec.js Normal file
View File

@ -0,0 +1,154 @@
const { EventEmitter } = require('../lib/EventEmitter');
const sinon = require('sinon');
const expect = require('expect');
describe('EventEmitter', () => {
let emitter;
beforeEach(() => {
emitter = new EventEmitter();
});
describe('on', () => {
const onTests = (methodName) => {
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');
expect(listener.callCount).toEqual(1);
});
it(`${methodName} sends the event data to the handler`, () => {
const listener = sinon.spy();
const data = {};
emitter[methodName]('foo', listener);
emitter.emit('foo', data);
expect(listener.callCount).toEqual(1);
expect(listener.firstCall.args[0]).toBe(data);
});
it(`${methodName}: supports chaining`, () => {
const listener = sinon.spy();
const returnValue = emitter[methodName]('foo', listener);
expect(returnValue).toBe(emitter);
});
};
onTests('on');
// we support addListener for legacy reasons
onTests('addListener');
});
describe('off', () => {
const offTests = (methodName) => {
it(`${methodName}: removes the listener so it is no longer called`, () => {
const listener = sinon.spy();
emitter.on('foo', listener);
emitter.emit('foo');
expect(listener.callCount).toEqual(1);
emitter.off('foo', listener);
emitter.emit('foo');
expect(listener.callCount).toEqual(1);
});
it(`${methodName}: supports chaining`, () => {
const listener = sinon.spy();
emitter.on('foo', listener);
const returnValue = emitter.off('foo', listener);
expect(returnValue).toBe(emitter);
});
};
offTests('off');
// we support removeListener for legacy reasons
offTests('removeListener');
});
describe('once', () => {
it('only calls the listener once and then removes it', () => {
const listener = sinon.spy();
emitter.once('foo', listener);
emitter.emit('foo');
expect(listener.callCount).toEqual(1);
emitter.emit('foo');
expect(listener.callCount).toEqual(1);
});
it('supports chaining', () => {
const listener = sinon.spy();
const returnValue = emitter.once('foo', listener);
expect(returnValue).toBe(emitter);
});
});
describe('emit', () => {
it('calls all the listeners for an event', () => {
const listener1 = sinon.spy();
const listener2 = sinon.spy();
const listener3 = sinon.spy();
emitter.on('foo', listener1).on('foo', listener2).on('bar', listener3);
emitter.emit('foo');
expect(listener1.callCount).toEqual(1);
expect(listener2.callCount).toEqual(1);
expect(listener3.callCount).toEqual(0);
});
it('passes data through to the listener', () => {
const listener = sinon.spy();
emitter.on('foo', listener);
const data = {};
emitter.emit('foo', data);
expect(listener.callCount).toEqual(1);
expect(listener.firstCall.args[0]).toBe(data);
});
it('returns true if the event has listeners', () => {
const listener = sinon.spy();
emitter.on('foo', listener);
expect(emitter.emit('foo')).toBe(true);
});
it('returns false if the event has listeners', () => {
const listener = sinon.spy();
emitter.on('foo', listener);
expect(emitter.emit('notFoo')).toBe(false);
});
});
describe('listenerCount', () => {
it('returns the number of listeners for the given event', () => {
emitter.on('foo', () => {});
emitter.on('foo', () => {});
emitter.on('bar', () => {});
expect(emitter.listenerCount('foo')).toEqual(2);
expect(emitter.listenerCount('bar')).toEqual(1);
expect(emitter.listenerCount('noListeners')).toEqual(0);
});
});
describe('removeAllListeners', () => {
it('removes every listener from all events by default', () => {
emitter.on('foo', () => {}).on('bar', () => {});
emitter.removeAllListeners();
expect(emitter.emit('foo')).toBe(false);
expect(emitter.emit('bar')).toBe(false);
});
it('returns the emitter for chaining', () => {
expect(emitter.removeAllListeners()).toBe(emitter);
});
it('can filter to remove only listeners for a given event name', () => {
emitter
.on('foo', () => {})
.on('bar', () => {})
.on('bar', () => {});
emitter.removeAllListeners('bar');
expect(emitter.emit('foo')).toBe(true);
expect(emitter.emit('bar')).toBe(false);
});
});
});

View File

@ -18,6 +18,7 @@ const path = require('path');
const utils = require('./utils');
const { waitEvent } = utils;
const expect = require('expect');
const sinon = require('sinon');
const {
getTestState,
setupTestBrowserHooks,
@ -130,6 +131,29 @@ describe('Page', function () {
});
});
// This test fails on Firefox on CI consistently but cannot be replicated
// locally. Skipping for now to unblock the Mitt release and given FF support
// isn't fully done yet but raising an issue to ask the FF folks to have a
// look at this.
describeFailsFirefox('removing and adding event handlers', () => {
it('should correctly fire event handlers as they are added and then removed', async () => {
const { page, server } = getTestState();
const handler = sinon.spy();
page.on('response', handler);
await page.goto(server.EMPTY_PAGE);
expect(handler.callCount).toBe(1);
page.off('response', handler);
await page.goto(server.EMPTY_PAGE);
// Still one because we removed the handler.
expect(handler.callCount).toBe(1);
page.on('response', handler);
await page.goto(server.EMPTY_PAGE);
// Two now because we added the handler back.
expect(handler.callCount).toBe(2);
});
});
describeFailsFirefox('Page.Events.error', function () {
it('should throw when page crashes', async () => {
const { page } = getTestState();

View File

@ -7,7 +7,8 @@
"moduleResolution": "node",
"module": "CommonJS",
"declaration": true,
"declarationMap": true
"declarationMap": true,
"esModuleInterop": true
},
"include": [
"src"

View File

@ -180,6 +180,27 @@ const expectedNonExistingMethods = new Map([
['Page', new Set(['emulateMedia'])],
]);
// All the methods from our EventEmitter that we don't document for each subclass.
const EVENT_LISTENER_METHODS = new Set([
'emit',
'listenerCount',
'off',
'on',
'once',
'removeListener',
'addListener',
'removeAllListeners',
]);
/* Methods that are defined in code but are not documented */
const expectedNotFoundMethods = new Map([
['Browser', EVENT_LISTENER_METHODS],
['BrowserContext', EVENT_LISTENER_METHODS],
['CDPSession', EVENT_LISTENER_METHODS],
['Page', EVENT_LISTENER_METHODS],
['WebWorker', EVENT_LISTENER_METHODS],
]);
/**
* @param {!Documentation} actual
* @param {!Documentation} expected
@ -205,14 +226,24 @@ function compareDocumentations(actual, expected) {
const methodDiff = diff(actualMethods, expectedMethods);
for (const methodName of methodDiff.extra) {
const missingMethodsForClass = expectedNonExistingMethods.get(className);
if (missingMethodsForClass && missingMethodsForClass.has(methodName))
const nonExistingMethodsForClass = expectedNonExistingMethods.get(
className
);
if (
nonExistingMethodsForClass &&
nonExistingMethodsForClass.has(methodName)
)
continue;
errors.push(`Non-existing method found: ${className}.${methodName}()`);
}
for (const methodName of methodDiff.missing)
for (const methodName of methodDiff.missing) {
const missingMethodsForClass = expectedNotFoundMethods.get(className);
if (missingMethodsForClass && missingMethodsForClass.has(methodName))
continue;
errors.push(`Method not found: ${className}.${methodName}()`);
}
for (const methodName of methodDiff.equal) {
const actualMethod = actualClass.methods.get(methodName);
@ -619,6 +650,62 @@ function compareDocumentations(actual, expected) {
expectedName: 'SnapshotOptions',
},
],
[
'Method EventEmitter.emit() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.listenerCount() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.off() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.on() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.once() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.removeListener() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.addListener() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
[
'Method EventEmitter.removeAllListeners() event',
{
actualName: 'string|symbol',
expectedName: 'Object',
},
],
]);
const expectedForSource = expectedNamingMismatches.get(source);