From 5810885879ec094185851c7c5a2eb6b17a406077 Mon Sep 17 00:00:00 2001 From: Zirak Date: Sat, 30 May 2020 15:48:46 +0000 Subject: [PATCH] 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. PR-URL: https://github.com/nodejs/node/pull/34015 Reviewed-By: Denys Otrishko Reviewed-By: Benjamin Gruenbaum --- lib/internal/event_target.js | 23 ++++++++++++++++++++++- test/parallel/test-eventtarget.js | 25 +++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/internal/event_target.js b/lib/internal/event_target.js index 21d3774a586a7f..fd26a37df3fe32 100644 --- a/lib/internal/event_target.js +++ b/lib/internal/event_target.js @@ -9,6 +9,7 @@ const { Object, Set, Symbol, + SymbolFor, SymbolToStringTag, } = primordials; @@ -16,7 +17,8 @@ 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'); @@ -24,6 +26,8 @@ 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'); @@ -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(); @@ -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); @@ -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') { diff --git a/test/parallel/test-eventtarget.js b/test/parallel/test-eventtarget.js index 3e652e1e3396b4..2ff77d4028b8d9 100644 --- a/test/parallel/test-eventtarget.js +++ b/test/parallel/test-eventtarget.js @@ -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' + }); + }); +}