Skip to content

Commit

Permalink
events: Handle a range of this values for dispatchEvent
Browse files Browse the repository at this point in the history
On the web, dispatchEvent is finicky about its `this` value. An exception is
thrown for `this` values which are not an EventTarget.

PR-URL: #34015
Reviewed-By: Denys Otrishko <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
  • Loading branch information
Zirak authored and codebytere committed Jun 27, 2020
1 parent aa1cb3f commit cd3a142
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 1 deletion.
23 changes: 22 additions & 1 deletion lib/internal/event_target.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,25 @@ const {
Object,
Set,
Symbol,
SymbolFor,
SymbolToStringTag,
} = primordials;

const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_EVENT_RECURSION,
ERR_MISSING_ARGS
ERR_MISSING_ARGS,
ERR_INVALID_THIS,
}
} = require('internal/errors');
const { validateInteger, validateObject } = require('internal/validators');

const { customInspectSymbol } = require('internal/util');
const { inspect } = require('util');

const kIsEventTarget = SymbolFor('nodejs.event_target');

const kEvents = Symbol('kEvents');
const kStop = Symbol('kStop');
const kTarget = Symbol('kTarget');
Expand Down Expand Up @@ -185,6 +189,10 @@ class Listener {
}

class EventTarget {
// Used in checking whether an object is an EventTarget. This is a well-known
// symbol as EventTarget may be used cross-realm. See discussion in #33661.
static [kIsEventTarget] = true;

[kEvents] = new Map();
#emitting = new Set();

Expand Down Expand Up @@ -257,6 +265,10 @@ class EventTarget {
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
}

if (!isEventTarget(this)) {
throw new ERR_INVALID_THIS('EventTarget');
}

if (this.#emitting.has(event.type) ||
event[kTarget] !== null) {
throw new ERR_EVENT_RECURSION(event.type);
Expand Down Expand Up @@ -438,6 +450,15 @@ function validateEventListenerOptions(options) {
};
}

// Test whether the argument is an event object. This is far from a fool-proof
// test, for example this input will result in a false positive:
// > isEventTarget({ constructor: EventTarget })
// It stands in its current implementation as a compromise. For the relevant
// discussion, see #33661.
function isEventTarget(obj) {
return obj && obj.constructor && obj.constructor[kIsEventTarget];
}

function addCatch(that, promise, event) {
const then = promise.then;
if (typeof then === 'function') {
Expand Down
25 changes: 25 additions & 0 deletions test/parallel/test-eventtarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,28 @@ ok(EventTarget);
const event = new Event('');
strictEqual(event.toString(), '[object Event]');
}

{
// `this` value of dispatchEvent
const target = new EventTarget();
const target2 = new EventTarget();
const event = new Event('foo');

ok(target.dispatchEvent.call(target2, event));

[
'foo',
{},
[],
1,
null,
undefined,
false,
Symbol(),
/a/
].forEach((i) => {
throws(() => target.dispatchEvent.call(i, event), {
code: 'ERR_INVALID_THIS'
});
});
}

0 comments on commit cd3a142

Please sign in to comment.