From 0d35eaa034d3433b1274c8d869e6ab2fda9d5d84 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sat, 6 Jun 2020 16:11:31 +0200 Subject: [PATCH] worker: allow passing JS wrapper objects via postMessage Enable JS wrapper objects to be used as transferable or cloneable objects in `postMessage()` calls, by having them extend a C++-backed class. This requires a few internal changes: - This commit adds the possibility for transferred objects to read/write JS values at the end of the serialization/deserialization phases. - This commit adds the possibility for transferred objects to list sub-transferables, e.g. typically the public JS wrapper class would list its C++ handle in there. - This commit adds usage of `BaseObject` in a few more places, because now during deserialization weakly held objects can also be involved, in addition to `MessagePort`s. PR-URL: https://github.com/nodejs/node/pull/33772 Backport-PR-URL: https://github.com/nodejs/node/pull/33965 Reviewed-By: Benjamin Gruenbaum --- lib/internal/bootstrap/node.js | 1 + lib/internal/worker/js_transferable.js | 31 ++ node.gyp | 1 + src/base_object.h | 9 + src/env.h | 6 + src/node_errors.h | 3 +- src/node_messaging.cc | 309 ++++++++++++++++-- src/node_messaging.h | 52 +++ test/parallel/test-bootstrap-modules.js | 2 + .../test-worker-workerdata-messageport.js | 3 +- 10 files changed, 384 insertions(+), 33 deletions(-) create mode 100644 lib/internal/worker/js_transferable.js diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index c872941b974216..2be5da2a993a52 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -59,6 +59,7 @@ process._exiting = false; // process.config is serialized config.gypi process.config = JSONParse(internalBinding('native_module').config); +require('internal/worker/js_transferable').setup(); // Bootstrappers for all threads, including worker threads and main thread const perThreadSetup = require('internal/process/per_thread'); diff --git a/lib/internal/worker/js_transferable.js b/lib/internal/worker/js_transferable.js new file mode 100644 index 00000000000000..41f1e6ff72ae06 --- /dev/null +++ b/lib/internal/worker/js_transferable.js @@ -0,0 +1,31 @@ +'use strict'; +const { + messaging_deserialize_symbol, + messaging_transfer_symbol, + messaging_clone_symbol, + messaging_transfer_list_symbol +} = internalBinding('symbols'); +const { + JSTransferable, + setDeserializerCreateObjectFunction +} = internalBinding('messaging'); + +function setup() { + // Register the handler that will be used when deserializing JS-based objects + // from .postMessage() calls. The format of `deserializeInfo` is generally + // 'module:Constructor', e.g. 'internal/fs/promises:FileHandle'. + setDeserializerCreateObjectFunction((deserializeInfo) => { + const [ module, ctor ] = deserializeInfo.split(':'); + const Ctor = require(module)[ctor]; + return new Ctor(); + }); +} + +module.exports = { + setup, + JSTransferable, + kClone: messaging_clone_symbol, + kDeserialize: messaging_deserialize_symbol, + kTransfer: messaging_transfer_symbol, + kTransferList: messaging_transfer_list_symbol +}; diff --git a/node.gyp b/node.gyp index 1a540953500b39..cd8bbc3630ff93 100644 --- a/node.gyp +++ b/node.gyp @@ -216,6 +216,7 @@ 'lib/internal/vm/module.js', 'lib/internal/worker.js', 'lib/internal/worker/io.js', + 'lib/internal/worker/js_transferable.js', 'lib/internal/watchdog.js', 'lib/internal/streams/lazy_transform.js', 'lib/internal/streams/async_iterator.js', diff --git a/src/base_object.h b/src/base_object.h index 5a18639c93282e..3bae620d713c55 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -123,12 +123,17 @@ class BaseObject : public MemoryRetainer { // make sure that they are not accidentally destroyed on the sending side. // TransferForMessaging() will be called to get a representation of the // object that is used for subsequent deserialization. + // The NestedTransferables() method can be used to transfer other objects + // along with this one, if a situation requires it. // - kCloneable: // This object can be cloned without being modified. // CloneForMessaging() will be called to get a representation of the // object that is used for subsequent deserialization, unless the // object is listed in transferList, in which case TransferForMessaging() // is attempted first. + // After a successful clone, FinalizeTransferRead() is called on the receiving + // end, and can read deserialize JS data possibly serialized by a previous + // FinalizeTransferWrite() call. enum class TransferMode { kUntransferable, kTransferable, @@ -137,6 +142,10 @@ class BaseObject : public MemoryRetainer { virtual TransferMode GetTransferMode() const; virtual std::unique_ptr TransferForMessaging(); virtual std::unique_ptr CloneForMessaging() const; + virtual v8::Maybe>> + NestedTransferables() const; + virtual v8::Maybe FinalizeTransferRead( + v8::Local context, v8::ValueDeserializer* deserializer); virtual inline void OnGCCollect(); diff --git a/src/env.h b/src/env.h index 9b9af74a559c90..b85fed613c8369 100644 --- a/src/env.h +++ b/src/env.h @@ -167,6 +167,10 @@ constexpr size_t kFsStatsBufferLength = #define PER_ISOLATE_SYMBOL_PROPERTIES(V) \ V(handle_onclose_symbol, "handle_onclose") \ V(no_message_symbol, "no_message_symbol") \ + V(messaging_deserialize_symbol, "messaging_deserialize_symbol") \ + V(messaging_transfer_symbol, "messaging_transfer_symbol") \ + V(messaging_clone_symbol, "messaging_clone_symbol") \ + V(messaging_transfer_list_symbol, "messaging_transfer_list_symbol") \ V(oninit_symbol, "oninit") \ V(owner_symbol, "owner_symbol") \ V(onpskexchange_symbol, "onpskexchange") \ @@ -209,6 +213,7 @@ constexpr size_t kFsStatsBufferLength = V(crypto_rsa_pss_string, "rsa-pss") \ V(cwd_string, "cwd") \ V(data_string, "data") \ + V(deserialize_info_string, "deserializeInfo") \ V(dest_string, "dest") \ V(destroyed_string, "destroyed") \ V(detached_string, "detached") \ @@ -465,6 +470,7 @@ constexpr size_t kFsStatsBufferLength = V(internal_binding_loader, v8::Function) \ V(immediate_callback_function, v8::Function) \ V(inspector_console_extension_installer, v8::Function) \ + V(messaging_deserialize_create_object, v8::Function) \ V(message_port, v8::Object) \ V(native_module_require, v8::Function) \ V(performance_entry_callback, v8::Function) \ diff --git a/src/node_errors.h b/src/node_errors.h index e23cd164ed719c..c9899e3b74b3dd 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -97,7 +97,8 @@ void OnFatalError(const char* location, const char* message); V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \ V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \ V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, \ - "MessagePort was found in message but not listed in transferList") \ + "Object that needs transfer was found in message but not listed " \ + "in transferList") \ V(ERR_MISSING_PLATFORM_FOR_WORKER, \ "The V8 platform used by this instance of Node does not support " \ "creating Workers") \ diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 7bc406baf0bada..053d376005747e 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -10,6 +10,7 @@ #include "util-inl.h" using node::contextify::ContextifyContext; +using node::errors::TryCatchScope; using v8::Array; using v8::ArrayBuffer; using v8::ArrayBufferCreationMode; @@ -37,6 +38,8 @@ using v8::WasmModuleObject; namespace node { +using BaseObjectList = std::vector>; + BaseObject::TransferMode BaseObject::GetTransferMode() const { return BaseObject::TransferMode::kUntransferable; } @@ -49,8 +52,22 @@ std::unique_ptr BaseObject::CloneForMessaging() const { return {}; } +Maybe BaseObject::NestedTransferables() const { + return Just(BaseObjectList {}); +} + +Maybe BaseObject::FinalizeTransferRead( + Local context, ValueDeserializer* deserializer) { + return Just(true); +} + namespace worker { +Maybe TransferData::FinalizeTransferWrite( + Local context, ValueSerializer* serializer) { + return Just(true); +} + Message::Message(MallocedBuffer&& buffer) : main_message_buf_(std::move(buffer)) {} @@ -115,21 +132,22 @@ MaybeLocal Message::Deserialize(Environment* env, // Create all necessary objects for transferables, e.g. MessagePort handles. std::vector> host_objects(transferables_.size()); + auto cleanup = OnScopeLeave([&]() { + for (BaseObjectPtr object : host_objects) { + if (!object) continue; + + // If the function did not finish successfully, host_objects will contain + // a list of objects that will never be passed to JS. Therefore, we + // destroy them here. + object->Detach(); + } + }); + for (uint32_t i = 0; i < transferables_.size(); ++i) { TransferData* data = transferables_[i].get(); host_objects[i] = data->Deserialize( env, context, std::move(transferables_[i])); - if (!host_objects[i]) { - for (BaseObjectPtr object : host_objects) { - if (!object) continue; - - // Since creating one of the objects failed, we don't want to have the - // other objects lying around in memory. We act as if the object has - // been garbage-collected. - object->Detach(); - } - return MaybeLocal(); - } + if (!host_objects[i]) return {}; } transferables_.clear(); @@ -180,9 +198,18 @@ MaybeLocal Message::Deserialize(Environment* env, array_buffer_contents_.clear(); if (deserializer.ReadHeader(context).IsNothing()) - return MaybeLocal(); - return handle_scope.Escape( - deserializer.ReadValue(context).FromMaybe(Local())); + return {}; + Local return_value; + if (!deserializer.ReadValue(context).ToLocal(&return_value)) + return {}; + + for (BaseObjectPtr base_object : host_objects) { + if (base_object->FinalizeTransferRead(context, &deserializer).IsNothing()) + return {}; + } + + host_objects.clear(); + return handle_scope.Escape(return_value); } void Message::AddSharedArrayBuffer( @@ -258,7 +285,8 @@ class SerializerDelegate : public ValueSerializer::Delegate { Maybe WriteHostObject(Isolate* isolate, Local object) override { if (env_->base_object_ctor_template()->HasInstance(object)) { - return WriteHostObject(Unwrap(object)); + return WriteHostObject( + BaseObjectPtr { Unwrap(object) }); } ThrowDataCloneError(env_->clone_unsupported_type_str()); @@ -294,31 +322,51 @@ class SerializerDelegate : public ValueSerializer::Delegate { return Just(msg_->AddWASMModule(module->GetTransferrableModule())); } - void Finish() { - // Only close the MessagePort handles and actually transfer them - // once we know that serialization succeeded. + Maybe Finish(Local context) { for (uint32_t i = 0; i < host_objects_.size(); i++) { - BaseObject* host_object = host_objects_[i]; + BaseObjectPtr host_object = std::move(host_objects_[i]); std::unique_ptr data; if (i < first_cloned_object_index_) data = host_object->TransferForMessaging(); if (!data) data = host_object->CloneForMessaging(); - CHECK(data); + if (!data) return Nothing(); + if (data->FinalizeTransferWrite(context, serializer).IsNothing()) + return Nothing(); msg_->AddTransferable(std::move(data)); } + return Just(true); } - inline void AddHostObject(BaseObject* host_object) { + inline void AddHostObject(BaseObjectPtr host_object) { // Make sure we have not started serializing the value itself yet. CHECK_EQ(first_cloned_object_index_, SIZE_MAX); - host_objects_.push_back(host_object); + host_objects_.emplace_back(std::move(host_object)); + } + + // Some objects in the transfer list may register sub-objects that can be + // transferred. This could e.g. be a public JS wrapper object, such as a + // FileHandle, that is registering its C++ handle for transfer. + inline Maybe AddNestedHostObjects() { + for (size_t i = 0; i < host_objects_.size(); i++) { + std::vector> nested_transferables; + if (!host_objects_[i]->NestedTransferables().To(&nested_transferables)) + return Nothing(); + for (auto nested_transferable : nested_transferables) { + if (std::find(host_objects_.begin(), + host_objects_.end(), + nested_transferable) == host_objects_.end()) { + AddHostObject(nested_transferable); + } + } + } + return Just(true); } ValueSerializer* serializer = nullptr; private: - Maybe WriteHostObject(BaseObject* host_object) { + Maybe WriteHostObject(BaseObjectPtr host_object) { for (uint32_t i = 0; i < host_objects_.size(); i++) { if (host_objects_[i] == host_object) { serializer->WriteUint32(i); @@ -350,7 +398,7 @@ class SerializerDelegate : public ValueSerializer::Delegate { Local context_; Message* msg_; std::vector> seen_shared_array_buffers_; - std::vector host_objects_; + std::vector> host_objects_; size_t first_cloned_object_index_ = SIZE_MAX; friend class worker::Message; @@ -419,10 +467,11 @@ Maybe Message::Serialize(Environment* env, "Transfer list contains source port")); return Nothing(); } - BaseObject* host_object = Unwrap(entry.As()); + BaseObjectPtr host_object { + Unwrap(entry.As()) }; if (env->message_port_constructor_template()->HasInstance(entry) && - (host_object == nullptr || - static_cast(host_object)->IsDetached())) { + (!host_object || + static_cast(host_object.get())->IsDetached())) { ThrowDataCloneException( context, FIXED_ONE_BYTE_STRING( @@ -442,7 +491,7 @@ Maybe Message::Serialize(Environment* env, entry.As()->GetConstructorName())); return Nothing(); } - if (host_object != nullptr && host_object->GetTransferMode() != + if (host_object && host_object->GetTransferMode() != BaseObject::TransferMode::kUntransferable) { delegate.AddHostObject(host_object); continue; @@ -452,6 +501,8 @@ Maybe Message::Serialize(Environment* env, THROW_ERR_INVALID_TRANSFER_OBJECT(env); return Nothing(); } + if (delegate.AddNestedHostObjects().IsNothing()) + return Nothing(); serializer.WriteHeader(); if (serializer.WriteValue(context, input).IsNothing()) { @@ -473,7 +524,8 @@ Maybe Message::Serialize(Environment* env, static_cast(contents.Data()), contents.ByteLength()}); } - delegate.Finish(); + if (delegate.Finish(context).IsNothing()) + return Nothing(); // The serializer gave us a buffer allocated using `malloc()`. std::pair data = serializer.Release(); @@ -717,9 +769,10 @@ void MessagePort::OnMessage() { HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(context); + Local emit_message = PersistentToLocal::Strong(emit_message_fn_); Local payload; - if (!ReceiveMessage(context, true).ToLocal(&payload)) break; + if (!ReceiveMessage(context, true).ToLocal(&payload)) goto reschedule; if (payload == env()->no_message_symbol()) break; if (!env()->can_call_into_js()) { @@ -728,8 +781,8 @@ void MessagePort::OnMessage() { continue; } - Local emit_message = PersistentToLocal::Strong(emit_message_fn_); if (MakeCallback(emit_message, 1, &payload).IsEmpty()) { + reschedule: // Re-schedule OnMessage() execution in case of failure. if (data_) TriggerAsync(); @@ -1047,8 +1100,187 @@ Local GetMessagePortConstructorTemplate(Environment* env) { return GetMessagePortConstructorTemplate(env); } +JSTransferable::JSTransferable(Environment* env, Local obj) + : BaseObject(env, obj) { + MakeWeak(); +} + +void JSTransferable::New(const FunctionCallbackInfo& args) { + CHECK(args.IsConstructCall()); + new JSTransferable(Environment::GetCurrent(args), args.This()); +} + +JSTransferable::TransferMode JSTransferable::GetTransferMode() const { + // Implement `kClone in this ? kCloneable : kTransferable`. + HandleScope handle_scope(env()->isolate()); + errors::TryCatchScope ignore_exceptions(env()); + + bool has_clone; + if (!object()->Has(env()->context(), + env()->messaging_clone_symbol()).To(&has_clone)) { + return TransferMode::kUntransferable; + } + + return has_clone ? TransferMode::kCloneable : TransferMode::kTransferable; +} + +std::unique_ptr JSTransferable::TransferForMessaging() { + return TransferOrClone(TransferMode::kTransferable); +} + +std::unique_ptr JSTransferable::CloneForMessaging() const { + return TransferOrClone(TransferMode::kCloneable); +} + +std::unique_ptr JSTransferable::TransferOrClone( + TransferMode mode) const { + // Call `this[symbol]()` where `symbol` is `kClone` or `kTransfer`, + // which should return an object with `data` and `deserializeInfo` properties; + // `data` is written to the serializer later, and `deserializeInfo` is stored + // on the `TransferData` instance as a string. + HandleScope handle_scope(env()->isolate()); + Local context = env()->isolate()->GetCurrentContext(); + Local method_name = mode == TransferMode::kCloneable ? + env()->messaging_clone_symbol() : env()->messaging_transfer_symbol(); + + Local method; + if (!object()->Get(context, method_name).ToLocal(&method)) { + return {}; + } + if (method->IsFunction()) { + Local result_v; + if (!method.As()->Call( + context, object(), 0, nullptr).ToLocal(&result_v)) { + return {}; + } + + if (result_v->IsObject()) { + Local result = result_v.As(); + Local data; + Local deserialize_info; + if (!result->Get(context, env()->data_string()).ToLocal(&data) || + !result->Get(context, env()->deserialize_info_string()) + .ToLocal(&deserialize_info)) { + return {}; + } + Utf8Value deserialize_info_str(env()->isolate(), deserialize_info); + if (*deserialize_info_str == nullptr) return {}; + return std::make_unique( + *deserialize_info_str, Global(env()->isolate(), data)); + } + } + + if (mode == TransferMode::kTransferable) + return TransferOrClone(TransferMode::kCloneable); + else + return {}; +} + +Maybe +JSTransferable::NestedTransferables() const { + // Call `this[kTransferList]()` and return the resulting list of BaseObjects. + HandleScope handle_scope(env()->isolate()); + Local context = env()->isolate()->GetCurrentContext(); + Local method_name = env()->messaging_transfer_list_symbol(); + + Local method; + if (!object()->Get(context, method_name).ToLocal(&method)) { + return Nothing(); + } + if (!method->IsFunction()) return Just(BaseObjectList {}); + + Local list_v; + if (!method.As()->Call( + context, object(), 0, nullptr).ToLocal(&list_v)) { + return Nothing(); + } + if (!list_v->IsArray()) return Just(BaseObjectList {}); + Local list = list_v.As(); + + BaseObjectList ret; + for (size_t i = 0; i < list->Length(); i++) { + Local value; + if (!list->Get(context, i).ToLocal(&value)) + return Nothing(); + if (env()->base_object_ctor_template()->HasInstance(value)) + ret.emplace_back(Unwrap(value.As())); + } + return Just(ret); +} + +Maybe JSTransferable::FinalizeTransferRead( + Local context, ValueDeserializer* deserializer) { + // Call `this[kDeserialize](data)` where `data` comes from the return value + // of `this[kTransfer]()` or `this[kClone]()`. + HandleScope handle_scope(env()->isolate()); + Local data; + if (!deserializer->ReadValue(context).ToLocal(&data)) return Nothing(); + + Local method_name = env()->messaging_deserialize_symbol(); + Local method; + if (!object()->Get(context, method_name).ToLocal(&method)) { + return Nothing(); + } + if (!method->IsFunction()) return Just(true); + + if (method.As()->Call(context, object(), 1, &data).IsEmpty()) { + return Nothing(); + } + return Just(true); +} + +JSTransferable::Data::Data(std::string&& deserialize_info, + v8::Global&& data) + : deserialize_info_(std::move(deserialize_info)), + data_(std::move(data)) {} + +BaseObjectPtr JSTransferable::Data::Deserialize( + Environment* env, + Local context, + std::unique_ptr self) { + // Create the JS wrapper object that will later be filled with data passed to + // the `[kDeserialize]()` method on it. This split is necessary, because here + // we need to create an object with the right prototype and internal fields, + // but the actual JS data stored in the serialized data can only be read at + // the end of the stream, after the main message has been read. + + if (context != env->context()) { + // It would be nice to throw some kind of exception here, but how do we + // pass that to end users? For now, just drop the message silently. + return {}; + } + HandleScope handle_scope(env->isolate()); + Local info; + if (!ToV8Value(context, deserialize_info_).ToLocal(&info)) return {}; + + Local ret; + CHECK(!env->messaging_deserialize_create_object().IsEmpty()); + if (!env->messaging_deserialize_create_object()->Call( + context, Null(env->isolate()), 1, &info).ToLocal(&ret) || + !env->base_object_ctor_template()->HasInstance(ret)) { + return {}; + } + + return BaseObjectPtr { Unwrap(ret.As()) }; +} + +Maybe JSTransferable::Data::FinalizeTransferWrite( + Local context, ValueSerializer* serializer) { + HandleScope handle_scope(context->GetIsolate()); + auto ret = serializer->WriteValue(context, PersistentToLocal::Strong(data_)); + data_.Reset(); + return ret; +} + namespace { +static void SetDeserializerCreateObjectFunction( + const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsFunction()); + env->set_messaging_deserialize_create_object(args[0].As()); +} + static void MessageChannel(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args.IsConstructCall()) { @@ -1091,6 +1323,19 @@ static void InitMessaging(Local target, templ->GetFunction(context).ToLocalChecked()).Check(); } + { + Local js_transferable_string = + FIXED_ONE_BYTE_STRING(env->isolate(), "JSTransferable"); + Local t = env->NewFunctionTemplate(JSTransferable::New); + t->Inherit(BaseObject::GetConstructorTemplate(env)); + t->SetClassName(js_transferable_string); + t->InstanceTemplate()->SetInternalFieldCount( + JSTransferable::kInternalFieldCount); + target->Set(context, + js_transferable_string, + t->GetFunction(context).ToLocalChecked()).Check(); + } + target->Set(context, env->message_port_constructor_string(), GetMessagePortConstructorTemplate(env) @@ -1103,6 +1348,8 @@ static void InitMessaging(Local target, env->SetMethod(target, "receiveMessageOnPort", MessagePort::ReceiveMessage); env->SetMethod(target, "moveMessagePortToContext", MessagePort::MoveToContext); + env->SetMethod(target, "setDeserializerCreateObjectFunction", + SetDeserializerCreateObjectFunction); { Local domexception = GetDOMException(context).ToLocalChecked(); diff --git a/src/node_messaging.h b/src/node_messaging.h index 34e55277477945..c70408f9591e71 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -31,6 +31,12 @@ class TransferData : public MemoryRetainer { Environment* env, v8::Local context, std::unique_ptr self) = 0; + // FinalizeTransferWrite() is the counterpart to + // BaseObject::FinalizeTransferRead(). It is called right after the transfer + // data was created, and defaults to doing nothing. After this function, + // this object should not hold any more Isolate-specific data. + virtual v8::Maybe FinalizeTransferWrite( + v8::Local context, v8::ValueSerializer* serializer); }; // Represents a single communication message. @@ -240,6 +246,52 @@ class MessagePort : public HandleWrap { friend class MessagePortData; }; +// Provide a base class from which JS classes that should be transferable or +// cloneable by postMesssage() can inherit. +// See e.g. FileHandle in internal/fs/promises.js for an example. +class JSTransferable : public BaseObject { + public: + JSTransferable(Environment* env, v8::Local obj); + static void New(const v8::FunctionCallbackInfo& args); + + TransferMode GetTransferMode() const override; + std::unique_ptr TransferForMessaging() override; + std::unique_ptr CloneForMessaging() const override; + v8::Maybe>> + NestedTransferables() const override; + v8::Maybe FinalizeTransferRead( + v8::Local context, + v8::ValueDeserializer* deserializer) override; + + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(JSTransferable) + SET_SELF_SIZE(JSTransferable) + + private: + std::unique_ptr TransferOrClone(TransferMode mode) const; + + class Data : public TransferData { + public: + Data(std::string&& deserialize_info, v8::Global&& data); + + BaseObjectPtr Deserialize( + Environment* env, + v8::Local context, + std::unique_ptr self) override; + v8::Maybe FinalizeTransferWrite( + v8::Local context, + v8::ValueSerializer* serializer) override; + + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(JSTransferableTransferData) + SET_SELF_SIZE(Data) + + private: + std::string deserialize_info_; + v8::Global data_; + }; +}; + v8::Local GetMessagePortConstructorTemplate( Environment* env); diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index b9e7855c3b3301..e04bcca95b5e2a 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -18,6 +18,7 @@ const expectedModules = new Set([ 'Internal Binding fs', 'Internal Binding fs_dir', 'Internal Binding inspector', + 'Internal Binding messaging', 'Internal Binding module_wrap', 'Internal Binding native_module', 'Internal Binding options', @@ -81,6 +82,7 @@ const expectedModules = new Set([ 'NativeModule internal/util/types', 'NativeModule internal/validators', 'NativeModule internal/vm/module', + 'NativeModule internal/worker/js_transferable', 'NativeModule path', 'NativeModule timers', 'NativeModule url', diff --git a/test/parallel/test-worker-workerdata-messageport.js b/test/parallel/test-worker-workerdata-messageport.js index 352d0729412ddb..9bf3422337e963 100644 --- a/test/parallel/test-worker-workerdata-messageport.js +++ b/test/parallel/test-worker-workerdata-messageport.js @@ -55,6 +55,7 @@ const meowScript = () => 'meow'; transferList: [] }), { code: 'ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST', - message: 'MessagePort was found in message but not listed in transferList' + message: 'Object that needs transfer was found in message but not ' + + 'listed in transferList' }); }