From 9ab743aa706be653e3b3c94d07960fe4342f9da5 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Fri, 4 Nov 2022 17:13:56 +0100 Subject: [PATCH] [feature] Add support for objets with a `handleEvent()` method Make `WebSocket.prototype.addEventListener()` support an event listener specified as an object with a `handleEvent()` method. Fixes #2092 --- doc/ws.md | 4 ++-- lib/event-target.js | 28 ++++++++++++++++++++++------ test/websocket.test.js | 23 +++++++++++++---------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/doc/ws.md b/doc/ws.md index e045a0db1..abf0f0735 100644 --- a/doc/ws.md +++ b/doc/ws.md @@ -426,7 +426,7 @@ handshake. This allows you to read headers from the server, for example ### websocket.addEventListener(type, listener[, options]) - `type` {String} A string representing the event type to listen for. -- `listener` {Function} The listener to add. +- `listener` {Function|Object} The listener to add. - `options` {Object} - `once` {Boolean} A `Boolean` indicating that the listener should be invoked at most once after being added. If `true`, the listener would be @@ -555,7 +555,7 @@ The current state of the connection. This is one of the ready state constants. ### websocket.removeEventListener(type, listener) - `type` {String} A string representing the event type to remove. -- `listener` {Function} The listener to remove. +- `listener` {Function|Object} The listener to remove. Removes an event listener emulating the `EventTarget` interface. This method only removes listeners added with diff --git a/lib/event-target.js b/lib/event-target.js index d5abd83a0..6b185270a 100644 --- a/lib/event-target.js +++ b/lib/event-target.js @@ -178,7 +178,7 @@ const EventTarget = { * Register an event listener. * * @param {String} type A string representing the event type to listen for - * @param {Function} listener The listener to add + * @param {(Function|Object)} listener The listener to add * @param {Object} [options] An options object specifies characteristics about * the event listener * @param {Boolean} [options.once=false] A `Boolean` indicating that the @@ -196,7 +196,7 @@ const EventTarget = { }); event[kTarget] = this; - listener.call(this, event); + callListener(listener, this, event); }; } else if (type === 'close') { wrapper = function onClose(code, message) { @@ -207,7 +207,7 @@ const EventTarget = { }); event[kTarget] = this; - listener.call(this, event); + callListener(listener, this, event); }; } else if (type === 'error') { wrapper = function onError(error) { @@ -217,14 +217,14 @@ const EventTarget = { }); event[kTarget] = this; - listener.call(this, event); + callListener(listener, this, event); }; } else if (type === 'open') { wrapper = function onOpen() { const event = new Event('open'); event[kTarget] = this; - listener.call(this, event); + callListener(listener, this, event); }; } else { return; @@ -244,7 +244,7 @@ const EventTarget = { * Remove an event listener. * * @param {String} type A string representing the event type to remove - * @param {Function} handler The listener to remove + * @param {(Function|Object)} handler The listener to remove * @public */ removeEventListener(type, handler) { @@ -264,3 +264,19 @@ module.exports = { EventTarget, MessageEvent }; + +/** + * Call an event listener + * + * @param {(Function|Object)} listener The listener to call + * @param {*} thisArg The value to use as `this`` when calling the listener + * @param {Event} event The event to pass to the listener + * @private + */ +function callListener(listener, thisArg, event) { + if (typeof listener === 'object' && listener.handleEvent) { + listener.handleEvent.call(listener, event); + } else { + listener.call(thisArg, event); + } +} diff --git a/test/websocket.test.js b/test/websocket.test.js index fad80ddb9..bbbc5ae9f 100644 --- a/test/websocket.test.js +++ b/test/websocket.test.js @@ -3264,14 +3264,15 @@ describe('WebSocket', () => { assert.strictEqual(ws.listenerCount('open'), 1); - ws.addEventListener( - 'message', - () => { + const listener = { + handleEvent() { events.push('message'); + assert.strictEqual(this, listener); assert.strictEqual(ws.listenerCount('message'), 0); - }, - { once: true } - ); + } + }; + + ws.addEventListener('message', listener, { once: true }); assert.strictEqual(ws.listenerCount('message'), 1); @@ -3318,17 +3319,19 @@ describe('WebSocket', () => { it('supports the `removeEventListener` method', () => { const ws = new WebSocket('ws://localhost', { agent: new CustomAgent() }); - ws.addEventListener('message', NOOP); + const listener = { handleEvent() {} }; + + ws.addEventListener('message', listener); ws.addEventListener('open', NOOP); - assert.strictEqual(ws.listeners('message')[0][kListener], NOOP); + assert.strictEqual(ws.listeners('message')[0][kListener], listener); assert.strictEqual(ws.listeners('open')[0][kListener], NOOP); ws.removeEventListener('message', () => {}); - assert.strictEqual(ws.listeners('message')[0][kListener], NOOP); + assert.strictEqual(ws.listeners('message')[0][kListener], listener); - ws.removeEventListener('message', NOOP); + ws.removeEventListener('message', listener); ws.removeEventListener('open', NOOP); assert.strictEqual(ws.listenerCount('message'), 0);