diff --git a/doc/api/errors.md b/doc/api/errors.md
index f3e5939f258576..0a49757da3e39e 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -629,6 +629,12 @@ An operation outside the bounds of a `Buffer` was attempted.
An attempt has been made to create a `Buffer` larger than the maximum allowed
size.
+
+### ERR_CANNOT_TRANSFER_OBJECT
+
+The value passed to `postMessage()` contained an object that is not supported
+for transferring.
+
### ERR_CANNOT_WATCH_SIGINT
@@ -1294,6 +1300,12 @@ strict compliance with the API specification (which in some cases may accept
`func(undefined)` and `func()` are treated identically, and the
[`ERR_INVALID_ARG_TYPE`][] error code may be used instead.
+
+### 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.
+
### ERR_MISSING_MODULE
diff --git a/doc/api/worker.md b/doc/api/worker.md
index 4724714cd62f26..6a391c5a9e3b19 100644
--- a/doc/api/worker.md
+++ b/doc/api/worker.md
@@ -83,7 +83,7 @@ the [HTML structured clone algorithm][]. In particular, it may contain circular
references and objects like typed arrays that the `JSON` API is not able
to stringify.
-`transferList` may be a list of `ArrayBuffer` objects.
+`transferList` may be a list of `ArrayBuffer` and `MessagePort` objects.
After transferring, they will not be usable on the sending side of the channel
anymore (even if they are not contained in `value`).
diff --git a/src/node_errors.h b/src/node_errors.h
index 81169d241bc226..9cadb0e18533ca 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -23,6 +23,7 @@ namespace node {
#define ERRORS_WITH_CODE(V) \
V(ERR_BUFFER_OUT_OF_BOUNDS, RangeError) \
V(ERR_BUFFER_TOO_LARGE, Error) \
+ V(ERR_CANNOT_TRANSFER_OBJECT, TypeError) \
V(ERR_CLOSED_MESSAGE_PORT, Error) \
V(ERR_CONSTRUCT_CALL_REQUIRED, Error) \
V(ERR_INDEX_OUT_OF_RANGE, RangeError) \
@@ -31,6 +32,7 @@ namespace node {
V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
V(ERR_MISSING_ARGS, TypeError) \
+ V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, TypeError) \
V(ERR_MISSING_MODULE, Error) \
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \
@@ -57,11 +59,14 @@ namespace node {
// Errors with predefined static messages
#define PREDEFINED_ERROR_MESSAGES(V) \
+ V(ERR_CANNOT_TRANSFER_OBJECT, "Cannot transfer object of unsupported type")\
V(ERR_CLOSED_MESSAGE_PORT, "Cannot send data on closed MessagePort") \
V(ERR_CONSTRUCT_CALL_REQUIRED, "Cannot call constructor without `new`") \
V(ERR_INDEX_OUT_OF_RANGE, "Index out of range") \
V(ERR_INVALID_TRANSFER_OBJECT, "Found invalid object in transferList") \
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
+ V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, \
+ "MessagePort was found in message but not listed in transferList") \
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, \
"Script execution was interrupted by `SIGINT`")
diff --git a/src/node_messaging.cc b/src/node_messaging.cc
index c6e701c7d94426..1c6551e0969f3e 100644
--- a/src/node_messaging.cc
+++ b/src/node_messaging.cc
@@ -41,14 +41,27 @@ namespace {
// `MessagePort`s and `SharedArrayBuffer`s, and make new JS objects out of them.
class DeserializerDelegate : public ValueDeserializer::Delegate {
public:
- DeserializerDelegate(Message* m, Environment* env)
- : env_(env), msg_(m) {}
+ DeserializerDelegate(Message* m,
+ Environment* env,
+ const std::vector& message_ports)
+ : env_(env), msg_(m), message_ports_(message_ports) {}
+
+ MaybeLocal