99#include " node_external_reference.h"
1010#include " node_process-inl.h"
1111#include " util-inl.h"
12+ #include " util.h"
1213
1314using node::contextify::ContextifyContext;
1415using node::errors::TryCatchScope;
@@ -66,6 +67,10 @@ bool Message::IsCloseMessage() const {
6667
6768namespace {
6869
70+ MaybeLocal<Function> GetDOMException (Local<Context> context);
71+
72+ static const uint32_t kDOMExceptionTag = 0xD011 ;
73+
6974// This is used to tell V8 how to read transferred host objects, like other
7075// `MessagePort`s and `SharedArrayBuffer`s, and make new JS objects out of them.
7176class DeserializerDelegate : public ValueDeserializer ::Delegate {
@@ -83,11 +88,63 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
8388 wasm_modules_(wasm_modules),
8489 shared_value_conveyor_(shared_value_conveyor) {}
8590
91+ MaybeLocal<Object> ReadDOMException (Isolate* isolate,
92+ Local<Context> context,
93+ v8::ValueDeserializer* deserializer) {
94+ Local<Value> name, message;
95+ if (!deserializer->ReadValue (context).ToLocal (&name) ||
96+ !deserializer->ReadValue (context).ToLocal (&message)) {
97+ return MaybeLocal<Object>();
98+ }
99+
100+ bool has_code = false ;
101+ Local<Value> code;
102+ has_code = deserializer->ReadValue (context).ToLocal (&code);
103+
104+ // V8 disallows executing JS code in the deserialization process, so we
105+ // cannot create a DOMException object directly. Instead, we create a
106+ // placeholder object that will be converted to a DOMException object
107+ // later on.
108+ Local<Object> placeholder = Object::New (isolate);
109+ if (placeholder
110+ ->Set (context,
111+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_name" ),
112+ name)
113+ .IsNothing () ||
114+ placeholder
115+ ->Set (context,
116+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_message" ),
117+ message)
118+ .IsNothing () ||
119+ (has_code &&
120+ placeholder
121+ ->Set (context,
122+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_code" ),
123+ code)
124+ .IsNothing ()) ||
125+ placeholder
126+ ->Set (context,
127+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_placeholder" ),
128+ v8::True (isolate))
129+ .IsNothing ()) {
130+ return MaybeLocal<Object>();
131+ }
132+
133+ return placeholder;
134+ }
135+
86136 MaybeLocal<Object> ReadHostObject (Isolate* isolate) override {
87137 // Identifying the index in the message's BaseObject array is sufficient.
88138 uint32_t id;
89139 if (!deserializer->ReadUint32 (&id))
90140 return MaybeLocal<Object>();
141+
142+ EscapableHandleScope scope (isolate);
143+ Local<Context> context = isolate->GetCurrentContext ();
144+ if (id == kDOMExceptionTag ) {
145+ return ReadDOMException (isolate, context, deserializer);
146+ }
147+
91148 if (id != kNormalObject ) {
92149 CHECK_LT (id, host_objects_.size ());
93150 Local<Object> object = host_objects_[id]->object (isolate);
@@ -97,8 +154,6 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
97154 return object;
98155 }
99156 }
100- EscapableHandleScope scope (isolate);
101- Local<Context> context = isolate->GetCurrentContext ();
102157 Local<Value> object;
103158 if (!deserializer->ReadValue (context).ToLocal (&object))
104159 return MaybeLocal<Object>();
@@ -136,6 +191,70 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
136191
137192} // anonymous namespace
138193
194+ MaybeLocal<Object> ConvertDOMExceptionData (Local<Context> context,
195+ Local<Value> value) {
196+ if (!value->IsObject ()) return MaybeLocal<Object>();
197+
198+ Isolate* isolate = context->GetIsolate ();
199+ Local<Object> obj = value.As <Object>();
200+
201+ Local<String> marker_key =
202+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_placeholder" );
203+ Local<Value> marker_val;
204+ if (!obj->Get (context, marker_key).ToLocal (&marker_val) ||
205+ !marker_val->IsTrue ()) {
206+ return MaybeLocal<Object>();
207+ }
208+
209+ Local<String> name_key =
210+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_name" );
211+ Local<String> message_key =
212+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_message" );
213+ Local<String> code_key =
214+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_code" );
215+
216+ Local<Value> name, message, code;
217+ if (!obj->Get (context, name_key).ToLocal (&name) ||
218+ !obj->Get (context, message_key).ToLocal (&message)) {
219+ return MaybeLocal<Object>();
220+ }
221+ bool has_code = obj->Get (context, code_key).ToLocal (&code);
222+
223+ Local<Function> dom_exception_ctor;
224+ if (!GetDOMException (context).ToLocal (&dom_exception_ctor)) {
225+ return MaybeLocal<Object>();
226+ }
227+
228+ // Create arguments for the constructor according to the JS implementation
229+ // First arg: message
230+ // Second arg: options object with name and potentially code
231+ Local<Object> options = Object::New (isolate);
232+ if (options
233+ ->Set (context,
234+ FIXED_ONE_BYTE_STRING (isolate, " name" ),
235+ name)
236+ .IsNothing ()) {
237+ return MaybeLocal<Object>();
238+ }
239+
240+ if (has_code &&
241+ options
242+ ->Set (context,
243+ FIXED_ONE_BYTE_STRING (isolate, " code" ),
244+ code)
245+ .IsNothing ()) {
246+ return MaybeLocal<Object>();
247+ }
248+
249+ Local<Value> argv[2 ] = {message, options};
250+ Local<Value> exception;
251+ if (!dom_exception_ctor->NewInstance (context, 2 , argv).ToLocal (&exception)) {
252+ return MaybeLocal<Object>();
253+ }
254+
255+ return exception.As <Object>();
256+ }
257+
139258MaybeLocal<Value> Message::Deserialize (Environment* env,
140259 Local<Context> context,
141260 Local<Value>* port_list) {
@@ -227,8 +346,14 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
227346 return {};
228347 }
229348
230- host_objects.clear ();
231- return handle_scope.Escape (return_value);
349+ Local<Object> converted_dom_exception;
350+ if (!ConvertDOMExceptionData (context, return_value)
351+ .ToLocal (&converted_dom_exception)) {
352+ host_objects.clear ();
353+ return handle_scope.Escape (return_value);
354+ }
355+
356+ return handle_scope.Escape (converted_dom_exception);
232357}
233358
234359void Message::AddSharedArrayBuffer (
@@ -294,6 +419,36 @@ void ThrowDataCloneException(Local<Context> context, Local<String> message) {
294419 isolate->ThrowException (exception);
295420}
296421
422+ Maybe<bool > IsDOMException (Isolate* isolate,
423+ Local<Context> context,
424+ Local<Object> obj) {
425+ HandleScope handle_scope (isolate);
426+
427+ Local<Object> per_context_bindings;
428+ Local<Value> dom_exception_ctor_val;
429+
430+ if (!GetPerContextExports (context).ToLocal (&per_context_bindings)) {
431+ return Just (false );
432+ }
433+
434+ if (!per_context_bindings
435+ ->Get (context,
436+ FIXED_ONE_BYTE_STRING (isolate, " DOMException" ))
437+ .ToLocal (&dom_exception_ctor_val) ||
438+ !dom_exception_ctor_val->IsFunction ()) {
439+ return Nothing<bool >();
440+ }
441+
442+ Local<Function> dom_exception_ctor = dom_exception_ctor_val.As <Function>();
443+ Local<Value> obj_constructor;
444+ if (!obj->Get (context, FIXED_ONE_BYTE_STRING (isolate, " constructor" ))
445+ .ToLocal (&obj_constructor) ||
446+ !obj_constructor->IsFunction ()) {
447+ return Just (false );
448+ }
449+ return Just (obj_constructor.As <Function>()->StrictEquals (dom_exception_ctor));
450+ }
451+
297452// This tells V8 how to serialize objects that it does not understand
298453// (e.g. C++ objects) into the output buffer, in a way that our own
299454// DeserializerDelegate understands how to unpack.
@@ -313,6 +468,11 @@ class SerializerDelegate : public ValueSerializer::Delegate {
313468 return Just (true );
314469 }
315470
471+ Maybe<bool > is_dom_exception = IsDOMException (isolate, context_, object);
472+ if (!is_dom_exception.IsNothing () && is_dom_exception.FromJust ()) {
473+ return Just (true );
474+ }
475+
316476 return Just (JSTransferable::IsJSTransferable (env_, context_, object));
317477 }
318478
@@ -328,6 +488,11 @@ class SerializerDelegate : public ValueSerializer::Delegate {
328488 return WriteHostObject (js_transferable);
329489 }
330490
491+ Maybe<bool > is_dom_exception = IsDOMException (isolate, context_, object);
492+ if (!is_dom_exception.IsNothing () && is_dom_exception.FromJust ()) {
493+ return WriteDOMException (context_, object);
494+ }
495+
331496 // Convert process.env to a regular object.
332497 auto env_proxy_ctor_template = env_->env_proxy_ctor_template ();
333498 if (!env_proxy_ctor_template.IsEmpty () &&
@@ -424,6 +589,26 @@ class SerializerDelegate : public ValueSerializer::Delegate {
424589 ValueSerializer* serializer = nullptr ;
425590
426591 private:
592+ Maybe<bool > WriteDOMException (Local<Context> context,
593+ Local<Object> exception) {
594+ serializer->WriteUint32 (kDOMExceptionTag );
595+
596+ Local<Value> name_val, message_val, code_val;
597+ if (!exception->Get (context, env_->name_string ()).ToLocal (&name_val) ||
598+ !exception->Get (context, env_->message_string ())
599+ .ToLocal (&message_val) ||
600+ !exception->Get (context, env_->code_string ()).ToLocal (&code_val)) {
601+ return Nothing<bool >();
602+ }
603+
604+ if (serializer->WriteValue (context, name_val).IsNothing () ||
605+ serializer->WriteValue (context, message_val).IsNothing () ||
606+ serializer->WriteValue (context, code_val).IsNothing ()) {
607+ return Nothing<bool >();
608+ }
609+
610+ return Just (true );
611+ }
427612 Maybe<bool > WriteHostObject (BaseObjectPtr<BaseObject> host_object) {
428613 BaseObject::TransferMode mode = host_object->GetTransferMode ();
429614 if (mode == TransferMode::kDisallowCloneAndTransfer ) {
0 commit comments