mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
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 <benjamingr@gmail.com>
This commit is contained in:
@@ -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');
|
||||
|
||||
31
lib/internal/worker/js_transferable.js
Normal file
31
lib/internal/worker/js_transferable.js
Normal file
@@ -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
|
||||
};
|
||||
1
node.gyp
1
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',
|
||||
|
||||
@@ -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<worker::TransferData> TransferForMessaging();
|
||||
virtual std::unique_ptr<worker::TransferData> CloneForMessaging() const;
|
||||
virtual v8::Maybe<std::vector<BaseObjectPtrImpl<BaseObject, false>>>
|
||||
NestedTransferables() const;
|
||||
virtual v8::Maybe<bool> FinalizeTransferRead(
|
||||
v8::Local<v8::Context> context, v8::ValueDeserializer* deserializer);
|
||||
|
||||
virtual inline void OnGCCollect();
|
||||
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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") \
|
||||
|
||||
@@ -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<BaseObjectPtr<BaseObject>>;
|
||||
|
||||
BaseObject::TransferMode BaseObject::GetTransferMode() const {
|
||||
return BaseObject::TransferMode::kUntransferable;
|
||||
}
|
||||
@@ -49,8 +52,22 @@ std::unique_ptr<worker::TransferData> BaseObject::CloneForMessaging() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<BaseObjectList> BaseObject::NestedTransferables() const {
|
||||
return Just(BaseObjectList {});
|
||||
}
|
||||
|
||||
Maybe<bool> BaseObject::FinalizeTransferRead(
|
||||
Local<Context> context, ValueDeserializer* deserializer) {
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
namespace worker {
|
||||
|
||||
Maybe<bool> TransferData::FinalizeTransferWrite(
|
||||
Local<Context> context, ValueSerializer* serializer) {
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
Message::Message(MallocedBuffer<char>&& buffer)
|
||||
: main_message_buf_(std::move(buffer)) {}
|
||||
|
||||
@@ -115,21 +132,22 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
|
||||
|
||||
// Create all necessary objects for transferables, e.g. MessagePort handles.
|
||||
std::vector<BaseObjectPtr<BaseObject>> host_objects(transferables_.size());
|
||||
auto cleanup = OnScopeLeave([&]() {
|
||||
for (BaseObjectPtr<BaseObject> 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<BaseObject> 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<Value>();
|
||||
}
|
||||
if (!host_objects[i]) return {};
|
||||
}
|
||||
transferables_.clear();
|
||||
|
||||
@@ -180,9 +198,18 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
|
||||
array_buffer_contents_.clear();
|
||||
|
||||
if (deserializer.ReadHeader(context).IsNothing())
|
||||
return MaybeLocal<Value>();
|
||||
return handle_scope.Escape(
|
||||
deserializer.ReadValue(context).FromMaybe(Local<Value>()));
|
||||
return {};
|
||||
Local<Value> return_value;
|
||||
if (!deserializer.ReadValue(context).ToLocal(&return_value))
|
||||
return {};
|
||||
|
||||
for (BaseObjectPtr<BaseObject> 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<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override {
|
||||
if (env_->base_object_ctor_template()->HasInstance(object)) {
|
||||
return WriteHostObject(Unwrap<BaseObject>(object));
|
||||
return WriteHostObject(
|
||||
BaseObjectPtr<BaseObject> { Unwrap<BaseObject>(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<bool> Finish(Local<Context> context) {
|
||||
for (uint32_t i = 0; i < host_objects_.size(); i++) {
|
||||
BaseObject* host_object = host_objects_[i];
|
||||
BaseObjectPtr<BaseObject> host_object = std::move(host_objects_[i]);
|
||||
std::unique_ptr<TransferData> data;
|
||||
if (i < first_cloned_object_index_)
|
||||
data = host_object->TransferForMessaging();
|
||||
if (!data)
|
||||
data = host_object->CloneForMessaging();
|
||||
CHECK(data);
|
||||
if (!data) return Nothing<bool>();
|
||||
if (data->FinalizeTransferWrite(context, serializer).IsNothing())
|
||||
return Nothing<bool>();
|
||||
msg_->AddTransferable(std::move(data));
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
inline void AddHostObject(BaseObject* host_object) {
|
||||
inline void AddHostObject(BaseObjectPtr<BaseObject> 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<bool> AddNestedHostObjects() {
|
||||
for (size_t i = 0; i < host_objects_.size(); i++) {
|
||||
std::vector<BaseObjectPtr<BaseObject>> nested_transferables;
|
||||
if (!host_objects_[i]->NestedTransferables().To(&nested_transferables))
|
||||
return Nothing<bool>();
|
||||
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<bool> WriteHostObject(BaseObject* host_object) {
|
||||
Maybe<bool> WriteHostObject(BaseObjectPtr<BaseObject> 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> context_;
|
||||
Message* msg_;
|
||||
std::vector<Global<SharedArrayBuffer>> seen_shared_array_buffers_;
|
||||
std::vector<BaseObject*> host_objects_;
|
||||
std::vector<BaseObjectPtr<BaseObject>> host_objects_;
|
||||
size_t first_cloned_object_index_ = SIZE_MAX;
|
||||
|
||||
friend class worker::Message;
|
||||
@@ -419,10 +467,11 @@ Maybe<bool> Message::Serialize(Environment* env,
|
||||
"Transfer list contains source port"));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
BaseObject* host_object = Unwrap<BaseObject>(entry.As<Object>());
|
||||
BaseObjectPtr<BaseObject> host_object {
|
||||
Unwrap<BaseObject>(entry.As<Object>()) };
|
||||
if (env->message_port_constructor_template()->HasInstance(entry) &&
|
||||
(host_object == nullptr ||
|
||||
static_cast<MessagePort*>(host_object)->IsDetached())) {
|
||||
(!host_object ||
|
||||
static_cast<MessagePort*>(host_object.get())->IsDetached())) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
FIXED_ONE_BYTE_STRING(
|
||||
@@ -442,7 +491,7 @@ Maybe<bool> Message::Serialize(Environment* env,
|
||||
entry.As<Object>()->GetConstructorName()));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
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<bool> Message::Serialize(Environment* env,
|
||||
THROW_ERR_INVALID_TRANSFER_OBJECT(env);
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (delegate.AddNestedHostObjects().IsNothing())
|
||||
return Nothing<bool>();
|
||||
|
||||
serializer.WriteHeader();
|
||||
if (serializer.WriteValue(context, input).IsNothing()) {
|
||||
@@ -473,7 +524,8 @@ Maybe<bool> Message::Serialize(Environment* env,
|
||||
static_cast<char*>(contents.Data()), contents.ByteLength()});
|
||||
}
|
||||
|
||||
delegate.Finish();
|
||||
if (delegate.Finish(context).IsNothing())
|
||||
return Nothing<bool>();
|
||||
|
||||
// The serializer gave us a buffer allocated using `malloc()`.
|
||||
std::pair<uint8_t*, size_t> data = serializer.Release();
|
||||
@@ -717,9 +769,10 @@ void MessagePort::OnMessage() {
|
||||
|
||||
HandleScope handle_scope(env()->isolate());
|
||||
Context::Scope context_scope(context);
|
||||
Local<Function> emit_message = PersistentToLocal::Strong(emit_message_fn_);
|
||||
|
||||
Local<Value> 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<Function> 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<FunctionTemplate> GetMessagePortConstructorTemplate(Environment* env) {
|
||||
return GetMessagePortConstructorTemplate(env);
|
||||
}
|
||||
|
||||
JSTransferable::JSTransferable(Environment* env, Local<Object> obj)
|
||||
: BaseObject(env, obj) {
|
||||
MakeWeak();
|
||||
}
|
||||
|
||||
void JSTransferable::New(const FunctionCallbackInfo<Value>& 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<TransferData> JSTransferable::TransferForMessaging() {
|
||||
return TransferOrClone(TransferMode::kTransferable);
|
||||
}
|
||||
|
||||
std::unique_ptr<TransferData> JSTransferable::CloneForMessaging() const {
|
||||
return TransferOrClone(TransferMode::kCloneable);
|
||||
}
|
||||
|
||||
std::unique_ptr<TransferData> 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> context = env()->isolate()->GetCurrentContext();
|
||||
Local<Symbol> method_name = mode == TransferMode::kCloneable ?
|
||||
env()->messaging_clone_symbol() : env()->messaging_transfer_symbol();
|
||||
|
||||
Local<Value> method;
|
||||
if (!object()->Get(context, method_name).ToLocal(&method)) {
|
||||
return {};
|
||||
}
|
||||
if (method->IsFunction()) {
|
||||
Local<Value> result_v;
|
||||
if (!method.As<Function>()->Call(
|
||||
context, object(), 0, nullptr).ToLocal(&result_v)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (result_v->IsObject()) {
|
||||
Local<Object> result = result_v.As<Object>();
|
||||
Local<Value> data;
|
||||
Local<Value> 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<Data>(
|
||||
*deserialize_info_str, Global<Value>(env()->isolate(), data));
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == TransferMode::kTransferable)
|
||||
return TransferOrClone(TransferMode::kCloneable);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<BaseObjectList>
|
||||
JSTransferable::NestedTransferables() const {
|
||||
// Call `this[kTransferList]()` and return the resulting list of BaseObjects.
|
||||
HandleScope handle_scope(env()->isolate());
|
||||
Local<Context> context = env()->isolate()->GetCurrentContext();
|
||||
Local<Symbol> method_name = env()->messaging_transfer_list_symbol();
|
||||
|
||||
Local<Value> method;
|
||||
if (!object()->Get(context, method_name).ToLocal(&method)) {
|
||||
return Nothing<BaseObjectList>();
|
||||
}
|
||||
if (!method->IsFunction()) return Just(BaseObjectList {});
|
||||
|
||||
Local<Value> list_v;
|
||||
if (!method.As<Function>()->Call(
|
||||
context, object(), 0, nullptr).ToLocal(&list_v)) {
|
||||
return Nothing<BaseObjectList>();
|
||||
}
|
||||
if (!list_v->IsArray()) return Just(BaseObjectList {});
|
||||
Local<Array> list = list_v.As<Array>();
|
||||
|
||||
BaseObjectList ret;
|
||||
for (size_t i = 0; i < list->Length(); i++) {
|
||||
Local<Value> value;
|
||||
if (!list->Get(context, i).ToLocal(&value))
|
||||
return Nothing<BaseObjectList>();
|
||||
if (env()->base_object_ctor_template()->HasInstance(value))
|
||||
ret.emplace_back(Unwrap<BaseObject>(value.As<Object>()));
|
||||
}
|
||||
return Just(ret);
|
||||
}
|
||||
|
||||
Maybe<bool> JSTransferable::FinalizeTransferRead(
|
||||
Local<Context> 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<Value> data;
|
||||
if (!deserializer->ReadValue(context).ToLocal(&data)) return Nothing<bool>();
|
||||
|
||||
Local<Symbol> method_name = env()->messaging_deserialize_symbol();
|
||||
Local<Value> method;
|
||||
if (!object()->Get(context, method_name).ToLocal(&method)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (!method->IsFunction()) return Just(true);
|
||||
|
||||
if (method.As<Function>()->Call(context, object(), 1, &data).IsEmpty()) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
JSTransferable::Data::Data(std::string&& deserialize_info,
|
||||
v8::Global<v8::Value>&& data)
|
||||
: deserialize_info_(std::move(deserialize_info)),
|
||||
data_(std::move(data)) {}
|
||||
|
||||
BaseObjectPtr<BaseObject> JSTransferable::Data::Deserialize(
|
||||
Environment* env,
|
||||
Local<Context> context,
|
||||
std::unique_ptr<TransferData> 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<Value> info;
|
||||
if (!ToV8Value(context, deserialize_info_).ToLocal(&info)) return {};
|
||||
|
||||
Local<Value> 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<BaseObject> { Unwrap<BaseObject>(ret.As<Object>()) };
|
||||
}
|
||||
|
||||
Maybe<bool> JSTransferable::Data::FinalizeTransferWrite(
|
||||
Local<Context> 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<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK(args[0]->IsFunction());
|
||||
env->set_messaging_deserialize_create_object(args[0].As<Function>());
|
||||
}
|
||||
|
||||
static void MessageChannel(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
if (!args.IsConstructCall()) {
|
||||
@@ -1091,6 +1323,19 @@ static void InitMessaging(Local<Object> target,
|
||||
templ->GetFunction(context).ToLocalChecked()).Check();
|
||||
}
|
||||
|
||||
{
|
||||
Local<String> js_transferable_string =
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(), "JSTransferable");
|
||||
Local<FunctionTemplate> 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<Object> target,
|
||||
env->SetMethod(target, "receiveMessageOnPort", MessagePort::ReceiveMessage);
|
||||
env->SetMethod(target, "moveMessagePortToContext",
|
||||
MessagePort::MoveToContext);
|
||||
env->SetMethod(target, "setDeserializerCreateObjectFunction",
|
||||
SetDeserializerCreateObjectFunction);
|
||||
|
||||
{
|
||||
Local<Function> domexception = GetDOMException(context).ToLocalChecked();
|
||||
|
||||
@@ -31,6 +31,12 @@ class TransferData : public MemoryRetainer {
|
||||
Environment* env,
|
||||
v8::Local<v8::Context> context,
|
||||
std::unique_ptr<TransferData> 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<bool> FinalizeTransferWrite(
|
||||
v8::Local<v8::Context> 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<v8::Object> obj);
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
TransferMode GetTransferMode() const override;
|
||||
std::unique_ptr<TransferData> TransferForMessaging() override;
|
||||
std::unique_ptr<TransferData> CloneForMessaging() const override;
|
||||
v8::Maybe<std::vector<BaseObjectPtr<BaseObject>>>
|
||||
NestedTransferables() const override;
|
||||
v8::Maybe<bool> FinalizeTransferRead(
|
||||
v8::Local<v8::Context> context,
|
||||
v8::ValueDeserializer* deserializer) override;
|
||||
|
||||
SET_NO_MEMORY_INFO()
|
||||
SET_MEMORY_INFO_NAME(JSTransferable)
|
||||
SET_SELF_SIZE(JSTransferable)
|
||||
|
||||
private:
|
||||
std::unique_ptr<TransferData> TransferOrClone(TransferMode mode) const;
|
||||
|
||||
class Data : public TransferData {
|
||||
public:
|
||||
Data(std::string&& deserialize_info, v8::Global<v8::Value>&& data);
|
||||
|
||||
BaseObjectPtr<BaseObject> Deserialize(
|
||||
Environment* env,
|
||||
v8::Local<v8::Context> context,
|
||||
std::unique_ptr<TransferData> self) override;
|
||||
v8::Maybe<bool> FinalizeTransferWrite(
|
||||
v8::Local<v8::Context> 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<v8::Value> data_;
|
||||
};
|
||||
};
|
||||
|
||||
v8::Local<v8::FunctionTemplate> GetMessagePortConstructorTemplate(
|
||||
Environment* env);
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user