Skip to content

Commit b6e89ab

Browse files
committed
events: Handle a range of this values for dispatchEvent
On the web, dispatchEvent is finicky about its `this` value. An exception is thrown for `this` values which are not an EventTarget.
1 parent 9918bdf commit b6e89ab

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

lib/internal/event_target.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
Set,
99
Symbol,
1010
NumberIsNaN,
11+
SymbolFor,
1112
SymbolToStringTag,
1213
} = primordials;
1314

@@ -16,13 +17,16 @@ const {
1617
ERR_INVALID_ARG_TYPE,
1718
ERR_EVENT_RECURSION,
1819
ERR_OUT_OF_RANGE,
19-
ERR_MISSING_ARGS
20+
ERR_MISSING_ARGS,
21+
ERR_INVALID_THIS,
2022
}
2123
} = require('internal/errors');
2224

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

28+
const kIsEventTarget = SymbolFor('nodejs.event_target');
29+
2630
const kEvents = Symbol('kEvents');
2731
const kStop = Symbol('kStop');
2832
const kTarget = Symbol('kTarget');
@@ -185,6 +189,10 @@ class Listener {
185189
}
186190

187191
class EventTarget {
192+
// Used in checking whether an object is an EventTarget. This is a well-known
193+
// symbol as EventTarget may be used cross-realm. See discussion in #33661.
194+
static [kIsEventTarget] = true;
195+
188196
[kEvents] = new Map();
189197
#emitting = new Set();
190198

@@ -257,6 +265,10 @@ class EventTarget {
257265
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
258266
}
259267

268+
if (!isEventTarget(this)) {
269+
throw new ERR_INVALID_THIS('EventTarget');
270+
}
271+
260272
if (this.#emitting.has(event.type) ||
261273
event[kTarget] !== null) {
262274
throw new ERR_EVENT_RECURSION(event.type);
@@ -447,6 +459,15 @@ function validateEventListenerOptions(options) {
447459
};
448460
}
449461

462+
// Test whether the argument is an event object. This is far from a fool-proof
463+
// test, for example this input will result in a false positive:
464+
// > isEventTarget({ constructor: EventTarget })
465+
// It stands in its current implementation as a compromise. For the relevant
466+
// discussion, see #33661.
467+
function isEventTarget(obj) {
468+
return obj && obj.constructor && obj.constructor[kIsEventTarget];
469+
}
470+
450471
function addCatch(that, promise, event) {
451472
const then = promise.then;
452473
if (typeof then === 'function') {

test/parallel/test-eventtarget.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,28 @@ ok(EventTarget);
439439
const event = new Event('');
440440
strictEqual(event.toString(), '[object Event]');
441441
}
442+
443+
{
444+
// `this` value of dispatchEvent
445+
const target = new EventTarget();
446+
const target2 = new EventTarget();
447+
const event = new Event('foo');
448+
449+
ok(target.dispatchEvent.call(target2, event));
450+
451+
[
452+
'foo',
453+
{},
454+
[],
455+
1,
456+
null,
457+
undefined,
458+
false,
459+
Symbol(),
460+
/a/
461+
].forEach((i) => {
462+
throws(() => target.dispatchEvent.call(i, event), {
463+
code: 'ERR_INVALID_THIS'
464+
});
465+
});
466+
}

0 commit comments

Comments
 (0)