diff --git a/.eslintrc.js b/.eslintrc.js index 8464945125b678..99f7196ffc8158 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -287,6 +287,11 @@ module.exports = { BigInt: 'readable', BigInt64Array: 'readable', BigUint64Array: 'readable', + Event: 'readable', + EventTarget: 'readable', + MessageChannel: 'readable', + MessageEvent: 'readable', + MessagePort: 'readable', TextEncoder: 'readable', TextDecoder: 'readable', queueMicrotask: 'readable', diff --git a/doc/api/events.md b/doc/api/events.md index 7ace75fa65ddbe..527354d408ca6a 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -1067,9 +1067,15 @@ const ac = new AbortController(); process.nextTick(() => ac.abort()); ``` + ## `EventTarget` and `Event` API > Stability: 1 - Experimental @@ -1080,7 +1086,7 @@ Neither the `EventTarget` nor `Event` classes are available for end user code to create. ```js -const target = getEventTargetSomehow(); +const target = new EventTarget(); target.addEventListener('foo', (event) => { console.log('foo event happened!'); @@ -1166,7 +1172,7 @@ const handler4 = { } }; -const target = getEventTargetSomehow(); +const target = new EventTarget(); target.addEventListener('foo', handler1); target.addEventListener('foo', handler2); @@ -1187,6 +1193,10 @@ The `EventTarget` does not implement any special default handling for ### Class: `Event` The `Event` object is an adaptation of the [`Event` Web API][]. Instances @@ -1339,6 +1349,11 @@ The event type identifier. ### Class: `EventTarget` #### `eventTarget.addEventListener(type, listener[, options])` @@ -1372,7 +1387,7 @@ a `listener`. Any individual `listener` may be added once with ```js function handler(event) {} -const target = getEventTargetSomehow(); +const target = new EventTarget(); target.addEventListener('foo', handler, { capture: true }); // first target.addEventListener('foo', handler, { capture: false }); // second diff --git a/doc/api/globals.md b/doc/api/globals.md index 8b279a21715e6b..4191c1e017afc7 100644 --- a/doc/api/globals.md +++ b/doc/api/globals.md @@ -169,6 +169,30 @@ added: v0.1.100 Used to print to stdout and stderr. See the [`console`][] section. +## `Event` + + + + +> Stability: 1 - Experimental + +A browser-compatible implementation of the `Event` class. See +[`EventTarget` and `Event` API][] for more details. + +## `EventTarget` + + + + +> Stability: 1 - Experimental + +A browser-compatible implementation of the `EventTarget` class. See +[`EventTarget` and `Event` API][] for more details. + ## `exports` This variable may appear to be global but is not. See [`exports`][]. @@ -187,6 +211,33 @@ within the browser `var something` will define a new global variable. In Node.js this is different. The top-level scope is not the global scope; `var something` inside a Node.js module will be local to that module. +## `MessageChannel` + + + + +The `MessageChannel` class. See [`MessageChannel`][] for more details. + +## `MessageEvent` + + + + +The `MessageEvent` class. See [`MessageEvent`][] for more details. + +## `MessagePort` + + + + +The `MessagePort` class. See [`MessagePort`][] for more details. + ## `module` This variable may appear to be global but is not. See [`module`][]. @@ -322,6 +373,10 @@ The object that acts as the namespace for all W3C [Mozilla Developer Network][webassembly-mdn] for usage and compatibility. [`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController +[`EventTarget` and `Event` API]: events.md#event-target-and-event-api +[`MessageChannel`]: worker_threads.md#worker_threads_class_messagechannel +[`MessageEvent`]: https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/MessageEvent +[`MessagePort`]: worker_threads.md#worker_threads_class_messageport [`TextDecoder`]: util.md#util_class_util_textdecoder [`TextEncoder`]: util.md#util_class_util_textencoder [`URLSearchParams`]: url.md#url_class_urlsearchparams diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 551367583e55a8..bb4689d0d342e8 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -140,6 +140,21 @@ if (!config.noBrowserGlobals) { exposeInterface(global, 'AbortController', AbortController); exposeInterface(global, 'AbortSignal', AbortSignal); + const { + EventTarget, + Event, + } = require('internal/event_target'); + exposeInterface(global, 'EventTarget', EventTarget); + exposeInterface(global, 'Event', Event); + const { + MessageChannel, + MessagePort, + MessageEvent, + } = require('internal/worker/io'); + exposeInterface(global, 'MessageChannel', MessageChannel); + exposeInterface(global, 'MessagePort', MessagePort); + exposeInterface(global, 'MessageEvent', MessageEvent); + // https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope const timers = require('timers'); defineOperation(global, 'clearInterval', timers.clearInterval); 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_external_reference.h b/src/node_external_reference.h index f332103c3f5a27..0544979dd9a6f1 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -64,7 +64,8 @@ class ExternalReferenceRegistry { V(string_decoder) \ V(trace_events) \ V(timers) \ - V(types) + V(types) \ + V(worker) #if NODE_HAVE_I18N_SUPPORT #define EXTERNAL_REFERENCE_BINDING_LIST_I18N(V) V(icu) 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/src/node_worker.cc b/src/node_worker.cc index b3dd29bf9fb17b..8750b3fef224da 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -2,6 +2,7 @@ #include "debug_utils-inl.h" #include "memory_tracker-inl.h" #include "node_errors.h" +#include "node_external_reference.h" #include "node_buffer.h" #include "node_options-inl.h" #include "node_perf.h" @@ -851,9 +852,20 @@ void InitWorker(Local target, NODE_DEFINE_CONSTANT(target, kTotalResourceLimitCount); } -} // anonymous namespace +void RegisterExternalReferences(ExternalReferenceRegistry* registry) { + registry->Register(GetEnvMessagePort); + registry->Register(Worker::New); + registry->Register(Worker::StartThread); + registry->Register(Worker::StopThread); + registry->Register(Worker::Ref); + registry->Register(Worker::Unref); + registry->Register(Worker::GetResourceLimits); + registry->Register(Worker::TakeHeapSnapshot); +} +} // anonymous namespace } // namespace worker } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(worker, node::worker::InitWorker) +NODE_MODULE_EXTERNAL_REFERENCE(worker, node::worker::RegisterExternalReferences) diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index ee9a62fbfa6455..23e2f534a73183 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -31,6 +31,7 @@ const expectedModules = new Set([ 'Internal Binding types', 'Internal Binding url', 'Internal Binding util', + 'Internal Binding worker', 'NativeModule buffer', 'NativeModule events', 'NativeModule fs', @@ -76,6 +77,17 @@ const expectedModules = new Set([ 'NativeModule internal/process/warning', 'NativeModule internal/querystring', 'NativeModule internal/source_map/source_map_cache', + 'NativeModule internal/streams/buffer_list', + 'NativeModule internal/streams/destroy', + 'NativeModule internal/streams/duplex', + 'NativeModule internal/streams/end-of-stream', + 'NativeModule internal/streams/legacy', + 'NativeModule internal/streams/passthrough', + 'NativeModule internal/streams/pipeline', + 'NativeModule internal/streams/readable', + 'NativeModule internal/streams/state', + 'NativeModule internal/streams/transform', + 'NativeModule internal/streams/writable', 'NativeModule internal/timers', 'NativeModule internal/url', 'NativeModule internal/util', @@ -84,8 +96,10 @@ const expectedModules = new Set([ 'NativeModule internal/util/types', 'NativeModule internal/validators', 'NativeModule internal/vm/module', + 'NativeModule internal/worker/io', 'NativeModule internal/worker/js_transferable', 'NativeModule path', + 'NativeModule stream', 'NativeModule timers', 'NativeModule url', 'NativeModule util', diff --git a/test/parallel/test-event-on-async-iterator.js b/test/parallel/test-event-on-async-iterator.js index d9817ff15e0701..276a6f62d37e33 100644 --- a/test/parallel/test-event-on-async-iterator.js +++ b/test/parallel/test-event-on-async-iterator.js @@ -5,9 +5,7 @@ const common = require('../common'); const assert = require('assert'); const { on, EventEmitter } = require('events'); const { - EventTarget, - NodeEventTarget, - Event + NodeEventTarget } = require('internal/event_target'); async function basic() { diff --git a/test/parallel/test-events-once.js b/test/parallel/test-events-once.js index be3ed794e19eb9..ea1963f0e58c1b 100644 --- a/test/parallel/test-events-once.js +++ b/test/parallel/test-events-once.js @@ -1,5 +1,5 @@ 'use strict'; -// Flags: --expose-internals --no-warnings +// Flags: --no-warnings const common = require('../common'); const { once, EventEmitter } = require('events'); @@ -9,7 +9,6 @@ const { fail, rejects, } = require('assert'); -const { EventTarget, Event } = require('internal/event_target'); async function onceAnEvent() { const ee = new EventEmitter(); diff --git a/test/parallel/test-eventtarget-whatwg-passive.js b/test/parallel/test-eventtarget-whatwg-passive.js index 5e33ec5c6c2e47..5399d19da45943 100644 --- a/test/parallel/test-eventtarget-whatwg-passive.js +++ b/test/parallel/test-eventtarget-whatwg-passive.js @@ -1,13 +1,7 @@ -// Flags: --expose-internals 'use strict'; const common = require('../common'); -const { - Event, - EventTarget, -} = require('internal/event_target'); - const { fail, ok, diff --git a/test/parallel/test-worker-message-event.js b/test/parallel/test-worker-message-event.js new file mode 100644 index 00000000000000..bbc2e3b612686e --- /dev/null +++ b/test/parallel/test-worker-message-event.js @@ -0,0 +1,94 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +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/, + }); +} + +{ + assert(new MessageEvent('message') instanceof Event); +}