Skip to content

Commit 8c61563

Browse files
committed
[fix] Make listeners added via event handler properties independent
Prevent the `onclose`, `onerror`, `onmessage`, and `onopen` getters and setters from returning or removing event listeners added with `WebSocket.prototype.addEventListener()`. Also prevent `WebSocket.prototype.removeEventListener()` from removing event listeners added with the `onclose`, `onerror`, `onmessage`, and `onopen` setters. Refs: #1818
1 parent ea95d9c commit 8c61563

File tree

4 files changed

+89
-37
lines changed

4 files changed

+89
-37
lines changed

lib/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module.exports = {
44
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
55
EMPTY_BUFFER: Buffer.alloc(0),
66
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
7+
kForOnEventAttribute: Symbol('kIsForOnEventAttribute'),
78
kListener: Symbol('kListener'),
89
kStatusCode: Symbol('status-code'),
910
kWebSocket: Symbol('websocket'),

lib/event-target.js

+32-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { kListener } = require('./constants');
3+
const { kForOnEventAttribute, kListener } = require('./constants');
44

55
/**
66
* Class representing an event.
@@ -127,40 +127,41 @@ const EventTarget = {
127127
* the listener would be automatically removed when invoked.
128128
* @public
129129
*/
130-
addEventListener(type, listener, options) {
131-
function onMessage(data, isBinary) {
132-
listener.call(
133-
this,
134-
new MessageEvent(isBinary ? data : data.toString(), this)
135-
);
136-
}
137-
138-
function onClose(code, message) {
139-
listener.call(this, new CloseEvent(code, message.toString(), this));
140-
}
141-
142-
function onError(error) {
143-
listener.call(this, new ErrorEvent(error, this));
144-
}
145-
146-
function onOpen() {
147-
listener.call(this, new OpenEvent(this));
148-
}
149-
150-
const method = options && options.once ? 'once' : 'on';
130+
addEventListener(
131+
type,
132+
listener,
133+
options = { once: false, [kForOnEventAttribute]: false }
134+
) {
135+
let wrapper;
151136

152137
if (type === 'message') {
153-
onMessage[kListener] = listener;
154-
this[method](type, onMessage);
138+
wrapper = function onMessage(data, isBinary) {
139+
const event = new MessageEvent(isBinary ? data : data.toString(), this);
140+
listener.call(this, event);
141+
};
155142
} else if (type === 'close') {
156-
onClose[kListener] = listener;
157-
this[method](type, onClose);
143+
wrapper = function onClose(code, message) {
144+
listener.call(this, new CloseEvent(code, message.toString(), this));
145+
};
158146
} else if (type === 'error') {
159-
onError[kListener] = listener;
160-
this[method](type, onError);
147+
wrapper = function onError(error) {
148+
listener.call(this, new ErrorEvent(error, this));
149+
};
161150
} else if (type === 'open') {
162-
onOpen[kListener] = listener;
163-
this[method](type, onOpen);
151+
wrapper = function onOpen() {
152+
listener.call(this, new OpenEvent(this));
153+
};
154+
} else {
155+
return;
156+
}
157+
158+
wrapper[kForOnEventAttribute] = options[kForOnEventAttribute];
159+
wrapper[kListener] = listener;
160+
161+
if (options.once) {
162+
this.once(type, wrapper);
163+
} else {
164+
this.on(type, wrapper);
164165
}
165166
},
166167

@@ -173,7 +174,7 @@ const EventTarget = {
173174
*/
174175
removeEventListener(type, handler) {
175176
for (const listener of this.listeners(type)) {
176-
if (listener[kListener] === handler) {
177+
if (listener[kListener] === handler && !listener[kForOnEventAttribute]) {
177178
this.removeListener(type, listener);
178179
break;
179180
}

lib/websocket.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const {
1515
BINARY_TYPES,
1616
EMPTY_BUFFER,
1717
GUID,
18+
kForOnEventAttribute,
1819
kListener,
1920
kStatusCode,
2021
kWebSocket,
@@ -524,22 +525,24 @@ Object.defineProperty(WebSocket.prototype, 'CLOSED', {
524525
enumerable: true,
525526
get() {
526527
for (const listener of this.listeners(method)) {
527-
if (listener[kListener]) return listener[kListener];
528+
if (listener[kForOnEventAttribute]) return listener[kListener];
528529
}
529530

530531
return undefined;
531532
},
532533
set(handler) {
533534
for (const listener of this.listeners(method)) {
534-
//
535-
// Remove only the listeners added via `addEventListener`.
536-
//
537-
if (listener[kListener]) this.removeListener(method, listener);
535+
if (listener[kForOnEventAttribute]) {
536+
this.removeListener(method, listener);
537+
break;
538+
}
538539
}
539540

540541
if (typeof handler !== 'function') return;
541542

542-
this.addEventListener(method, handler);
543+
this.addEventListener(method, handler, {
544+
[kForOnEventAttribute]: true
545+
});
543546
}
544547
});
545548
});

test/websocket.test.js

+47
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,11 @@ describe('WebSocket', () => {
21112111
assert.strictEqual(ws.onclose, NOOP);
21122112
assert.strictEqual(ws.onerror, NOOP);
21132113
assert.strictEqual(ws.onopen, NOOP);
2114+
2115+
ws.onmessage = 'foo';
2116+
2117+
assert.strictEqual(ws.onmessage, undefined);
2118+
assert.strictEqual(ws.listenerCount('onmessage'), 0);
21142119
});
21152120

21162121
it('works like the `EventEmitter` interface', (done) => {
@@ -2199,6 +2204,40 @@ describe('WebSocket', () => {
21992204
assert.deepStrictEqual(events, ['open', 'message']);
22002205
});
22012206

2207+
it("doesn't return listeners added with `addEventListener`", () => {
2208+
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
2209+
2210+
ws.addEventListener('open', NOOP);
2211+
2212+
const listeners = ws.listeners('open');
2213+
2214+
assert.strictEqual(listeners.length, 1);
2215+
assert.strictEqual(listeners[0][kListener], NOOP);
2216+
2217+
assert.strictEqual(ws.onopen, undefined);
2218+
});
2219+
2220+
it("doesn't remove listeners added with `addEventListener`", () => {
2221+
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
2222+
2223+
ws.addEventListener('close', NOOP);
2224+
ws.onclose = NOOP;
2225+
2226+
let listeners = ws.listeners('close');
2227+
2228+
assert.strictEqual(listeners.length, 2);
2229+
assert.strictEqual(listeners[0][kListener], NOOP);
2230+
assert.strictEqual(listeners[1][kListener], NOOP);
2231+
2232+
ws.onclose = NOOP;
2233+
2234+
listeners = ws.listeners('close');
2235+
2236+
assert.strictEqual(listeners.length, 2);
2237+
assert.strictEqual(listeners[0][kListener], NOOP);
2238+
assert.strictEqual(listeners[1][kListener], NOOP);
2239+
});
2240+
22022241
it('supports the `removeEventListener` method', () => {
22032242
const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() });
22042243

@@ -2257,6 +2296,14 @@ describe('WebSocket', () => {
22572296
ws.removeEventListener('message', NOOP);
22582297

22592298
assert.deepStrictEqual(ws.listeners('message'), [NOOP]);
2299+
2300+
ws.onclose = NOOP;
2301+
2302+
assert.strictEqual(ws.listeners('close')[0][kListener], NOOP);
2303+
2304+
ws.removeEventListener('close', NOOP);
2305+
2306+
assert.strictEqual(ws.listeners('close')[0][kListener], NOOP);
22602307
});
22612308

22622309
it('wraps text data in a `MessageEvent`', (done) => {

0 commit comments

Comments
 (0)