mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
events: initial implementation of experimental EventTarget
See documentation changes for details Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/33556 Refs: https://github.com/nodejs/node/pull/33527 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Bradley Farias <bradley.meck@gmail.com>
This commit is contained in:
1 parent
47044a91c6
commit
785842a053
8 files changed
+1316
No files matched your search
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
const common = require('../common.js');
|
||||
|
||||
const bench = common.createBenchmark(main, {
|
||||
n: [2e7],
|
||||
listeners: [1, 5, 10]
|
||||
}, { flags: ['--expose-internals'] });
|
||||
|
||||
function main({ n, listeners }) {
|
||||
const { EventTarget, Event } = require('internal/event_target');
|
||||
const target = new EventTarget();
|
||||
|
||||
for (let n = 0; n < listeners; n++)
|
||||
target.addEventListener('foo', () => {});
|
||||
|
||||
const event = new Event('foo');
|
||||
|
||||
bench.start();
|
||||
for (let i = 0; i < n; i++) {
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
bench.end(n);
|
||||
|
||||
}
|
||||
@@ -903,6 +903,11 @@ Encoding provided to `TextDecoder()` API was not one of the
|
||||
|
||||
`--print` cannot be used with ESM input.
|
||||
|
||||
<a id="ERR_EVENT_RECURSION"></a>
|
||||
### `ERR_EVENT_RECURSION`
|
||||
|
||||
Thrown when an attempt is made to recursively dispatch an event on `EventTarget`.
|
||||
|
||||
<a id="ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE"></a>
|
||||
### `ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE`
|
||||
|
||||
|
||||
@@ -935,6 +935,462 @@ if the `EventEmitter` emits `'error'`. It removes all listeners when
|
||||
exiting the loop. The `value` returned by each iteration is an array
|
||||
composed of the emitted event arguments.
|
||||
|
||||
## `EventTarget` and `Event` API
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1 - Experimental
|
||||
|
||||
The `EventTarget` and `Event` objects are a Node.js-specific implementation
|
||||
of the [`EventTarget` Web API][] that are exposed by some Node.js core APIs.
|
||||
Neither the `EventTarget` nor `Event` classes are currently available for end
|
||||
user code to create.
|
||||
|
||||
```js
|
||||
const target = getEventTargetSomehow();
|
||||
|
||||
target.addEventListener('foo', (event) => {
|
||||
console.log('foo event happened!');
|
||||
});
|
||||
```
|
||||
|
||||
### Node.js `EventTarget` vs. DOM `EventTarget`
|
||||
|
||||
There are two key differences between the Node.js `EventTarget` and the
|
||||
[`EventTarget` Web API][]:
|
||||
|
||||
1. Whereas DOM `EventTarget` instances *may* be hierarchical, there is no
|
||||
concept of hierarchy and event propagation in Node.js. That is, an event
|
||||
dispatched to an `EventTarget` does not propagate through a hierarchy of
|
||||
nested target objects that may each have their own set of handlers for the
|
||||
event.
|
||||
2. In the Node.js `EventTarget`, if an event listener is an async function
|
||||
or returns a `Promise`, and the returned `Promise` rejects, the rejection
|
||||
will be automatically captured and handled the same way as a listener that
|
||||
throws synchronously (see [`EventTarget` Error Handling][] for details).
|
||||
|
||||
### `NodeEventTarget` vs. `EventEmitter`
|
||||
|
||||
The `NodeEventTarget` object implements a modified subset of the
|
||||
`EventEmitter` API that allows it to closely *emulate* an `EventEmitter` in
|
||||
certain situations. It is important to understand, however, that an
|
||||
`NodeEventTarget` is *not* an instance of `EventEmitter` and cannot be used in
|
||||
place of an `EventEmitter` in most cases.
|
||||
|
||||
1. Unlike `EventEmitter`, any given `listener` can be registered at most once
|
||||
per event `type`. Attempts to register a `listener` multiple times will be
|
||||
ignored.
|
||||
2. The `NodeEventTarget` does not emulate the full `EventEmitter` API.
|
||||
Specifically the `prependListener()`, `prependOnceListener()`,
|
||||
`rawListeners()`, `setMaxListeners()`, `getMaxListeners()`, and
|
||||
`errorMonitor` APIs are not emulated. The `'newListener'` and
|
||||
`'removeListener'` events will also not be emitted.
|
||||
3. The `NodeEventTarget` does not implement any special default behavior
|
||||
for events with type `'error'`.
|
||||
3. The `NodeEventTarget` supports `EventListener` objects as well as
|
||||
functions as handlers for all event types.
|
||||
|
||||
### Event Listener
|
||||
|
||||
Event listeners registered for an event `type` may either be JavaScript
|
||||
functions or objects with a `handleEvent` property whose value is a function.
|
||||
|
||||
In either case, the handler function will be invoked with the `event` argument
|
||||
passed to the `eventTarget.dispatchEvent()` function.
|
||||
|
||||
Async functions may be used as event listeners. If an async handler function
|
||||
rejects, the rejection will be captured and be will handled as described in
|
||||
[`EventTarget` Error Handling][].
|
||||
|
||||
An error thrown by one handler function will not prevent the other handlers
|
||||
from being invoked.
|
||||
|
||||
The return value of a handler function will be ignored.
|
||||
|
||||
Handlers are always invoked in the order they were added.
|
||||
|
||||
Handler functions may mutate the `event` object.
|
||||
|
||||
```js
|
||||
function handler1(event) {
|
||||
console.log(event.type); // Prints 'foo'
|
||||
event.a = 1;
|
||||
}
|
||||
|
||||
async function handler2(event) {
|
||||
console.log(event.type); // Prints 'foo'
|
||||
console.log(event.a); // Prints 1
|
||||
}
|
||||
|
||||
const handler3 = {
|
||||
handleEvent(event) {
|
||||
console.log(event.type); // Prints 'foo'
|
||||
}
|
||||
};
|
||||
|
||||
const handler4 = {
|
||||
async handleEvent(event) {
|
||||
console.log(event.type); // Prints 'foo'
|
||||
}
|
||||
};
|
||||
|
||||
const target = getEventTargetSomehow();
|
||||
|
||||
target.addEventListener('foo', handler1);
|
||||
target.addEventListener('foo', handler2);
|
||||
target.addEventListener('foo', handler3);
|
||||
target.addEventListener('foo', handler4, { once: true });
|
||||
```
|
||||
|
||||
### `EventTarget` Error Handling
|
||||
|
||||
When a registered event listener throws (or returns a Promise that rejects),
|
||||
by default the error will be forwarded to the `process.on('error')` event
|
||||
on `process.nextTick()`. Throwing within an event listener will *not* stop
|
||||
the other registered handlers from being invoked.
|
||||
|
||||
The `EventTarget` does not implement any special default handling for
|
||||
`'error'` type events.
|
||||
|
||||
### Class: `Event`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
The `Event` object is an adaptation of the [`Event` Web API][]. Instances
|
||||
are created internally by Node.js.
|
||||
|
||||
#### `event.bubbles`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {boolean} Always returns `false`.
|
||||
|
||||
This is not used in Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.cancelBubble()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Alias for `event.stopPropagation()`. This is not used in Node.js and is
|
||||
provided purely for completeness.
|
||||
|
||||
#### `event.cancelable`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {boolean} True if the event was created with the `cancelable` option.
|
||||
|
||||
#### `event.composed`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {boolean} Always returns `false`.
|
||||
|
||||
This is not used in Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.composedPath()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Returns an array containing the current `EventTarget` as the only entry or
|
||||
empty if the event is not currently being dispatched. This is not used in
|
||||
Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.currentTarget`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {EventTarget} The `EventTarget` currently dispatching the event.
|
||||
|
||||
Alias for `event.target`.
|
||||
|
||||
#### `event.defaultPrevented`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {boolean}
|
||||
|
||||
Will be `true` if `cancelable` is `true` and `event.preventDefault()` has been
|
||||
called.
|
||||
|
||||
#### `event.eventPhase`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {number} Returns `0` while an event is not being dispatched, `2` while
|
||||
it is being dispatched.
|
||||
|
||||
This is not used in Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.isTrusted`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {boolean} Always returns `false`.
|
||||
|
||||
This is not used in Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.preventDefault()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Sets the `defaultPrevented` property to `true` if `cancelable` is `true`.
|
||||
|
||||
#### `event.returnValue`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {boolean} True if the event has not been canceled.
|
||||
|
||||
This is not used in Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.srcElement`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {EventTarget} The `EventTarget` currently dispatching the event.
|
||||
|
||||
Alias for `event.target`.
|
||||
|
||||
#### `event.stopImmediatePropagation()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
Stops the invocation of event listeners after the current one completes.
|
||||
|
||||
#### `event.stopPropagation()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
This is not used in Node.js and is provided purely for completeness.
|
||||
|
||||
#### `event.target`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {EventTarget} The `EventTarget` currently dispatching the event.
|
||||
|
||||
#### `event.timeStamp`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {number}
|
||||
|
||||
The millisecond timestamp when the `Event` object was created.
|
||||
|
||||
#### `event.type`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Type: {string}
|
||||
|
||||
The event type identifier.
|
||||
|
||||
### Class: `EventTarget`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
#### `eventTarget.addEventListener(type, listener[, options])`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
* `options` {Object}
|
||||
* `once` {boolean} When `true`, the listener will be automatically removed
|
||||
when it is first invoked. *Default*: `false`
|
||||
* `passive` {boolean} When `true`, serves as a hint that the listener will
|
||||
not call the `Event` object's `preventDefault()` method. *Default*: `false`
|
||||
* `capture` {boolean} Not directly used by Node.js. Added for API
|
||||
completeness. *Default*: `false`
|
||||
|
||||
Adds a new handler for the `type` event. Any given `listener` will be added
|
||||
only once per `type` and per `capture` option value.
|
||||
|
||||
If the `once` option is `true`, the `listener` will be removed after the
|
||||
next time a `type` event is dispatched.
|
||||
|
||||
The `capture` option is not used by Node.js in any functional way other than
|
||||
tracking registered event listeners per the `EventTarget` specification.
|
||||
Specifically, the `capture` option is used as part of the key when registering
|
||||
a `listener`. Any individual `listener` may be added once with
|
||||
`capture = false`, and once with `capture = true`.
|
||||
|
||||
```js
|
||||
function handler(event) {}
|
||||
|
||||
const target = getEventTargetSomehow();
|
||||
target.addEventListener('foo', handler, { capture: true }); // first
|
||||
target.addEventListener('foo', handler, { capture: false }); // second
|
||||
|
||||
// Removes the second instance of handler
|
||||
target.removeEventListener('foo', handler);
|
||||
|
||||
// Removes the first instance of handler
|
||||
target.removeEventListener('foo', handler, { capture: true });
|
||||
```
|
||||
|
||||
#### `eventTarget.dispatchEvent(event)`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `event` {Object|Event}
|
||||
|
||||
Dispatches the `event` to the list of handlers for `event.type`. The `event`
|
||||
may be an `Event` object or any object with a `type` property whose value is
|
||||
a `string`.
|
||||
|
||||
The registered event listeners will be synchronously invoked in the order they
|
||||
were registered.
|
||||
|
||||
#### `eventTarget.removeEventListener(type, listener)`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
* `options` {Object}
|
||||
* `capture` {boolean}
|
||||
|
||||
Removes the `listener` from the list of handlers for event `type`.
|
||||
|
||||
### Class: `NodeEventTarget extends EventTarget`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
The `NodeEventTarget` is a Node.js-specific extension to `EventTarget`
|
||||
that emulates a subset of the `EventEmitter` API.
|
||||
|
||||
#### `nodeEventTarget.addListener(type, listener[, options])`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
* `options` {Object}
|
||||
* `once` {boolean}
|
||||
|
||||
* Returns: {EventTarget} this
|
||||
|
||||
Node.js-specific extension to the `EventTarget` class that emulates the
|
||||
equivalent `EventEmitter` API. The only difference between `addListener()` and
|
||||
`addEventListener()` is that `addListener()` will return a reference to the
|
||||
`EventTarget`.
|
||||
|
||||
#### `nodeEventTarget.eventNames()`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns: {string[]}
|
||||
|
||||
Node.js-specific extension to the `EventTarget` class that returns an array
|
||||
of event `type` names for which event listeners are currently registered.
|
||||
|
||||
#### `nodeEventTarget.listenerCount(type)`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
|
||||
* Returns: {number}
|
||||
|
||||
Node.js-specific extension to the `EventTarget` class that returns the number
|
||||
of event listeners registered for the `type`.
|
||||
|
||||
#### `nodeEventTarget.off(type, listener)`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
|
||||
* Returns: {EventTarget} this
|
||||
|
||||
Node.js-speciic alias for `eventTarget.removeListener()`.
|
||||
|
||||
#### `nodeEventTarget.on(type, listener[, options])`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
* `options` {Object}
|
||||
* `once` {boolean}
|
||||
|
||||
* Returns: {EventTarget} this
|
||||
|
||||
Node.js-specific alias for `eventTarget.addListener()`.
|
||||
|
||||
#### `nodeEventTarget.once(type, listener[, options])`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
* `options` {Object}
|
||||
|
||||
* Returns: {EventTarget} this
|
||||
|
||||
Node.js-specific extension to the `EventTarget` class that adds a `once`
|
||||
listener for the given event `type`. This is equivalent to calling `on`
|
||||
with the `once` option set to `true`.
|
||||
|
||||
#### `nodeEventTarget.removeAllListeners([type])`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
|
||||
Node.js-specific extension to the `EventTarget` class. If `type` is specified,
|
||||
removes all registered listeners for `type`, otherwise removes all registered
|
||||
listeners.
|
||||
|
||||
#### `nodeEventTarget.removeListener(type, listener)`
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `type` {string}
|
||||
* `listener` {Function|EventListener}
|
||||
|
||||
* Returns: {EventTarget} this
|
||||
|
||||
Node.js-specific extension to the `EventTarget` class that removes the
|
||||
`listener` for the given `type`. The only difference between `removeListener()`
|
||||
and `removeEventListener()` is that `removeListener()` will return a reference
|
||||
to the `EventTarget`.
|
||||
|
||||
[WHATWG-EventTarget]: https://dom.spec.whatwg.org/#interface-eventtarget
|
||||
[`--trace-warnings`]: cli.html#cli_trace_warnings
|
||||
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
|
||||
@@ -942,6 +1398,9 @@ composed of the emitted event arguments.
|
||||
[`emitter.listenerCount()`]: #events_emitter_listenercount_eventname
|
||||
[`emitter.removeListener()`]: #events_emitter_removelistener_eventname_listener
|
||||
[`emitter.setMaxListeners(n)`]: #events_emitter_setmaxlisteners_n
|
||||
[`Event` Web API]: https://dom.spec.whatwg.org/#event
|
||||
[`EventTarget` Error Handling]: #events_eventtarget_error_handling
|
||||
[`EventTarget` Web API]: https://dom.spec.whatwg.org/#eventtarget
|
||||
[`fs.ReadStream`]: fs.html#fs_class_fs_readstream
|
||||
[`net.Server`]: net.html#net_class_net_server
|
||||
[`process.on('warning')`]: process.html#process_event_warning
|
||||
|
||||
@@ -825,6 +825,7 @@ E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) {
|
||||
E('ERR_ENCODING_NOT_SUPPORTED', 'The "%s" encoding is not supported',
|
||||
RangeError);
|
||||
E('ERR_EVAL_ESM_CANNOT_PRINT', '--print cannot be used with ESM input', Error);
|
||||
E('ERR_EVENT_RECURSION', 'The event "%s" is already being dispatched', Error);
|
||||
E('ERR_FALSY_VALUE_REJECTION', function(reason) {
|
||||
this.reason = reason;
|
||||
return 'Promise was rejected with falsy value';
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
ArrayFrom,
|
||||
Error,
|
||||
Map,
|
||||
Object,
|
||||
Set,
|
||||
Symbol,
|
||||
NumberIsNaN,
|
||||
} = primordials;
|
||||
|
||||
const {
|
||||
codes: {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_EVENT_RECURSION,
|
||||
ERR_OUT_OF_RANGE,
|
||||
}
|
||||
} = require('internal/errors');
|
||||
|
||||
const perf_hooks = require('perf_hooks');
|
||||
const { customInspectSymbol } = require('internal/util');
|
||||
const { inspect } = require('util');
|
||||
|
||||
const kEvents = Symbol('kEvents');
|
||||
const kStop = Symbol('kStop');
|
||||
const kTarget = Symbol('kTarget');
|
||||
|
||||
const kNewListener = Symbol('kNewListener');
|
||||
const kRemoveListener = Symbol('kRemoveListener');
|
||||
|
||||
class Event {
|
||||
#type = undefined;
|
||||
#defaultPrevented = false;
|
||||
#cancelable = false;
|
||||
#timestamp = perf_hooks.performance.now();
|
||||
|
||||
// Neither of these are currently used in the Node.js implementation
|
||||
// of EventTarget because there is no concept of bubbling or
|
||||
// composition. We preserve their values in Event but they are
|
||||
// non-ops and do not carry any semantics in Node.js
|
||||
#bubbles = false;
|
||||
#composed = false;
|
||||
|
||||
|
||||
constructor(type, options) {
|
||||
if (options != null && typeof options !== 'object')
|
||||
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
|
||||
const { cancelable, bubbles, composed } = { ...options };
|
||||
this.#cancelable = !!cancelable;
|
||||
this.#bubbles = !!bubbles;
|
||||
this.#composed = !!composed;
|
||||
this.#type = String(type);
|
||||
}
|
||||
|
||||
[customInspectSymbol](depth, options) {
|
||||
const name = this.constructor.name;
|
||||
if (depth < 0)
|
||||
return name;
|
||||
|
||||
const opts = Object.assign({}, options, {
|
||||
dept: options.depth === null ? null : options.depth - 1
|
||||
});
|
||||
|
||||
return `${name} ${inspect({
|
||||
type: this.#type,
|
||||
defaultPrevented: this.#defaultPrevented,
|
||||
cancelable: this.#cancelable,
|
||||
timeStamp: this.#timestamp,
|
||||
}, opts)}`;
|
||||
}
|
||||
|
||||
stopImmediatePropagation() {
|
||||
this[kStop] = true;
|
||||
}
|
||||
|
||||
preventDefault() {
|
||||
this.#defaultPrevented = true;
|
||||
}
|
||||
|
||||
get target() { return this[kTarget]; }
|
||||
get currentTarget() { return this[kTarget]; }
|
||||
get srcElement() { return this[kTarget]; }
|
||||
|
||||
get type() { return this.#type; }
|
||||
|
||||
get cancelable() { return this.#cancelable; }
|
||||
|
||||
get defaultPrevented() { return this.#cancelable && this.#defaultPrevented; }
|
||||
|
||||
get timeStamp() { return this.#timestamp; }
|
||||
|
||||
|
||||
// The following are non-op and unused properties/methods from Web API Event.
|
||||
// These are not supported in Node.js and are provided purely for
|
||||
// API completeness.
|
||||
|
||||
composedPath() { return this[kTarget] ? [this[kTarget]] : []; }
|
||||
get returnValue() { return !this.defaultPrevented; }
|
||||
get bubbles() { return this.#bubbles; }
|
||||
get composed() { return this.#composed; }
|
||||
get isTrusted() { return false; }
|
||||
get eventPhase() {
|
||||
return this[kTarget] ? 2 : 0; // Equivalent to AT_TARGET or NONE
|
||||
}
|
||||
cancelBubble() {
|
||||
// Non-op in Node.js. Alias for stopPropagation
|
||||
}
|
||||
stopPropagation() {
|
||||
// Non-op in Node.js
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() { return 'Event'; }
|
||||
}
|
||||
|
||||
// The listeners for an EventTarget are maintained as a linked list.
|
||||
// Unfortunately, the way EventTarget is defined, listeners are accounted
|
||||
// using the tuple [handler,capture], and even if we don't actually make
|
||||
// use of capture or bubbling, in order to be spec compliant we have to
|
||||
// take on the additional complexity of supporting it. Fortunately, using
|
||||
// the linked list makes dispatching faster, even if adding/removing is
|
||||
// slower.
|
||||
class Listener {
|
||||
next;
|
||||
previous;
|
||||
listener;
|
||||
callback;
|
||||
once;
|
||||
capture;
|
||||
passive;
|
||||
|
||||
constructor(previous, listener, once, capture, passive) {
|
||||
if (previous !== undefined)
|
||||
previous.next = this;
|
||||
this.previous = previous;
|
||||
this.listener = listener;
|
||||
this.once = once;
|
||||
this.capture = capture;
|
||||
this.passive = passive;
|
||||
|
||||
this.callback =
|
||||
typeof listener === 'function' ?
|
||||
listener :
|
||||
listener.handleEvent.bind(listener);
|
||||
}
|
||||
|
||||
same(listener, capture) {
|
||||
return this.listener === listener && this.capture === capture;
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.previous !== undefined)
|
||||
this.previous.next = this.next;
|
||||
if (this.next !== undefined)
|
||||
this.next.previous = this.previous;
|
||||
}
|
||||
}
|
||||
|
||||
class EventTarget {
|
||||
[kEvents] = new Map();
|
||||
#emitting = new Set();
|
||||
|
||||
[kNewListener](size, type, listener, once, capture, passive) {}
|
||||
[kRemoveListener](size, type, listener, capture) {}
|
||||
|
||||
addEventListener(type, listener, options = {}) {
|
||||
validateListener(listener);
|
||||
type = String(type);
|
||||
|
||||
const {
|
||||
once,
|
||||
capture,
|
||||
passive
|
||||
} = validateEventListenerOptions(options);
|
||||
|
||||
let root = this[kEvents].get(type);
|
||||
|
||||
if (root === undefined) {
|
||||
root = { size: 1, next: undefined };
|
||||
// This is the first handler in our linked list.
|
||||
new Listener(root, listener, once, capture, passive);
|
||||
this[kNewListener](root.size, type, listener, once, capture, passive);
|
||||
this[kEvents].set(type, root);
|
||||
return;
|
||||
}
|
||||
|
||||
let handler = root.next;
|
||||
let previous;
|
||||
|
||||
// We have to walk the linked list to see if we have a match
|
||||
while (handler !== undefined && !handler.same(listener, capture)) {
|
||||
previous = handler;
|
||||
handler = handler.next;
|
||||
}
|
||||
|
||||
if (handler !== undefined) { // Duplicate! Ignore
|
||||
return;
|
||||
}
|
||||
|
||||
new Listener(previous, listener, once, capture, passive);
|
||||
root.size++;
|
||||
this[kNewListener](root.size, type, listener, once, capture, passive);
|
||||
}
|
||||
|
||||
removeEventListener(type, listener, options = {}) {
|
||||
validateListener(listener);
|
||||
type = String(type);
|
||||
const { capture } = validateEventListenerOptions(options);
|
||||
const root = this[kEvents].get(type);
|
||||
if (root === undefined || root.next === undefined)
|
||||
return;
|
||||
|
||||
let handler = root.next;
|
||||
while (handler !== undefined) {
|
||||
if (handler.same(listener, capture)) {
|
||||
handler.remove();
|
||||
root.size--;
|
||||
if (root.size === 0)
|
||||
this[kEvents].delete(type);
|
||||
this[kRemoveListener](root.size, type, listener, capture);
|
||||
break;
|
||||
}
|
||||
handler = handler.next;
|
||||
}
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
if (!(event instanceof Event)) {
|
||||
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
|
||||
}
|
||||
|
||||
if (this.#emitting.has(event.type) ||
|
||||
event[kTarget] !== undefined) {
|
||||
throw new ERR_EVENT_RECURSION(event.type);
|
||||
}
|
||||
|
||||
const root = this[kEvents].get(event.type);
|
||||
if (root === undefined || root.next === undefined)
|
||||
return true;
|
||||
|
||||
event[kTarget] = this;
|
||||
this.#emitting.add(event.type);
|
||||
|
||||
let handler = root.next;
|
||||
let next;
|
||||
|
||||
while (handler !== undefined &&
|
||||
(handler.passive || event[kStop] !== true)) {
|
||||
// Cache the next item in case this iteration removes the current one
|
||||
next = handler.next;
|
||||
|
||||
if (handler.once) {
|
||||
handler.remove();
|
||||
root.size--;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = handler.callback.call(this, event);
|
||||
if (result !== undefined && result !== null)
|
||||
addCatch(this, result, event);
|
||||
} catch (err) {
|
||||
emitUnhandledRejectionOrErr(this, err, event);
|
||||
}
|
||||
|
||||
handler = next;
|
||||
}
|
||||
|
||||
this.#emitting.delete(event.type);
|
||||
event[kTarget] = undefined;
|
||||
|
||||
return event.defaultPrevented === true ? false : true;
|
||||
}
|
||||
|
||||
[customInspectSymbol](depth, options) {
|
||||
const name = this.constructor.name;
|
||||
if (depth < 0)
|
||||
return name;
|
||||
|
||||
const opts = Object.assign({}, options, {
|
||||
dept: options.depth === null ? null : options.depth - 1
|
||||
});
|
||||
|
||||
return `${name} ${inspect({}, opts)}`;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(EventTarget.prototype, {
|
||||
addEventListener: { enumerable: true },
|
||||
removeEventListener: { enumerable: true },
|
||||
dispatchEvent: { enumerable: true }
|
||||
});
|
||||
|
||||
class NodeEventTarget extends EventTarget {
|
||||
static defaultMaxListeners = 10;
|
||||
|
||||
#maxListeners = NodeEventTarget.defaultMaxListeners;
|
||||
#maxListenersWarned = false;
|
||||
|
||||
[kNewListener](size, type, listener, once, capture, passive) {
|
||||
if (this.#maxListeners > 0 &&
|
||||
size > this.#maxListeners &&
|
||||
!this.#maxListenersWarned) {
|
||||
this.#maxListenersWarned = true;
|
||||
// No error code for this since it is a Warning
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const w = new Error('Possible EventTarget memory leak detected. ' +
|
||||
`${size} ${type} listeners ` +
|
||||
`added to ${inspect(this, { depth: -1 })}. Use ` +
|
||||
'setMaxListeners() to increase limit');
|
||||
w.name = 'MaxListenersExceededWarning';
|
||||
w.target = this;
|
||||
w.type = type;
|
||||
w.count = size;
|
||||
process.emitWarning(w);
|
||||
}
|
||||
}
|
||||
|
||||
setMaxListeners(n) {
|
||||
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
|
||||
throw new ERR_OUT_OF_RANGE('n', 'a non-negative number', n);
|
||||
}
|
||||
this.#maxListeners = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
getMaxListeners() {
|
||||
return this.#maxListeners;
|
||||
}
|
||||
|
||||
eventNames() {
|
||||
return ArrayFrom(this[kEvents].keys());
|
||||
}
|
||||
|
||||
listenerCount(type) {
|
||||
const root = this[kEvents].get(String(type));
|
||||
return root !== undefined ? root.size : 0;
|
||||
}
|
||||
|
||||
off(type, listener, options) {
|
||||
this.removeEventListener(type, listener, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeListener(type, listener, options) {
|
||||
this.removeEventListener(type, listener, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
on(type, listener) {
|
||||
this.addEventListener(type, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
addListener(type, listener) {
|
||||
this.addEventListener(type, listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
once(type, listener) {
|
||||
this.addEventListener(type, listener, { once: true });
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAllListeners(type) {
|
||||
if (type !== undefined) {
|
||||
this[kEvents].delete(String(type));
|
||||
} else {
|
||||
this[kEvents].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(NodeEventTarget.prototype, {
|
||||
setMaxListeners: { enumerable: true },
|
||||
getMaxListeners: { enumerable: true },
|
||||
eventNames: { enumerable: true },
|
||||
listenerCount: { enumerable: true },
|
||||
off: { enumerable: true },
|
||||
removeListener: { enumerable: true },
|
||||
on: { enumerable: true },
|
||||
addListener: { enumerable: true },
|
||||
once: { enumerable: true },
|
||||
removeAllListeners: { enumerable: true },
|
||||
});
|
||||
|
||||
// EventTarget API
|
||||
|
||||
function validateListener(listener) {
|
||||
if (typeof listener === 'function' ||
|
||||
(listener != null &&
|
||||
typeof listener === 'object' &&
|
||||
typeof listener.handleEvent === 'function')) {
|
||||
return;
|
||||
}
|
||||
throw new ERR_INVALID_ARG_TYPE('listener', 'EventListener', listener);
|
||||
}
|
||||
|
||||
function validateEventListenerOptions(options) {
|
||||
if (options == null || typeof options !== 'object')
|
||||
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
|
||||
const {
|
||||
once = false,
|
||||
capture = false,
|
||||
passive = false,
|
||||
} = options;
|
||||
return {
|
||||
once: !!once,
|
||||
capture: !!capture,
|
||||
passive: !!passive,
|
||||
};
|
||||
}
|
||||
|
||||
function addCatch(that, promise, event) {
|
||||
const then = promise.then;
|
||||
if (typeof then === 'function') {
|
||||
then.call(promise, undefined, function(err) {
|
||||
// The callback is called with nextTick to avoid a follow-up
|
||||
// rejection from this promise.
|
||||
process.nextTick(emitUnhandledRejectionOrErr, that, err, event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function emitUnhandledRejectionOrErr(that, err, event) {
|
||||
process.emit('error', err, event);
|
||||
}
|
||||
|
||||
// EventEmitter-ish API:
|
||||
|
||||
module.exports = {
|
||||
Event,
|
||||
EventTarget,
|
||||
NodeEventTarget,
|
||||
};
|
||||
@@ -130,6 +130,7 @@
|
||||
'lib/internal/encoding.js',
|
||||
'lib/internal/errors.js',
|
||||
'lib/internal/error-serdes.js',
|
||||
'lib/internal/event_target.js',
|
||||
'lib/internal/fixed_queue.js',
|
||||
'lib/internal/freelist.js',
|
||||
'lib/internal/freeze_intrinsics.js',
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
// Flags: --expose-internals --no-warnings
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const {
|
||||
Event,
|
||||
EventTarget,
|
||||
NodeEventTarget,
|
||||
} = require('internal/event_target');
|
||||
|
||||
const {
|
||||
ok,
|
||||
deepStrictEqual,
|
||||
strictEqual,
|
||||
throws,
|
||||
} = require('assert');
|
||||
|
||||
const { once } = require('events');
|
||||
|
||||
// The globals are defined.
|
||||
ok(Event);
|
||||
ok(EventTarget);
|
||||
|
||||
// First, test Event
|
||||
{
|
||||
const ev = new Event('foo');
|
||||
strictEqual(ev.type, 'foo');
|
||||
strictEqual(ev.cancelable, false);
|
||||
strictEqual(ev.defaultPrevented, false);
|
||||
strictEqual(typeof ev.timeStamp, 'number');
|
||||
|
||||
deepStrictEqual(ev.composedPath(), []);
|
||||
strictEqual(ev.returnValue, true);
|
||||
strictEqual(ev.bubbles, false);
|
||||
strictEqual(ev.composed, false);
|
||||
strictEqual(ev.isTrusted, false);
|
||||
strictEqual(ev.eventPhase, 0);
|
||||
|
||||
// Not cancelable
|
||||
ev.preventDefault();
|
||||
strictEqual(ev.defaultPrevented, false);
|
||||
}
|
||||
|
||||
{
|
||||
const ev = new Event('foo', { cancelable: true });
|
||||
strictEqual(ev.type, 'foo');
|
||||
strictEqual(ev.cancelable, true);
|
||||
strictEqual(ev.defaultPrevented, false);
|
||||
|
||||
ev.preventDefault();
|
||||
strictEqual(ev.defaultPrevented, true);
|
||||
}
|
||||
|
||||
{
|
||||
const eventTarget = new EventTarget();
|
||||
|
||||
const ev1 = common.mustCall(function(event) {
|
||||
strictEqual(event.type, 'foo');
|
||||
strictEqual(this, eventTarget);
|
||||
strictEqual(event.eventPhase, 2);
|
||||
}, 2);
|
||||
|
||||
const ev2 = {
|
||||
handleEvent: common.mustCall(function(event) {
|
||||
strictEqual(event.type, 'foo');
|
||||
strictEqual(this, ev2);
|
||||
})
|
||||
};
|
||||
|
||||
eventTarget.addEventListener('foo', ev1);
|
||||
eventTarget.addEventListener('foo', ev2, { once: true });
|
||||
ok(eventTarget.dispatchEvent(new Event('foo')));
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
|
||||
eventTarget.removeEventListener('foo', ev1);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
const eventTarget = new NodeEventTarget();
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
deepStrictEqual(eventTarget.eventNames(), []);
|
||||
|
||||
const ev1 = common.mustCall(function(event) {
|
||||
strictEqual(event.type, 'foo');
|
||||
strictEqual(this, eventTarget);
|
||||
}, 2);
|
||||
|
||||
const ev2 = {
|
||||
handleEvent: common.mustCall(function(event) {
|
||||
strictEqual(event.type, 'foo');
|
||||
strictEqual(this, ev2);
|
||||
})
|
||||
};
|
||||
|
||||
eventTarget.addEventListener('foo', ev1);
|
||||
eventTarget.addEventListener('foo', ev2, { once: true });
|
||||
strictEqual(eventTarget.listenerCount('foo'), 2);
|
||||
ok(eventTarget.dispatchEvent(new Event('foo')));
|
||||
strictEqual(eventTarget.listenerCount('foo'), 1);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
|
||||
eventTarget.removeEventListener('foo', ev1);
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
const eventTarget = new EventTarget();
|
||||
const event = new Event('foo', { cancelable: true });
|
||||
eventTarget.addEventListener('foo', (event) => event.preventDefault());
|
||||
ok(!eventTarget.dispatchEvent(event));
|
||||
}
|
||||
|
||||
{
|
||||
const eventTarget = new NodeEventTarget();
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
deepStrictEqual(eventTarget.eventNames(), []);
|
||||
|
||||
const ev1 = common.mustCall((event) => {
|
||||
strictEqual(event.type, 'foo');
|
||||
}, 2);
|
||||
|
||||
const ev2 = {
|
||||
handleEvent: common.mustCall((event) => {
|
||||
strictEqual(event.type, 'foo');
|
||||
})
|
||||
};
|
||||
|
||||
strictEqual(eventTarget.on('foo', ev1), eventTarget);
|
||||
strictEqual(eventTarget.once('foo', ev2, { once: true }), eventTarget);
|
||||
strictEqual(eventTarget.listenerCount('foo'), 2);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
strictEqual(eventTarget.listenerCount('foo'), 1);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
|
||||
strictEqual(eventTarget.off('foo', ev1), eventTarget);
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
const eventTarget = new NodeEventTarget();
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
deepStrictEqual(eventTarget.eventNames(), []);
|
||||
|
||||
const ev1 = common.mustCall((event) => {
|
||||
strictEqual(event.type, 'foo');
|
||||
}, 2);
|
||||
|
||||
const ev2 = {
|
||||
handleEvent: common.mustCall((event) => {
|
||||
strictEqual(event.type, 'foo');
|
||||
})
|
||||
};
|
||||
|
||||
eventTarget.addListener('foo', ev1);
|
||||
eventTarget.once('foo', ev2, { once: true });
|
||||
strictEqual(eventTarget.listenerCount('foo'), 2);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
strictEqual(eventTarget.listenerCount('foo'), 1);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
|
||||
eventTarget.removeListener('foo', ev1);
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
const eventTarget = new NodeEventTarget();
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
deepStrictEqual(eventTarget.eventNames(), []);
|
||||
|
||||
// Won't actually be called.
|
||||
const ev1 = () => {};
|
||||
|
||||
// Won't actually be called.
|
||||
const ev2 = { handleEvent() {} };
|
||||
|
||||
eventTarget.addListener('foo', ev1);
|
||||
eventTarget.addEventListener('foo', ev1);
|
||||
eventTarget.once('foo', ev2, { once: true });
|
||||
eventTarget.once('foo', ev2, { once: false });
|
||||
eventTarget.on('bar', ev1);
|
||||
strictEqual(eventTarget.listenerCount('foo'), 2);
|
||||
strictEqual(eventTarget.listenerCount('bar'), 1);
|
||||
deepStrictEqual(eventTarget.eventNames(), ['foo', 'bar']);
|
||||
eventTarget.removeAllListeners('foo');
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
strictEqual(eventTarget.listenerCount('bar'), 1);
|
||||
deepStrictEqual(eventTarget.eventNames(), ['bar']);
|
||||
eventTarget.removeAllListeners();
|
||||
strictEqual(eventTarget.listenerCount('foo'), 0);
|
||||
strictEqual(eventTarget.listenerCount('bar'), 0);
|
||||
deepStrictEqual(eventTarget.eventNames(), []);
|
||||
}
|
||||
|
||||
{
|
||||
const uncaughtException = common.mustCall((err, event) => {
|
||||
strictEqual(err.message, 'boom');
|
||||
strictEqual(event.type, 'foo');
|
||||
}, 4);
|
||||
|
||||
// Whether or not the handler function is async or not, errors
|
||||
// are routed to uncaughtException
|
||||
process.on('error', uncaughtException);
|
||||
|
||||
const eventTarget = new EventTarget();
|
||||
|
||||
const ev1 = async () => { throw new Error('boom'); };
|
||||
const ev2 = () => { throw new Error('boom'); };
|
||||
const ev3 = { handleEvent() { throw new Error('boom'); } };
|
||||
const ev4 = { async handleEvent() { throw new Error('boom'); } };
|
||||
|
||||
// Errors in a handler won't stop calling the others.
|
||||
eventTarget.addEventListener('foo', ev1, { once: true });
|
||||
eventTarget.addEventListener('foo', ev2, { once: true });
|
||||
eventTarget.addEventListener('foo', ev3, { once: true });
|
||||
eventTarget.addEventListener('foo', ev4, { once: true });
|
||||
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
const eventTarget = new EventTarget();
|
||||
|
||||
// Once handler only invoked once
|
||||
const ev = common.mustCall((event) => {
|
||||
throws(() => eventTarget.dispatchEvent(new Event('foo')), {
|
||||
code: 'ERR_EVENT_RECURSION'
|
||||
});
|
||||
});
|
||||
|
||||
// Errors in a handler won't stop calling the others.
|
||||
eventTarget.addEventListener('foo', ev);
|
||||
|
||||
eventTarget.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
// Coercion to string works
|
||||
strictEqual((new Event(1)).type, '1');
|
||||
strictEqual((new Event(false)).type, 'false');
|
||||
strictEqual((new Event({})).type, String({}));
|
||||
|
||||
const target = new EventTarget();
|
||||
|
||||
[
|
||||
'foo',
|
||||
{}, // No type event
|
||||
undefined,
|
||||
1,
|
||||
false
|
||||
].forEach((i) => {
|
||||
throws(() => target.dispatchEvent(i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
'foo',
|
||||
1,
|
||||
{}, // No handleEvent function
|
||||
false,
|
||||
undefined
|
||||
].forEach((i) => {
|
||||
throws(() => target.addEventListener('foo', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
'foo',
|
||||
1,
|
||||
{}, // No handleEvent function
|
||||
false,
|
||||
undefined
|
||||
].forEach((i) => {
|
||||
throws(() => target.removeEventListener('foo', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
once(target, 'foo').then(common.mustCall());
|
||||
target.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
const target = new NodeEventTarget();
|
||||
|
||||
process.on('warning', common.mustCall((warning) => {
|
||||
ok(warning instanceof Error);
|
||||
strictEqual(warning.name, 'MaxListenersExceededWarning');
|
||||
strictEqual(warning.target, target);
|
||||
strictEqual(warning.count, 2);
|
||||
strictEqual(warning.type, 'foo');
|
||||
ok(warning.message.includes(
|
||||
'2 foo listeners added to NodeEventTarget'));
|
||||
}));
|
||||
|
||||
strictEqual(target.getMaxListeners(), NodeEventTarget.defaultMaxListeners);
|
||||
target.setMaxListeners(1);
|
||||
target.on('foo', () => {});
|
||||
target.on('foo', () => {});
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
const event = new Event('foo');
|
||||
event.stopImmediatePropagation();
|
||||
target.addEventListener('foo', common.mustNotCall());
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
const event = new Event('foo');
|
||||
target.addEventListener('foo', common.mustCall((event) => {
|
||||
event.stopImmediatePropagation();
|
||||
}));
|
||||
target.addEventListener('foo', common.mustNotCall());
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
const event = new Event('foo');
|
||||
target.addEventListener('foo', common.mustCall((event) => {
|
||||
event.stopImmediatePropagation();
|
||||
}));
|
||||
target.addEventListener('foo', common.mustNotCall());
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
const event = new Event('foo');
|
||||
target.addEventListener('foo', common.mustCall((event) => {
|
||||
strictEqual(event.target, target);
|
||||
strictEqual(event.currentTarget, target);
|
||||
strictEqual(event.srcElement, target);
|
||||
}));
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
{
|
||||
const target1 = new EventTarget();
|
||||
const target2 = new EventTarget();
|
||||
const event = new Event('foo');
|
||||
target1.addEventListener('foo', common.mustCall((event) => {
|
||||
throws(() => target2.dispatchEvent(event), {
|
||||
code: 'ERR_EVENT_RECURSION'
|
||||
});
|
||||
}));
|
||||
target1.dispatchEvent(event);
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
const a = common.mustCall(() => target.removeEventListener('foo', a));
|
||||
const b = common.mustCall(2);
|
||||
|
||||
target.addEventListener('foo', a);
|
||||
target.addEventListener('foo', b);
|
||||
|
||||
target.dispatchEvent(new Event('foo'));
|
||||
target.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
|
||||
{
|
||||
const target = new EventTarget();
|
||||
const a = common.mustCall(3);
|
||||
|
||||
target.addEventListener('foo', a, { capture: true });
|
||||
target.addEventListener('foo', a, { capture: false });
|
||||
|
||||
target.dispatchEvent(new Event('foo'));
|
||||
target.removeEventListener('foo', a, { capture: true });
|
||||
target.dispatchEvent(new Event('foo'));
|
||||
target.removeEventListener('foo', a, { capture: false });
|
||||
target.dispatchEvent(new Event('foo'));
|
||||
}
|
||||
@@ -77,6 +77,9 @@ const customTypesMap = {
|
||||
'import.meta': 'esm.html#esm_import_meta',
|
||||
|
||||
'EventEmitter': 'events.html#events_class_eventemitter',
|
||||
'EventTarget': 'events.html#events_class_eventtarget',
|
||||
'Event': 'events.html#events_class_event',
|
||||
'EventListener': 'events.html#events_event_listener',
|
||||
|
||||
'FileHandle': 'fs.html#fs_class_filehandle',
|
||||
'fs.Dir': 'fs.html#fs_class_fs_dir',
|
||||
@@ -119,6 +122,9 @@ const customTypesMap = {
|
||||
'net.Server': 'net.html#net_class_net_server',
|
||||
'net.Socket': 'net.html#net_class_net_socket',
|
||||
|
||||
'NodeEventTarget':
|
||||
'events.html#events_class_nodeeventtarget_extends_eventtarget',
|
||||
|
||||
'os.constants.dlopen': 'os.html#os_dlopen_constants',
|
||||
|
||||
'Histogram': 'perf_hooks.html#perf_hooks_class_histogram',
|
||||
|
||||
Reference in new issue
Block a user