diff --git a/doc/api/errors.md b/doc/api/errors.md
index 64bd22f337f2c6..853a968d184363 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1570,8 +1570,9 @@ is thrown if a required option is missing.
### `ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST`
-A `MessagePort` was found in the object passed to a `postMessage()` call,
-but not provided in the `transferList` for that call.
+An object that needs to be explicitly listed in the `transferList` argument
+was found in the object passed to a `postMessage()` call, but not provided in
+the `transferList` for that call. Usually, this is a `MessagePort`.
### `ERR_MISSING_PASSPHRASE`
diff --git a/src/base_object.h b/src/base_object.h
index 9e47a028ed456b..5a18639c93282e 100644
--- a/src/base_object.h
+++ b/src/base_object.h
@@ -34,6 +34,10 @@ class Environment;
template
class BaseObjectPtrImpl;
+namespace worker {
+class TransferData;
+}
+
class BaseObject : public MemoryRetainer {
public:
enum InternalFields { kSlot, kInternalFieldCount };
@@ -101,7 +105,39 @@ class BaseObject : public MemoryRetainer {
static v8::Local GetConstructorTemplate(
Environment* env);
- protected:
+ // Interface for transferring BaseObject instances using the .postMessage()
+ // method of MessagePorts (and, by extension, Workers).
+ // GetTransferMode() returns a transfer mode that indicates how to deal with
+ // the current object:
+ // - kUntransferable:
+ // No transfer is possible, either because this type of BaseObject does
+ // not know how to be transfered, or because it is not in a state in
+ // which it is possible to do so (e.g. because it has already been
+ // transfered).
+ // - kTransferable:
+ // This object can be transfered in a destructive fashion, i.e. will be
+ // rendered unusable on the sending side of the channel in the process
+ // of being transfered. (In C++ this would be referred to as movable but
+ // not copyable.) Objects of this type need to be listed in the
+ // `transferList` argument of the relevant postMessage() call in order to
+ // 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.
+ // - 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.
+ enum class TransferMode {
+ kUntransferable,
+ kTransferable,
+ kCloneable
+ };
+ virtual TransferMode GetTransferMode() const;
+ virtual std::unique_ptr TransferForMessaging();
+ virtual std::unique_ptr CloneForMessaging() const;
+
virtual inline void OnGCCollect();
private:
diff --git a/src/node_messaging.cc b/src/node_messaging.cc
index f19e541bff08fb..7bc406baf0bada 100644
--- a/src/node_messaging.cc
+++ b/src/node_messaging.cc
@@ -36,6 +36,19 @@ using v8::ValueSerializer;
using v8::WasmModuleObject;
namespace node {
+
+BaseObject::TransferMode BaseObject::GetTransferMode() const {
+ return BaseObject::TransferMode::kUntransferable;
+}
+
+std::unique_ptr BaseObject::TransferForMessaging() {
+ return CloneForMessaging();
+}
+
+std::unique_ptr BaseObject::CloneForMessaging() const {
+ return {};
+}
+
namespace worker {
Message::Message(MallocedBuffer&& buffer)
@@ -54,21 +67,20 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
DeserializerDelegate(
Message* m,
Environment* env,
- const std::vector& message_ports,
+ const std::vector>& host_objects,
const std::vector>& shared_array_buffers,
const std::vector& wasm_modules)
- : message_ports_(message_ports),
+ : host_objects_(host_objects),
shared_array_buffers_(shared_array_buffers),
wasm_modules_(wasm_modules) {}
MaybeLocal ReadHostObject(Isolate* isolate) override {
- // Currently, only MessagePort hosts objects are supported, so identifying
- // by the index in the message's MessagePort array is sufficient.
+ // Identifying the index in the message's BaseObject array is sufficient.
uint32_t id;
if (!deserializer->ReadUint32(&id))
return MaybeLocal();
- CHECK_LE(id, message_ports_.size());
- return message_ports_[id]->object(isolate);
+ CHECK_LE(id, host_objects_.size());
+ return host_objects_[id]->object(isolate);
}
MaybeLocal GetSharedArrayBufferFromId(
@@ -87,7 +99,7 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
ValueDeserializer* deserializer = nullptr;
private:
- const std::vector& message_ports_;
+ const std::vector>& host_objects_;
const std::vector>& shared_array_buffers_;
const std::vector& wasm_modules_;
};
@@ -101,22 +113,25 @@ MaybeLocal Message::Deserialize(Environment* env,
EscapableHandleScope handle_scope(env->isolate());
Context::Scope context_scope(context);
- // Create all necessary MessagePort handles.
- std::vector ports(message_ports_.size());
- for (uint32_t i = 0; i < message_ports_.size(); ++i) {
- ports[i] = MessagePort::New(env,
- context,
- std::move(message_ports_[i]));
- if (ports[i] == nullptr) {
- for (MessagePort* port : ports) {
- // This will eventually release the MessagePort object itself.
- if (port != nullptr)
- port->Close();
+ // Create all necessary objects for transferables, e.g. MessagePort handles.
+ std::vector> host_objects(transferables_.size());
+ 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();
}
}
- message_ports_.clear();
+ transferables_.clear();
std::vector> shared_array_buffers;
// Attach all transferred SharedArrayBuffers to their new Isolate.
@@ -130,7 +145,7 @@ MaybeLocal Message::Deserialize(Environment* env,
shared_array_buffers_.clear();
DeserializerDelegate delegate(
- this, env, ports, shared_array_buffers, wasm_modules_);
+ this, env, host_objects, shared_array_buffers, wasm_modules_);
ValueDeserializer deserializer(
env->isolate(),
reinterpret_cast(main_message_buf_.data),
@@ -175,8 +190,8 @@ void Message::AddSharedArrayBuffer(
shared_array_buffers_.push_back(reference);
}
-void Message::AddMessagePort(std::unique_ptr&& data) {
- message_ports_.emplace_back(std::move(data));
+void Message::AddTransferable(std::unique_ptr&& data) {
+ transferables_.emplace_back(std::move(data));
}
uint32_t Message::AddWASMModule(WasmModuleObject::TransferrableModule&& mod) {
@@ -242,8 +257,8 @@ class SerializerDelegate : public ValueSerializer::Delegate {
}
Maybe WriteHostObject(Isolate* isolate, Local object) override {
- if (env_->message_port_constructor_template()->HasInstance(object)) {
- return WriteMessagePort(Unwrap(object));
+ if (env_->base_object_ctor_template()->HasInstance(object)) {
+ return WriteHostObject(Unwrap(object));
}
ThrowDataCloneError(env_->clone_unsupported_type_str());
@@ -282,32 +297,61 @@ class SerializerDelegate : public ValueSerializer::Delegate {
void Finish() {
// Only close the MessagePort handles and actually transfer them
// once we know that serialization succeeded.
- for (MessagePort* port : ports_) {
- port->Close();
- msg_->AddMessagePort(port->Detach());
+ for (uint32_t i = 0; i < host_objects_.size(); i++) {
+ BaseObject* host_object = 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);
+ msg_->AddTransferable(std::move(data));
}
}
+ inline void AddHostObject(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);
+ }
+
ValueSerializer* serializer = nullptr;
private:
- Maybe WriteMessagePort(MessagePort* port) {
- for (uint32_t i = 0; i < ports_.size(); i++) {
- if (ports_[i] == port) {
+ Maybe WriteHostObject(BaseObject* host_object) {
+ for (uint32_t i = 0; i < host_objects_.size(); i++) {
+ if (host_objects_[i] == host_object) {
serializer->WriteUint32(i);
return Just(true);
}
}
- THROW_ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST(env_);
- return Nothing();
+ BaseObject::TransferMode mode = host_object->GetTransferMode();
+ if (mode == BaseObject::TransferMode::kUntransferable) {
+ ThrowDataCloneError(env_->clone_unsupported_type_str());
+ return Nothing();
+ } else if (mode == BaseObject::TransferMode::kTransferable) {
+ // TODO(addaleax): This message code is too specific. Fix that in a
+ // semver-major follow-up.
+ THROW_ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST(env_);
+ return Nothing();
+ }
+
+ CHECK_EQ(mode, BaseObject::TransferMode::kCloneable);
+ uint32_t index = host_objects_.size();
+ if (first_cloned_object_index_ == SIZE_MAX)
+ first_cloned_object_index_ = index;
+ serializer->WriteUint32(index);
+ host_objects_.push_back(host_object);
+ return Just(true);
}
Environment* env_;
Local context_;
Message* msg_;
std::vector> seen_shared_array_buffers_;
- std::vector ports_;
+ std::vector host_objects_;
+ size_t first_cloned_object_index_ = SIZE_MAX;
friend class worker::Message;
};
@@ -366,8 +410,7 @@ Maybe Message::Serialize(Environment* env,
array_buffers.push_back(ab);
serializer.TransferArrayBuffer(id, ab);
continue;
- } else if (env->message_port_constructor_template()
- ->HasInstance(entry)) {
+ } else if (env->base_object_ctor_template()->HasInstance(entry)) {
// Check if the source MessagePort is being transferred.
if (!source_port.IsEmpty() && entry == source_port) {
ThrowDataCloneException(
@@ -376,8 +419,10 @@ Maybe Message::Serialize(Environment* env,
"Transfer list contains source port"));
return Nothing();
}
- MessagePort* port = Unwrap(entry.As());
- if (port == nullptr || port->IsDetached()) {
+ BaseObject* host_object = Unwrap(entry.As());
+ if (env->message_port_constructor_template()->HasInstance(entry) &&
+ (host_object == nullptr ||
+ static_cast(host_object)->IsDetached())) {
ThrowDataCloneException(
context,
FIXED_ONE_BYTE_STRING(
@@ -385,17 +430,23 @@ Maybe Message::Serialize(Environment* env,
"MessagePort in transfer list is already detached"));
return Nothing();
}
- if (std::find(delegate.ports_.begin(), delegate.ports_.end(), port) !=
- delegate.ports_.end()) {
+ if (std::find(delegate.host_objects_.begin(),
+ delegate.host_objects_.end(),
+ host_object) != delegate.host_objects_.end()) {
ThrowDataCloneException(
context,
- FIXED_ONE_BYTE_STRING(
- env->isolate(),
- "Transfer list contains duplicate MessagePort"));
+ String::Concat(env->isolate(),
+ FIXED_ONE_BYTE_STRING(
+ env->isolate(),
+ "Transfer list contains duplicate "),
+ entry.As()->GetConstructorName()));
return Nothing();
}
- delegate.ports_.push_back(port);
- continue;
+ if (host_object != nullptr && host_object->GetTransferMode() !=
+ BaseObject::TransferMode::kUntransferable) {
+ delegate.AddHostObject(host_object);
+ continue;
+ }
}
THROW_ERR_INVALID_TRANSFER_OBJECT(env);
@@ -436,7 +487,7 @@ void Message::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("array_buffer_contents", array_buffer_contents_);
tracker->TrackFieldWithSize("shared_array_buffers",
shared_array_buffers_.size() * sizeof(shared_array_buffers_[0]));
- tracker->TrackField("message_ports", message_ports_);
+ tracker->TrackField("transferables", transferables_);
}
MessagePortData::MessagePortData(MessagePort* owner) : owner_(owner) { }
@@ -702,6 +753,25 @@ std::unique_ptr MessagePort::Detach() {
return std::move(data_);
}
+BaseObject::TransferMode MessagePort::GetTransferMode() const {
+ if (IsDetached())
+ return BaseObject::TransferMode::kUntransferable;
+ return BaseObject::TransferMode::kTransferable;
+}
+
+std::unique_ptr MessagePort::TransferForMessaging() {
+ Close();
+ return Detach();
+}
+
+BaseObjectPtr MessagePortData::Deserialize(
+ Environment* env,
+ Local context,
+ std::unique_ptr self) {
+ return BaseObjectPtr { MessagePort::New(
+ env, context,
+ static_unique_pointer_cast(std::move(self))) };
+}
Maybe MessagePort::PostMessage(Environment* env,
Local message_v,
@@ -729,8 +799,8 @@ Maybe MessagePort::PostMessage(Environment* env,
// Check if the target port is posted to itself.
if (data_->sibling_ != nullptr) {
- for (const auto& port_data : msg.message_ports()) {
- if (data_->sibling_ == port_data.get()) {
+ for (const auto& transferable : msg.transferables()) {
+ if (data_->sibling_ == transferable.get()) {
doomed = true;
ProcessEmitWarning(env, "The target port was posted to itself, and "
"the communication channel was lost");
diff --git a/src/node_messaging.h b/src/node_messaging.h
index 43f710a84e8315..34e55277477945 100644
--- a/src/node_messaging.h
+++ b/src/node_messaging.h
@@ -16,6 +16,23 @@ class MessagePort;
typedef MaybeStackBuffer, 8> TransferList;
+// Used to represent the in-flight structure of an object that is being
+// transfered or cloned using postMessage().
+class TransferData : public MemoryRetainer {
+ public:
+ // Deserialize this object on the receiving end after a .postMessage() call.
+ // - `context` may not be the same as `env->context()`. This method should
+ // not produce JS objects coming from Contexts other than `context`.
+ // - `self` is a unique_ptr for the object that this is being called on.
+ // - The return value is treated like a `Maybe`, i.e. if `nullptr` is
+ // returned, any further deserialization of the message is stopped and
+ // control is returned to the event loop or JS as soon as possible.
+ virtual BaseObjectPtr Deserialize(
+ Environment* env,
+ v8::Local context,
+ std::unique_ptr self) = 0;
+};
+
// Represents a single communication message.
class Message : public MemoryRetainer {
public:
@@ -55,16 +72,17 @@ class Message : public MemoryRetainer {
void AddSharedArrayBuffer(const SharedArrayBufferMetadataReference& ref);
// Internal method of Message that is called once serialization finishes
// and that transfers ownership of `data` to this message.
- void AddMessagePort(std::unique_ptr&& data);
+ void AddTransferable(std::unique_ptr&& data);
// Internal method of Message that is called when a new WebAssembly.Module
// object is encountered in the incoming value's structure.
uint32_t AddWASMModule(v8::WasmModuleObject::TransferrableModule&& mod);
- // The MessagePorts that will be transferred, as recorded by Serialize().
+ // The host objects that will be transferred, as recorded by Serialize()
+ // (e.g. MessagePorts).
// Used for warning user about posting the target MessagePort to itself,
// which will as a side effect destroy the communication channel.
- const std::vector>& message_ports() const {
- return message_ports_;
+ const std::vector>& transferables() const {
+ return transferables_;
}
void MemoryInfo(MemoryTracker* tracker) const override;
@@ -76,7 +94,7 @@ class Message : public MemoryRetainer {
MallocedBuffer main_message_buf_;
std::vector> array_buffer_contents_;
std::vector shared_array_buffers_;
- std::vector> message_ports_;
+ std::vector> transferables_;
std::vector wasm_modules_;
friend class MessagePort;
@@ -84,7 +102,7 @@ class Message : public MemoryRetainer {
// This contains all data for a `MessagePort` instance that is not tied to
// a specific Environment/Isolate/event loop, for easier transfer between those.
-class MessagePortData : public MemoryRetainer {
+class MessagePortData : public TransferData {
public:
explicit MessagePortData(MessagePort* owner);
~MessagePortData() override;
@@ -109,6 +127,10 @@ class MessagePortData : public MemoryRetainer {
void Disentangle();
void MemoryInfo(MemoryTracker* tracker) const override;
+ BaseObjectPtr Deserialize(
+ Environment* env,
+ v8::Local context,
+ std::unique_ptr self) override;
SET_MEMORY_INFO_NAME(MessagePortData)
SET_SELF_SIZE(MessagePortData)
@@ -196,6 +218,9 @@ class MessagePort : public HandleWrap {
// NULL pointer to the C++ MessagePort object is also detached.
inline bool IsDetached() const;
+ TransferMode GetTransferMode() const override;
+ std::unique_ptr TransferForMessaging() override;
+
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(MessagePort)
SET_SELF_SIZE(MessagePort)
diff --git a/src/util.h b/src/util.h
index b1950500061bf1..f406e3ad0e5849 100644
--- a/src/util.h
+++ b/src/util.h
@@ -761,6 +761,12 @@ class PersistentToLocal {
}
};
+// Like std::static_pointer_cast but for unique_ptr with the default deleter.
+template
+std::unique_ptr static_unique_pointer_cast(std::unique_ptr&& ptr) {
+ return std::unique_ptr(static_cast(ptr.release()));
+}
+
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS