@@ -66,6 +66,10 @@ bool Message::IsCloseMessage() const {
6666
6767namespace {
6868
69+ MaybeLocal<Function> GetDOMException (Local<Context> context);
70+
71+ static const uint32_t kDOMExceptionTag = 0xD011 ;
72+
6973// This is used to tell V8 how to read transferred host objects, like other
7074// `MessagePort`s and `SharedArrayBuffer`s, and make new JS objects out of them.
7175class DeserializerDelegate : public ValueDeserializer ::Delegate {
@@ -83,11 +87,66 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
8387 wasm_modules_(wasm_modules),
8488 shared_value_conveyor_(shared_value_conveyor) {}
8589
90+ MaybeLocal<Object> ReadDOMException (Isolate* isolate,
91+ Local<Context> context,
92+ v8::ValueDeserializer* deserializer) {
93+ Local<Value> name, message;
94+ if (!deserializer->ReadValue (context).ToLocal (&name) ||
95+ !deserializer->ReadValue (context).ToLocal (&message)) {
96+ return MaybeLocal<Object>();
97+ }
98+
99+ bool has_code = false ;
100+ Local<Value> code;
101+ has_code = deserializer->ReadValue (context).ToLocal (&code);
102+
103+ // V8 disallows executing JS code in the deserialization process, so we
104+ // cannot create a DOMException object directly. Instead, we create a
105+ // placeholder object that will be converted to a DOMException object
106+ // later on.
107+ Local<Object> placeholder = Object::New (isolate);
108+ if (placeholder
109+ ->Set (context,
110+ String::NewFromUtf8 (isolate, " __domexception_name" )
111+ .ToLocalChecked (),
112+ name)
113+ .IsNothing () ||
114+ placeholder
115+ ->Set (context,
116+ String::NewFromUtf8 (isolate, " __domexception_message" )
117+ .ToLocalChecked (),
118+ message)
119+ .IsNothing () ||
120+ (has_code &&
121+ placeholder
122+ ->Set (context,
123+ String::NewFromUtf8 (isolate, " __domexception_code" )
124+ .ToLocalChecked (),
125+ code)
126+ .IsNothing ()) ||
127+ placeholder
128+ ->Set (context,
129+ String::NewFromUtf8 (isolate, " __domexception_placeholder" )
130+ .ToLocalChecked (),
131+ v8::True (isolate))
132+ .IsNothing ()) {
133+ return MaybeLocal<Object>();
134+ }
135+
136+ return placeholder;
137+ }
138+
86139 MaybeLocal<Object> ReadHostObject (Isolate* isolate) override {
87140 // Identifying the index in the message's BaseObject array is sufficient.
88141 uint32_t id;
89142 if (!deserializer->ReadUint32 (&id))
90143 return MaybeLocal<Object>();
144+
145+ Local<Context> context = isolate->GetCurrentContext ();
146+ if (id == kDOMExceptionTag ) {
147+ return ReadDOMException (isolate, context, deserializer);
148+ }
149+
91150 if (id != kNormalObject ) {
92151 CHECK_LT (id, host_objects_.size ());
93152 Local<Object> object = host_objects_[id]->object (isolate);
@@ -98,7 +157,6 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
98157 }
99158 }
100159 EscapableHandleScope scope (isolate);
101- Local<Context> context = isolate->GetCurrentContext ();
102160 Local<Value> object;
103161 if (!deserializer->ReadValue (context).ToLocal (&object))
104162 return MaybeLocal<Object>();
@@ -136,6 +194,71 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
136194
137195} // anonymous namespace
138196
197+ MaybeLocal<Object> ConvertDOMExceptionData (Local<Context> context,
198+ Local<Value> value) {
199+ if (!value->IsObject ()) return MaybeLocal<Object>();
200+
201+ Isolate* isolate = context->GetIsolate ();
202+ Local<Object> obj = value.As <Object>();
203+
204+ Local<String> marker_key =
205+ String::NewFromUtf8 (isolate, " __domexception_placeholder" )
206+ .ToLocalChecked ();
207+ Local<Value> marker_val;
208+ if (!obj->Get (context, marker_key).ToLocal (&marker_val) ||
209+ !marker_val->IsTrue ()) {
210+ return MaybeLocal<Object>();
211+ }
212+
213+ Local<String> name_key =
214+ String::NewFromUtf8 (isolate, " __domexception_name" ).ToLocalChecked ();
215+ Local<String> message_key =
216+ String::NewFromUtf8 (isolate, " __domexception_message" ).ToLocalChecked ();
217+ Local<String> code_key =
218+ String::NewFromUtf8 (isolate, " __domexception_code" ).ToLocalChecked ();
219+
220+ Local<Value> name, message, code;
221+ if (!obj->Get (context, name_key).ToLocal (&name) ||
222+ !obj->Get (context, message_key).ToLocal (&message)) {
223+ return MaybeLocal<Object>();
224+ }
225+ bool has_code = obj->Get (context, code_key).ToLocal (&code);
226+
227+ Local<Function> dom_exception_ctor;
228+ if (!GetDOMException (context).ToLocal (&dom_exception_ctor)) {
229+ return MaybeLocal<Object>();
230+ }
231+
232+ // Create arguments for the constructor according to the JS implementation
233+ // First arg: message
234+ // Second arg: options object with name and potentially code
235+ Local<Object> options = Object::New (isolate);
236+ if (options
237+ ->Set (context,
238+ String::NewFromUtf8 (isolate, " name" ).ToLocalChecked (),
239+ name)
240+ .IsNothing ()) {
241+ return MaybeLocal<Object>();
242+ }
243+
244+ if (has_code &&
245+ options
246+ ->Set (context,
247+ String::NewFromUtf8 (isolate, " code" ).ToLocalChecked (),
248+ code)
249+ .IsNothing ()) {
250+ return MaybeLocal<Object>();
251+ }
252+
253+ Local<Value> argv[2 ] = {message, options};
254+ Local<Value> exception;
255+ if (!dom_exception_ctor->NewInstance (context, 2 , argv).ToLocal (&exception)) {
256+ return MaybeLocal<Object>();
257+ }
258+
259+ return exception.As <Object>();
260+ }
261+
139262MaybeLocal<Value> Message::Deserialize (Environment* env,
140263 Local<Context> context,
141264 Local<Value>* port_list) {
@@ -227,8 +350,14 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
227350 return {};
228351 }
229352
230- host_objects.clear ();
231- return handle_scope.Escape (return_value);
353+ Local<Object> converted_dom_exception;
354+ if (!ConvertDOMExceptionData (context, return_value)
355+ .ToLocal (&converted_dom_exception)) {
356+ host_objects.clear ();
357+ return handle_scope.Escape (return_value);
358+ }
359+
360+ return handle_scope.Escape (converted_dom_exception);
232361}
233362
234363void Message::AddSharedArrayBuffer (
@@ -294,6 +423,39 @@ void ThrowDataCloneException(Local<Context> context, Local<String> message) {
294423 isolate->ThrowException (exception);
295424}
296425
426+ Maybe<bool > IsDOMException (v8::Isolate* isolate,
427+ v8::Local<v8::Context> context,
428+ v8::Local<v8::Object> obj) {
429+ v8::HandleScope handle_scope (isolate);
430+
431+ v8::Local<v8::Object> per_context_bindings;
432+ v8::Local<v8::Value> dom_exception_ctor_val;
433+
434+ if (!GetPerContextExports (context).ToLocal (&per_context_bindings)) {
435+ return Nothing<bool >();
436+ }
437+
438+ if (!per_context_bindings
439+ ->Get (context,
440+ v8::String::NewFromUtf8 (isolate, " DOMException" )
441+ .ToLocalChecked ())
442+ .ToLocal (&dom_exception_ctor_val) ||
443+ !dom_exception_ctor_val->IsFunction ()) {
444+ return Nothing<bool >();
445+ }
446+
447+ v8::Local<v8::Function> dom_exception_ctor =
448+ dom_exception_ctor_val.As <v8::Function>();
449+
450+ v8::Maybe<bool > result = obj->InstanceOf (context, dom_exception_ctor);
451+
452+ if (result.IsNothing ()) {
453+ return Nothing<bool >();
454+ }
455+
456+ return Just (result.FromJust ());
457+ }
458+
297459// This tells V8 how to serialize objects that it does not understand
298460// (e.g. C++ objects) into the output buffer, in a way that our own
299461// DeserializerDelegate understands how to unpack.
@@ -313,6 +475,10 @@ class SerializerDelegate : public ValueSerializer::Delegate {
313475 return Just (true );
314476 }
315477
478+ if (IsDOMException (isolate, context_, object).FromJust ()) {
479+ return Just (true );
480+ }
481+
316482 return Just (JSTransferable::IsJSTransferable (env_, context_, object));
317483 }
318484
@@ -328,6 +494,10 @@ class SerializerDelegate : public ValueSerializer::Delegate {
328494 return WriteHostObject (js_transferable);
329495 }
330496
497+ if (IsDOMException (isolate, context_, object).FromJust ()) {
498+ return WriteDOMException (context_, object);
499+ }
500+
331501 // Convert process.env to a regular object.
332502 auto env_proxy_ctor_template = env_->env_proxy_ctor_template ();
333503 if (!env_proxy_ctor_template.IsEmpty () &&
@@ -424,6 +594,26 @@ class SerializerDelegate : public ValueSerializer::Delegate {
424594 ValueSerializer* serializer = nullptr ;
425595
426596 private:
597+ Maybe<bool > WriteDOMException (Local<Context> context,
598+ Local<Object> exception) {
599+ serializer->WriteUint32 (kDOMExceptionTag );
600+
601+ Local<Value> name_val, message_val, code_val;
602+ if (!exception->Get (context, env_->name_string ()).ToLocal (&name_val) ||
603+ !exception->Get (context, env_->message_string ())
604+ .ToLocal (&message_val) ||
605+ !exception->Get (context, env_->code_string ()).ToLocal (&code_val)) {
606+ return Nothing<bool >();
607+ }
608+
609+ if (serializer->WriteValue (context, name_val).IsNothing () ||
610+ serializer->WriteValue (context, message_val).IsNothing () ||
611+ serializer->WriteValue (context, code_val).IsNothing ()) {
612+ return Nothing<bool >();
613+ }
614+
615+ return Just (true );
616+ }
427617 Maybe<bool > WriteHostObject (BaseObjectPtr<BaseObject> host_object) {
428618 BaseObject::TransferMode mode = host_object->GetTransferMode ();
429619 if (mode == TransferMode::kDisallowCloneAndTransfer ) {
0 commit comments