Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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
144 changes: 74 additions & 70 deletions lib/events.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ const { addAbortListener } = require('internal/events/abort_listener');

const kCapture = Symbol('kCapture');
const kErrorMonitor = Symbol('events.errorMonitor');
const kShapeMode = Symbol('shapeMode');
const kEmitting = Symbol('events.emitting');
const kMaxEventTargetListeners = Symbol('events.maxEventTargetListeners');
const kMaxEventTargetListenersWarned =
Expand DownExpand Up@@ -335,9 +334,6 @@ EventEmitter.init = function(opts) {
this._events === ObjectGetPrototypeOf(this)._events) {
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
} else {
this[kShapeMode] = true;
}

this._maxListeners ||= undefined;
Expand DownExpand Up@@ -446,6 +442,39 @@ function enhanceStackTrace(err, own) {
return err.stack + sep + ArrayPrototypeJoin(ownStack, '\n');
}

function getUnhandledErrorException(ee, args) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, ee, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand this to explain why we're ignoring the error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't new but it's always bugged me that's it's not explained :-)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the lines above we are trying to expand the stacktrace. If we can't for whatever reasons, we don't hide the original error.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell do you want me to update the comment?

}

return er;
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
return err;
}

/**
* Synchronously calls each of the listeners registered
* for the event.
Expand All@@ -466,38 +495,10 @@ EventEmitter.prototype.emit = function emit(type, ...args) {

// If there is no 'error' event listener then throw.
if (doError) {
let er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
try {
const capture = {};
ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
__proto__: null,
value: FunctionPrototypeBind(enhanceStackTrace, this, er, capture),
configurable: true,
});
} catch {
// Continue regardless of error.
}

// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

let stringifiedEr;
try {
stringifiedEr = inspect(er);
} catch {
stringifiedEr = er;
}

// At least give some kind of context to the user
const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
err.context = er;
throw err; // Unhandled 'error' event
const er = getUnhandledErrorException(this, args);
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}

const handler = events[type];
Expand DownExpand Up@@ -584,20 +585,23 @@ function _addListener(target, type, listener, prepend) {

// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}
if (m > 0 && existing.length > m && !existing.warned)
warnMaxListenersExceeded(target, type, existing, m);
}

return target;
}

function warnMaxListenersExceeded(target, type, existing, m) {
existing.warned = true;
// No error code for this since it is a Warning
const w = genericNodeError(
`Possible EventEmitter memory leak detected. ${existing.length} ${String(type)} listeners ` +
`added to ${inspect(target, { depth: -1 })}. MaxListeners is ${m}. Use emitter.setMaxListeners() to increase limit`,
{ name: 'MaxListenersExceededWarning', emitter: target, type: type, count: existing.length });
process.emitWarning(w);
}

/**
* Adds a listener to the event emitter.
* @param {string | symbol} type
Expand All@@ -622,22 +626,16 @@ EventEmitter.prototype.prependListener =
return _addListener(this, type, listener, true);
};

function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return ReflectApply(this.listener, this.target, arguments);
}
}

function _onceWrap(target, type, listener) {
const state = { fired: false, wrapFn: undefined, target, type, listener };
const wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
let fired = false;
function wrapper(...args) {
if (fired) return;
fired = true;
target.removeListener(type, wrapper);
return ReflectApply(listener, target, args);
}
wrapper.listener = listener;
return wrapper;
}

/**
Expand DownExpand Up@@ -689,13 +687,12 @@ EventEmitter.prototype.removeListener =
if (list === listener || list.listener === listener) {
this._eventsCount -= 1;

if (this[kShapeMode]) {
events[type] = undefined;
} else if (this._eventsCount === 0) {
this._events = { __proto__: null };
} else {
delete events[type];
}
// Leave the key in place with an `undefined` value: repeatedly
// adding and removing a listener for the same event this way keeps
// the `events` object in the same shape and avoids both a `delete`
// (which would put the object into dictionary mode) and allocating
// a fresh object when the last listener is removed.
events[type] = undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can cause memory leaks when using unstructured event names, I.e random ids etc...


if (events.removeListener !== undefined)
this.emit('removeListener', type, list.listener || listener);
Expand DownExpand Up@@ -755,7 +752,6 @@ EventEmitter.prototype.removeAllListeners =
else
delete events[type];
}
this[kShapeMode] = false;
return this;
}

Expand All@@ -768,7 +764,6 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners('removeListener');
this._events = { __proto__: null };
this._eventsCount = 0;
this[kShapeMode] = false;
return this;
}

Expand DownExpand Up@@ -868,7 +863,16 @@ EventEmitter.prototype.listenerCount = function listenerCount(type, listener) {
* @returns {(string | symbol)[]}
*/
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
if (this._eventsCount === 0)
return [];
const events = this._events;
const names = [];
for (const key of ReflectOwnKeys(events)) {
// Removed listeners leave the key in place with an `undefined` value.
if (events[key] !== undefined)
ArrayPrototypePush(names, key);
}
return names;
};

function arrayClone(arr) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error: foo:bar
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_sameline.js:6:34)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
node:events:<line>
throw er; // Unhandled 'error' event
^
throw er; // Unhandled 'error' event
^

Error
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_subclass.js:7:25)
Expand Down
Loading