Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

workers, event_target: add brand checks for detached accessors #39773

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 168 additions & 40 deletions lib/internal/event_target.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
FunctionPrototypeCall,
NumberIsInteger,
ObjectAssign,
ObjectCreate,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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');

Expand Down Expand Up @@ -368,6 +465,8 @@ class EventTarget {
}

removeEventListener(type, listener, options = {}) {
if (!isEventTarget(this))
throw new ERR_INVALID_THIS('EventTarget');
if (!shouldAddListener(listener))
return;

Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -492,22 +593,23 @@ 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) {
initEventTarget(self);
}

class NodeEventTarget extends EventTarget {
static [kIsNodeEventTarget] = true;
static defaultMaxListeners = 10;

constructor() {
Expand All @@ -516,55 +618,77 @@ 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);
return hadListeners;
}

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 {
Expand All @@ -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
Expand Down Expand Up @@ -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') {
Expand Down
Loading