Skip to content

Commit cfbe906

Browse files
jasnelltargos
authored andcommitted
events: add brand checks for detached accessors
PR-URL: #39773 Reviewed-By: Antoine du Hamel <[email protected]>
1 parent 3b1ce93 commit cfbe906

File tree

2 files changed

+237
-40
lines changed

2 files changed

+237
-40
lines changed

lib/internal/event_target.js

+168-40
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
FunctionPrototypeCall,
99
NumberIsInteger,
1010
ObjectAssign,
11+
ObjectCreate,
1112
ObjectDefineProperties,
1213
ObjectDefineProperty,
1314
ObjectGetOwnPropertyDescriptor,
@@ -39,6 +40,7 @@ const { customInspectSymbol } = require('internal/util');
3940
const { inspect } = require('util');
4041

4142
const kIsEventTarget = SymbolFor('nodejs.event_target');
43+
const kIsNodeEventTarget = Symbol('kIsNodeEventTarget');
4244

4345
const EventEmitter = require('events');
4446
const {
@@ -80,6 +82,10 @@ const isTrusted = ObjectGetOwnPropertyDescriptor({
8082
}
8183
}, 'isTrusted').get;
8284

85+
function isEvent(value) {
86+
return typeof value?.[kType] === 'string';
87+
}
88+
8389
class Event {
8490
constructor(type, options = null) {
8591
if (arguments.length === 0)
@@ -110,6 +116,8 @@ class Event {
110116
}
111117

112118
[customInspectSymbol](depth, options) {
119+
if (!isEvent(this))
120+
throw new ERR_INVALID_THIS('Event');
113121
const name = this.constructor.name;
114122
if (depth < 0)
115123
return name;
@@ -127,46 +135,111 @@ class Event {
127135
}
128136

129137
stopImmediatePropagation() {
138+
if (!isEvent(this))
139+
throw new ERR_INVALID_THIS('Event');
130140
this[kStop] = true;
131141
}
132142

133143
preventDefault() {
144+
if (!isEvent(this))
145+
throw new ERR_INVALID_THIS('Event');
134146
this[kDefaultPrevented] = true;
135147
}
136148

137-
get target() { return this[kTarget]; }
138-
get currentTarget() { return this[kTarget]; }
139-
get srcElement() { return this[kTarget]; }
149+
get target() {
150+
if (!isEvent(this))
151+
throw new ERR_INVALID_THIS('Event');
152+
return this[kTarget];
153+
}
140154

141-
get type() { return this[kType]; }
155+
get currentTarget() {
156+
if (!isEvent(this))
157+
throw new ERR_INVALID_THIS('Event');
158+
return this[kTarget];
159+
}
142160

143-
get cancelable() { return this[kCancelable]; }
161+
get srcElement() {
162+
if (!isEvent(this))
163+
throw new ERR_INVALID_THIS('Event');
164+
return this[kTarget];
165+
}
166+
167+
get type() {
168+
if (!isEvent(this))
169+
throw new ERR_INVALID_THIS('Event');
170+
return this[kType];
171+
}
172+
173+
get cancelable() {
174+
if (!isEvent(this))
175+
throw new ERR_INVALID_THIS('Event');
176+
return this[kCancelable];
177+
}
144178

145179
get defaultPrevented() {
180+
if (!isEvent(this))
181+
throw new ERR_INVALID_THIS('Event');
146182
return this[kCancelable] && this[kDefaultPrevented];
147183
}
148184

149-
get timeStamp() { return this[kTimestamp]; }
185+
get timeStamp() {
186+
if (!isEvent(this))
187+
throw new ERR_INVALID_THIS('Event');
188+
return this[kTimestamp];
189+
}
150190

151191

152192
// The following are non-op and unused properties/methods from Web API Event.
153193
// These are not supported in Node.js and are provided purely for
154194
// API completeness.
155195

156-
composedPath() { return this[kIsBeingDispatched] ? [this[kTarget]] : []; }
157-
get returnValue() { return !this.defaultPrevented; }
158-
get bubbles() { return this[kBubbles]; }
159-
get composed() { return this[kComposed]; }
196+
composedPath() {
197+
if (!isEvent(this))
198+
throw new ERR_INVALID_THIS('Event');
199+
return this[kIsBeingDispatched] ? [this[kTarget]] : [];
200+
}
201+
202+
get returnValue() {
203+
if (!isEvent(this))
204+
throw new ERR_INVALID_THIS('Event');
205+
return !this.defaultPrevented;
206+
}
207+
208+
get bubbles() {
209+
if (!isEvent(this))
210+
throw new ERR_INVALID_THIS('Event');
211+
return this[kBubbles];
212+
}
213+
214+
get composed() {
215+
if (!isEvent(this))
216+
throw new ERR_INVALID_THIS('Event');
217+
return this[kComposed];
218+
}
219+
160220
get eventPhase() {
221+
if (!isEvent(this))
222+
throw new ERR_INVALID_THIS('Event');
161223
return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE;
162224
}
163-
get cancelBubble() { return this[kPropagationStopped]; }
225+
226+
get cancelBubble() {
227+
if (!isEvent(this))
228+
throw new ERR_INVALID_THIS('Event');
229+
return this[kPropagationStopped];
230+
}
231+
164232
set cancelBubble(value) {
233+
if (!isEvent(this))
234+
throw new ERR_INVALID_THIS('Event');
165235
if (value) {
166236
this.stopPropagation();
167237
}
168238
}
239+
169240
stopPropagation() {
241+
if (!isEvent(this))
242+
throw new ERR_INVALID_THIS('Event');
170243
this[kPropagationStopped] = true;
171244
}
172245

@@ -176,12 +249,34 @@ class Event {
176249
static BUBBLING_PHASE = 3;
177250
}
178251

179-
ObjectDefineProperty(Event.prototype, SymbolToStringTag, {
180-
writable: false,
181-
enumerable: false,
182-
configurable: true,
183-
value: 'Event',
184-
});
252+
const kEnumerableProperty = ObjectCreate(null);
253+
kEnumerableProperty.enumerable = true;
254+
255+
ObjectDefineProperties(
256+
Event.prototype, {
257+
[SymbolToStringTag]: {
258+
writable: false,
259+
enumerable: false,
260+
configurable: true,
261+
value: 'Event',
262+
},
263+
stopImmediatePropagation: kEnumerableProperty,
264+
preventDefault: kEnumerableProperty,
265+
target: kEnumerableProperty,
266+
currentTarget: kEnumerableProperty,
267+
srcElement: kEnumerableProperty,
268+
type: kEnumerableProperty,
269+
cancelable: kEnumerableProperty,
270+
defaultPrevented: kEnumerableProperty,
271+
timeStamp: kEnumerableProperty,
272+
composedPath: kEnumerableProperty,
273+
returnValue: kEnumerableProperty,
274+
bubbles: kEnumerableProperty,
275+
composed: kEnumerableProperty,
276+
eventPhase: kEnumerableProperty,
277+
cancelBubble: kEnumerableProperty,
278+
stopPropagation: kEnumerableProperty,
279+
});
185280

186281
class NodeCustomEvent extends Event {
187282
constructor(type, options) {
@@ -297,6 +392,8 @@ class EventTarget {
297392
[kRemoveListener](size, type, listener, capture) {}
298393

299394
addEventListener(type, listener, options = {}) {
395+
if (!isEventTarget(this))
396+
throw new ERR_INVALID_THIS('EventTarget');
300397
if (arguments.length < 2)
301398
throw new ERR_MISSING_ARGS('type', 'listener');
302399

@@ -368,6 +465,8 @@ class EventTarget {
368465
}
369466

370467
removeEventListener(type, listener, options = {}) {
468+
if (!isEventTarget(this))
469+
throw new ERR_INVALID_THIS('EventTarget');
371470
if (!shouldAddListener(listener))
372471
return;
373472

@@ -393,12 +492,12 @@ class EventTarget {
393492
}
394493

395494
dispatchEvent(event) {
396-
if (!(event instanceof Event))
397-
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
398-
399495
if (!isEventTarget(this))
400496
throw new ERR_INVALID_THIS('EventTarget');
401497

498+
if (!(event instanceof Event))
499+
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
500+
402501
if (event[kIsBeingDispatched])
403502
throw new ERR_EVENT_RECURSION(event.type);
404503

@@ -479,6 +578,8 @@ class EventTarget {
479578
return new NodeCustomEvent(type, { detail: nodeValue });
480579
}
481580
[customInspectSymbol](depth, options) {
581+
if (!isEventTarget(this))
582+
throw new ERR_INVALID_THIS('EventTarget');
482583
const name = this.constructor.name;
483584
if (depth < 0)
484585
return name;
@@ -492,22 +593,23 @@ class EventTarget {
492593
}
493594

494595
ObjectDefineProperties(EventTarget.prototype, {
495-
addEventListener: { enumerable: true },
496-
removeEventListener: { enumerable: true },
497-
dispatchEvent: { enumerable: true }
498-
});
499-
ObjectDefineProperty(EventTarget.prototype, SymbolToStringTag, {
500-
writable: false,
501-
enumerable: false,
502-
configurable: true,
503-
value: 'EventTarget',
596+
addEventListener: kEnumerableProperty,
597+
removeEventListener: kEnumerableProperty,
598+
dispatchEvent: kEnumerableProperty,
599+
[SymbolToStringTag]: {
600+
writable: false,
601+
enumerable: false,
602+
configurable: true,
603+
value: 'EventTarget',
604+
}
504605
});
505606

506607
function initNodeEventTarget(self) {
507608
initEventTarget(self);
508609
}
509610

510611
class NodeEventTarget extends EventTarget {
612+
static [kIsNodeEventTarget] = true;
511613
static defaultMaxListeners = 10;
512614

513615
constructor() {
@@ -516,55 +618,77 @@ class NodeEventTarget extends EventTarget {
516618
}
517619

518620
setMaxListeners(n) {
621+
if (!isNodeEventTarget(this))
622+
throw new ERR_INVALID_THIS('NodeEventTarget');
519623
EventEmitter.setMaxListeners(n, this);
520624
}
521625

522626
getMaxListeners() {
627+
if (!isNodeEventTarget(this))
628+
throw new ERR_INVALID_THIS('NodeEventTarget');
523629
return this[kMaxEventTargetListeners];
524630
}
525631

526632
eventNames() {
633+
if (!isNodeEventTarget(this))
634+
throw new ERR_INVALID_THIS('NodeEventTarget');
527635
return ArrayFrom(this[kEvents].keys());
528636
}
529637

530638
listenerCount(type) {
639+
if (!isNodeEventTarget(this))
640+
throw new ERR_INVALID_THIS('NodeEventTarget');
531641
const root = this[kEvents].get(String(type));
532642
return root !== undefined ? root.size : 0;
533643
}
534644

535645
off(type, listener, options) {
646+
if (!isNodeEventTarget(this))
647+
throw new ERR_INVALID_THIS('NodeEventTarget');
536648
this.removeEventListener(type, listener, options);
537649
return this;
538650
}
539651

540652
removeListener(type, listener, options) {
653+
if (!isNodeEventTarget(this))
654+
throw new ERR_INVALID_THIS('NodeEventTarget');
541655
this.removeEventListener(type, listener, options);
542656
return this;
543657
}
544658

545659
on(type, listener) {
660+
if (!isNodeEventTarget(this))
661+
throw new ERR_INVALID_THIS('NodeEventTarget');
546662
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
547663
return this;
548664
}
549665

550666
addListener(type, listener) {
667+
if (!isNodeEventTarget(this))
668+
throw new ERR_INVALID_THIS('NodeEventTarget');
551669
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
552670
return this;
553671
}
554672
emit(type, arg) {
673+
if (!isNodeEventTarget(this))
674+
throw new ERR_INVALID_THIS('NodeEventTarget');
555675
validateString(type, 'type');
556676
const hadListeners = this.listenerCount(type) > 0;
557677
this[kHybridDispatch](arg, type);
558678
return hadListeners;
559679
}
560680

561681
once(type, listener) {
682+
if (!isNodeEventTarget(this))
683+
throw new ERR_INVALID_THIS('NodeEventTarget');
562684
this.addEventListener(type, listener,
563685
{ once: true, [kIsNodeStyleListener]: true });
564686
return this;
565687
}
566688

567689
removeAllListeners(type) {
690+
if (!isNodeEventTarget(this))
691+
throw new ERR_INVALID_THIS('NodeEventTarget');
568692
if (type !== undefined) {
569693
this[kEvents].delete(String(type));
570694
} else {
@@ -576,17 +700,17 @@ class NodeEventTarget extends EventTarget {
576700
}
577701

578702
ObjectDefineProperties(NodeEventTarget.prototype, {
579-
setMaxListeners: { enumerable: true },
580-
getMaxListeners: { enumerable: true },
581-
eventNames: { enumerable: true },
582-
listenerCount: { enumerable: true },
583-
off: { enumerable: true },
584-
removeListener: { enumerable: true },
585-
on: { enumerable: true },
586-
addListener: { enumerable: true },
587-
once: { enumerable: true },
588-
emit: { enumerable: true },
589-
removeAllListeners: { enumerable: true },
703+
setMaxListeners: kEnumerableProperty,
704+
getMaxListeners: kEnumerableProperty,
705+
eventNames: kEnumerableProperty,
706+
listenerCount: kEnumerableProperty,
707+
off: kEnumerableProperty,
708+
removeListener: kEnumerableProperty,
709+
on: kEnumerableProperty,
710+
addListener: kEnumerableProperty,
711+
once: kEnumerableProperty,
712+
emit: kEnumerableProperty,
713+
removeAllListeners: kEnumerableProperty,
590714
});
591715

592716
// EventTarget API
@@ -631,6 +755,10 @@ function isEventTarget(obj) {
631755
return obj?.constructor?.[kIsEventTarget];
632756
}
633757

758+
function isNodeEventTarget(obj) {
759+
return obj?.constructor?.[kIsNodeEventTarget];
760+
}
761+
634762
function addCatch(promise) {
635763
const then = promise.then;
636764
if (typeof then === 'function') {

0 commit comments

Comments
 (0)