mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
events: add brand checks for detached accessors
PR-URL: https://github.com/nodejs/node/pull/39773 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
@@ -8,6 +8,7 @@ const {
|
||||
FunctionPrototypeCall,
|
||||
NumberIsInteger,
|
||||
ObjectAssign,
|
||||
ObjectCreate,
|
||||
ObjectDefineProperties,
|
||||
ObjectDefineProperty,
|
||||
ObjectGetOwnPropertyDescriptor,
|
||||
@@ -39,6 +40,7 @@ const { customInspectSymbol } = require('internal/util');
|
||||
const { inspect } = require('util');
|
||||
|
||||
const kIsEventTarget = SymbolFor('nodejs.event_target');
|
||||
const kIsNodeEventTarget = Symbol('kIsNodeEventTarget');
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const {
|
||||
@@ -80,6 +82,10 @@ const isTrusted = ObjectGetOwnPropertyDescriptor({
|
||||
}
|
||||
}, 'isTrusted').get;
|
||||
|
||||
function isEvent(value) {
|
||||
return typeof value?.[kType] === 'string';
|
||||
}
|
||||
|
||||
class Event {
|
||||
constructor(type, options = null) {
|
||||
if (arguments.length === 0)
|
||||
@@ -110,6 +116,8 @@ class Event {
|
||||
}
|
||||
|
||||
[customInspectSymbol](depth, options) {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
const name = this.constructor.name;
|
||||
if (depth < 0)
|
||||
return name;
|
||||
@@ -127,46 +135,111 @@ class Event {
|
||||
}
|
||||
|
||||
stopImmediatePropagation() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
this[kStop] = true;
|
||||
}
|
||||
|
||||
preventDefault() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
this[kDefaultPrevented] = true;
|
||||
}
|
||||
|
||||
get target() { return this[kTarget]; }
|
||||
get currentTarget() { return this[kTarget]; }
|
||||
get srcElement() { return this[kTarget]; }
|
||||
get target() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kTarget];
|
||||
}
|
||||
|
||||
get type() { return this[kType]; }
|
||||
get currentTarget() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kTarget];
|
||||
}
|
||||
|
||||
get cancelable() { return this[kCancelable]; }
|
||||
get srcElement() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kTarget];
|
||||
}
|
||||
|
||||
get type() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kType];
|
||||
}
|
||||
|
||||
get cancelable() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kCancelable];
|
||||
}
|
||||
|
||||
get defaultPrevented() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kCancelable] && this[kDefaultPrevented];
|
||||
}
|
||||
|
||||
get timeStamp() { return this[kTimestamp]; }
|
||||
get timeStamp() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kTimestamp];
|
||||
}
|
||||
|
||||
|
||||
// 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[kIsBeingDispatched] ? [this[kTarget]] : []; }
|
||||
get returnValue() { return !this.defaultPrevented; }
|
||||
get bubbles() { return this[kBubbles]; }
|
||||
get composed() { return this[kComposed]; }
|
||||
composedPath() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kIsBeingDispatched] ? [this[kTarget]] : [];
|
||||
}
|
||||
|
||||
get returnValue() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return !this.defaultPrevented;
|
||||
}
|
||||
|
||||
get bubbles() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kBubbles];
|
||||
}
|
||||
|
||||
get composed() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kComposed];
|
||||
}
|
||||
|
||||
get eventPhase() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kIsBeingDispatched] ? Event.AT_TARGET : Event.NONE;
|
||||
}
|
||||
get cancelBubble() { return this[kPropagationStopped]; }
|
||||
|
||||
get cancelBubble() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
return this[kPropagationStopped];
|
||||
}
|
||||
|
||||
set cancelBubble(value) {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
if (value) {
|
||||
this.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
stopPropagation() {
|
||||
if (!isEvent(this))
|
||||
throw new ERR_INVALID_THIS('Event');
|
||||
this[kPropagationStopped] = true;
|
||||
}
|
||||
|
||||
@@ -176,12 +249,34 @@ class Event {
|
||||
static BUBBLING_PHASE = 3;
|
||||
}
|
||||
|
||||
ObjectDefineProperty(Event.prototype, SymbolToStringTag, {
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: 'Event',
|
||||
});
|
||||
const kEnumerableProperty = ObjectCreate(null);
|
||||
kEnumerableProperty.enumerable = true;
|
||||
|
||||
ObjectDefineProperties(
|
||||
Event.prototype, {
|
||||
[SymbolToStringTag]: {
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: 'Event',
|
||||
},
|
||||
stopImmediatePropagation: kEnumerableProperty,
|
||||
preventDefault: kEnumerableProperty,
|
||||
target: kEnumerableProperty,
|
||||
currentTarget: kEnumerableProperty,
|
||||
srcElement: kEnumerableProperty,
|
||||
type: kEnumerableProperty,
|
||||
cancelable: kEnumerableProperty,
|
||||
defaultPrevented: kEnumerableProperty,
|
||||
timeStamp: kEnumerableProperty,
|
||||
composedPath: kEnumerableProperty,
|
||||
returnValue: kEnumerableProperty,
|
||||
bubbles: kEnumerableProperty,
|
||||
composed: kEnumerableProperty,
|
||||
eventPhase: kEnumerableProperty,
|
||||
cancelBubble: kEnumerableProperty,
|
||||
stopPropagation: kEnumerableProperty,
|
||||
});
|
||||
|
||||
class NodeCustomEvent extends Event {
|
||||
constructor(type, options) {
|
||||
@@ -297,6 +392,8 @@ class EventTarget {
|
||||
[kRemoveListener](size, type, listener, capture) {}
|
||||
|
||||
addEventListener(type, listener, options = {}) {
|
||||
if (!isEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('EventTarget');
|
||||
if (arguments.length < 2)
|
||||
throw new ERR_MISSING_ARGS('type', 'listener');
|
||||
|
||||
@@ -368,6 +465,8 @@ class EventTarget {
|
||||
}
|
||||
|
||||
removeEventListener(type, listener, options = {}) {
|
||||
if (!isEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('EventTarget');
|
||||
if (!shouldAddListener(listener))
|
||||
return;
|
||||
|
||||
@@ -393,12 +492,12 @@ class EventTarget {
|
||||
}
|
||||
|
||||
dispatchEvent(event) {
|
||||
if (!(event instanceof Event))
|
||||
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
|
||||
|
||||
if (!isEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('EventTarget');
|
||||
|
||||
if (!(event instanceof Event))
|
||||
throw new ERR_INVALID_ARG_TYPE('event', 'Event', event);
|
||||
|
||||
if (event[kIsBeingDispatched])
|
||||
throw new ERR_EVENT_RECURSION(event.type);
|
||||
|
||||
@@ -479,6 +578,8 @@ class EventTarget {
|
||||
return new NodeCustomEvent(type, { detail: nodeValue });
|
||||
}
|
||||
[customInspectSymbol](depth, options) {
|
||||
if (!isEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('EventTarget');
|
||||
const name = this.constructor.name;
|
||||
if (depth < 0)
|
||||
return name;
|
||||
@@ -492,15 +593,15 @@ class EventTarget {
|
||||
}
|
||||
|
||||
ObjectDefineProperties(EventTarget.prototype, {
|
||||
addEventListener: { enumerable: true },
|
||||
removeEventListener: { enumerable: true },
|
||||
dispatchEvent: { enumerable: true }
|
||||
});
|
||||
ObjectDefineProperty(EventTarget.prototype, SymbolToStringTag, {
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: 'EventTarget',
|
||||
addEventListener: kEnumerableProperty,
|
||||
removeEventListener: kEnumerableProperty,
|
||||
dispatchEvent: kEnumerableProperty,
|
||||
[SymbolToStringTag]: {
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: 'EventTarget',
|
||||
}
|
||||
});
|
||||
|
||||
function initNodeEventTarget(self) {
|
||||
@@ -508,6 +609,7 @@ function initNodeEventTarget(self) {
|
||||
}
|
||||
|
||||
class NodeEventTarget extends EventTarget {
|
||||
static [kIsNodeEventTarget] = true;
|
||||
static defaultMaxListeners = 10;
|
||||
|
||||
constructor() {
|
||||
@@ -516,42 +618,60 @@ class NodeEventTarget extends EventTarget {
|
||||
}
|
||||
|
||||
setMaxListeners(n) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
EventEmitter.setMaxListeners(n, this);
|
||||
}
|
||||
|
||||
getMaxListeners() {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
return this[kMaxEventTargetListeners];
|
||||
}
|
||||
|
||||
eventNames() {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
return ArrayFrom(this[kEvents].keys());
|
||||
}
|
||||
|
||||
listenerCount(type) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
const root = this[kEvents].get(String(type));
|
||||
return root !== undefined ? root.size : 0;
|
||||
}
|
||||
|
||||
off(type, listener, options) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
this.removeEventListener(type, listener, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
removeListener(type, listener, options) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
this.removeEventListener(type, listener, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
on(type, listener) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
|
||||
return this;
|
||||
}
|
||||
|
||||
addListener(type, listener) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
this.addEventListener(type, listener, { [kIsNodeStyleListener]: true });
|
||||
return this;
|
||||
}
|
||||
emit(type, arg) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
validateString(type, 'type');
|
||||
const hadListeners = this.listenerCount(type) > 0;
|
||||
this[kHybridDispatch](arg, type);
|
||||
@@ -559,12 +679,16 @@ class NodeEventTarget extends EventTarget {
|
||||
}
|
||||
|
||||
once(type, listener) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
this.addEventListener(type, listener,
|
||||
{ once: true, [kIsNodeStyleListener]: true });
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAllListeners(type) {
|
||||
if (!isNodeEventTarget(this))
|
||||
throw new ERR_INVALID_THIS('NodeEventTarget');
|
||||
if (type !== undefined) {
|
||||
this[kEvents].delete(String(type));
|
||||
} else {
|
||||
@@ -576,17 +700,17 @@ class NodeEventTarget extends EventTarget {
|
||||
}
|
||||
|
||||
ObjectDefineProperties(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 },
|
||||
emit: { enumerable: true },
|
||||
removeAllListeners: { enumerable: true },
|
||||
setMaxListeners: kEnumerableProperty,
|
||||
getMaxListeners: kEnumerableProperty,
|
||||
eventNames: kEnumerableProperty,
|
||||
listenerCount: kEnumerableProperty,
|
||||
off: kEnumerableProperty,
|
||||
removeListener: kEnumerableProperty,
|
||||
on: kEnumerableProperty,
|
||||
addListener: kEnumerableProperty,
|
||||
once: kEnumerableProperty,
|
||||
emit: kEnumerableProperty,
|
||||
removeAllListeners: kEnumerableProperty,
|
||||
});
|
||||
|
||||
// EventTarget API
|
||||
@@ -631,6 +755,10 @@ function isEventTarget(obj) {
|
||||
return obj?.constructor?.[kIsEventTarget];
|
||||
}
|
||||
|
||||
function isNodeEventTarget(obj) {
|
||||
return obj?.constructor?.[kIsNodeEventTarget];
|
||||
}
|
||||
|
||||
function addCatch(promise) {
|
||||
const then = promise.then;
|
||||
if (typeof then === 'function') {
|
||||
|
||||
69
test/parallel/test-eventtarget-brandcheck.js
Normal file
69
test/parallel/test-eventtarget-brandcheck.js
Normal file
@@ -0,0 +1,69 @@
|
||||
// Flags: --expose-internals
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
|
||||
const {
|
||||
Event,
|
||||
EventTarget,
|
||||
NodeEventTarget,
|
||||
} = require('internal/event_target');
|
||||
|
||||
[
|
||||
'target',
|
||||
'currentTarget',
|
||||
'srcElement',
|
||||
'type',
|
||||
'cancelable',
|
||||
'defaultPrevented',
|
||||
'timeStamp',
|
||||
'returnValue',
|
||||
'bubbles',
|
||||
'composed',
|
||||
'eventPhase',
|
||||
].forEach((i) => {
|
||||
assert.throws(() => Reflect.get(Event.prototype, i, {}), {
|
||||
code: 'ERR_INVALID_THIS',
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
'stopImmediatePropagation',
|
||||
'preventDefault',
|
||||
'composedPath',
|
||||
'cancelBubble',
|
||||
'stopPropagation',
|
||||
].forEach((i) => {
|
||||
assert.throws(() => Reflect.apply(Event.prototype[i], [], {}), {
|
||||
code: 'ERR_INVALID_THIS',
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
'addEventListener',
|
||||
'removeEventListener',
|
||||
'dispatchEvent',
|
||||
].forEach((i) => {
|
||||
assert.throws(() => Reflect.apply(EventTarget.prototype[i], [], {}), {
|
||||
code: 'ERR_INVALID_THIS',
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
'setMaxListeners',
|
||||
'getMaxListeners',
|
||||
'eventNames',
|
||||
'listenerCount',
|
||||
'off',
|
||||
'removeListener',
|
||||
'on',
|
||||
'addListener',
|
||||
'once',
|
||||
'emit',
|
||||
'removeAllListeners',
|
||||
].forEach((i) => {
|
||||
assert.throws(() => Reflect.apply(NodeEventTarget.prototype[i], [], {}), {
|
||||
code: 'ERR_INVALID_THIS',
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user