From 705d888387f7a58c10711acbe5a4661c496969ad Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 4 Oct 2020 13:44:50 +0200 Subject: [PATCH] worker: make MessageEvent class more Web-compatible PR-URL: https://github.com/nodejs/node/pull/35496 Fixes: https://github.com/nodejs/node/issues/35495 Reviewed-By: Rich Trott Reviewed-By: James M Snell Reviewed-By: Joyee Cheung Reviewed-By: Myles Borins Reviewed-By: Shelley Vohr Reviewed-By: Michael Dawson Reviewed-By: Khaidi Chu --- lib/internal/worker/io.js | 57 +++++++++++++- src/node_messaging.cc | 8 ++ src/node_messaging.h | 1 + test/parallel/test-worker-message-event.js | 92 ++++++++++++++++++++++ 4 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 test/parallel/test-worker-message-event.js diff --git a/lib/internal/worker/io.js b/lib/internal/worker/io.js index 5b5118a1d70b21..e3ca6fe0cc10b5 100644 --- a/lib/internal/worker/io.js +++ b/lib/internal/worker/io.js @@ -4,6 +4,7 @@ const { ObjectAssign, ObjectCreate, ObjectDefineProperty, + ObjectDefineProperties, ObjectGetOwnPropertyDescriptors, ObjectGetPrototypeOf, ObjectSetPrototypeOf, @@ -21,7 +22,8 @@ const { drainMessagePort, moveMessagePortToContext, receiveMessageOnPort: receiveMessageOnPort_, - stopMessagePort + stopMessagePort, + checkMessagePort } = internalBinding('messaging'); const { getEnvMessagePort @@ -38,12 +40,20 @@ const { kRemoveListener, } = require('internal/event_target'); const { inspect } = require('internal/util/inspect'); +const { + ERR_INVALID_ARG_TYPE +} = require('internal/errors').codes; +const kData = Symbol('kData'); const kIncrementsPortRef = Symbol('kIncrementsPortRef'); +const kLastEventId = Symbol('kLastEventId'); const kName = Symbol('kName'); +const kOrigin = Symbol('kOrigin'); const kPort = Symbol('kPort'); +const kPorts = Symbol('kPorts'); const kWaitingStreams = Symbol('kWaitingStreams'); const kWritableCallbacks = Symbol('kWritableCallbacks'); +const kSource = Symbol('kSource'); const kStartedReading = Symbol('kStartedReading'); const kStdioWantsMoreDataCallback = Symbol('kStdioWantsMoreDataCallback'); @@ -72,19 +82,57 @@ ObjectSetPrototypeOf(MessagePort.prototype, NodeEventTarget.prototype); MessagePort.prototype.ref = MessagePortPrototype.ref; MessagePort.prototype.unref = MessagePortPrototype.unref; +function validateMessagePort(port, name) { + if (!checkMessagePort(port)) + throw new ERR_INVALID_ARG_TYPE(name, 'MessagePort', port); +} + class MessageEvent extends Event { - constructor(data, target, type) { + constructor(type, { + data = null, + origin = '', + lastEventId = '', + source = null, + ports = [], + } = {}) { super(type); - this.data = data; + this[kData] = data; + this[kOrigin] = `${origin}`; + this[kLastEventId] = `${lastEventId}`; + this[kSource] = source; + this[kPorts] = [...ports]; + + if (this[kSource] !== null) + validateMessagePort(this[kSource], 'init.source'); + for (let i = 0; i < this[kPorts].length; i++) + validateMessagePort(this[kPorts][i], `init.ports[${i}]`); } } +ObjectDefineProperties(MessageEvent.prototype, { + data: { + get() { return this[kData]; }, enumerable: true, configurable: true + }, + origin: { + get() { return this[kOrigin]; }, enumerable: true, configurable: true + }, + lastEventId: { + get() { return this[kLastEventId]; }, enumerable: true, configurable: true + }, + source: { + get() { return this[kSource]; }, enumerable: true, configurable: true + }, + ports: { + get() { return this[kPorts]; }, enumerable: true, configurable: true + }, +}); + ObjectDefineProperty( MessagePort.prototype, kCreateEvent, { value: function(data, type) { - return new MessageEvent(data, this, type); + return new MessageEvent(type, { data }); }, configurable: false, writable: false, @@ -283,6 +331,7 @@ module.exports = { moveMessagePortToContext, MessagePort, MessageChannel, + MessageEvent, receiveMessageOnPort, setupPortReferencing, ReadableWorkerStdio, diff --git a/src/node_messaging.cc b/src/node_messaging.cc index a072176523e1e9..db3c24c3f853e6 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -1004,6 +1004,12 @@ void MessagePort::Stop(const FunctionCallbackInfo& args) { port->Stop(); } +void MessagePort::CheckType(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set( + GetMessagePortConstructorTemplate(env)->HasInstance(args[0])); +} + void MessagePort::Drain(const FunctionCallbackInfo& args) { MessagePort* port; ASSIGN_OR_RETURN_UNWRAP(&port, args[0].As()); @@ -1339,6 +1345,7 @@ static void InitMessaging(Local target, // These are not methods on the MessagePort prototype, because // the browser equivalents do not provide them. env->SetMethod(target, "stopMessagePort", MessagePort::Stop); + env->SetMethod(target, "checkMessagePort", MessagePort::CheckType); env->SetMethod(target, "drainMessagePort", MessagePort::Drain); env->SetMethod(target, "receiveMessageOnPort", MessagePort::ReceiveMessage); env->SetMethod(target, "moveMessagePortToContext", @@ -1363,6 +1370,7 @@ static void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(MessagePort::PostMessage); registry->Register(MessagePort::Start); registry->Register(MessagePort::Stop); + registry->Register(MessagePort::CheckType); registry->Register(MessagePort::Drain); registry->Register(MessagePort::ReceiveMessage); registry->Register(MessagePort::MoveToContext); diff --git a/src/node_messaging.h b/src/node_messaging.h index 378468b6f44465..7ef02226480cb8 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -195,6 +195,7 @@ class MessagePort : public HandleWrap { static void PostMessage(const v8::FunctionCallbackInfo& args); static void Start(const v8::FunctionCallbackInfo& args); static void Stop(const v8::FunctionCallbackInfo& args); + static void CheckType(const v8::FunctionCallbackInfo& args); static void Drain(const v8::FunctionCallbackInfo& args); static void ReceiveMessage(const v8::FunctionCallbackInfo& args); diff --git a/test/parallel/test-worker-message-event.js b/test/parallel/test-worker-message-event.js new file mode 100644 index 00000000000000..6bb90a993ea0b4 --- /dev/null +++ b/test/parallel/test-worker-message-event.js @@ -0,0 +1,92 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const { MessageEvent, MessageChannel } = require('internal/worker/io'); + +const dummyPort = new MessageChannel().port1; + +{ + for (const [ args, expected ] of [ + [ + ['message'], + { + type: 'message', data: null, origin: '', + lastEventId: '', source: null, ports: [] + } + ], + [ + ['message', { data: undefined, origin: 'foo' }], + { + type: 'message', data: null, origin: 'foo', + lastEventId: '', source: null, ports: [] + } + ], + [ + ['message', { data: 2, origin: 1, lastEventId: 0 }], + { + type: 'message', data: 2, origin: '1', + lastEventId: '0', source: null, ports: [] + } + ], + [ + ['message', { lastEventId: 'foo' }], + { + type: 'message', data: null, origin: '', + lastEventId: 'foo', source: null, ports: [] + } + ], + [ + ['messageerror', { lastEventId: 'foo', source: dummyPort }], + { + type: 'messageerror', data: null, origin: '', + lastEventId: 'foo', source: dummyPort, ports: [] + } + ], + [ + ['message', { ports: [dummyPort], source: null }], + { + type: 'message', data: null, origin: '', + lastEventId: '', source: null, ports: [dummyPort] + } + ], + ]) { + const ev = new MessageEvent(...args); + const { type, data, origin, lastEventId, source, ports } = ev; + assert.deepStrictEqual(expected, { + type, data, origin, lastEventId, source, ports + }); + } +} + +{ + assert.throws(() => { + new MessageEvent('message', { source: 1 }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "init\.source" property must be an instance of MessagePort/, + }); + assert.throws(() => { + new MessageEvent('message', { source: {} }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "init\.source" property must be an instance of MessagePort/, + }); + assert.throws(() => { + new MessageEvent('message', { ports: 0 }); + }, { + message: /ports is not iterable/, + }); + assert.throws(() => { + new MessageEvent('message', { ports: [ null ] }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "init\.ports\[0\]" property must be an instance of MessagePort/, + }); + assert.throws(() => { + new MessageEvent('message', { ports: [ {} ] }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "init\.ports\[0\]" property must be an instance of MessagePort/, + }); +}