@@ -18,6 +18,7 @@ using v8::BackingStore;
1818using v8::CompiledWasmModule;
1919using v8::Context;
2020using v8::EscapableHandleScope;
21+ using v8::Exception;
2122using v8::Function;
2223using v8::FunctionCallbackInfo;
2324using v8::FunctionTemplate;
@@ -67,6 +68,10 @@ bool Message::IsCloseMessage() const {
6768
6869namespace {
6970
71+ MaybeLocal<Function> GetDOMException (Local<Context> context);
72+
73+ static const uint32_t kDOMExceptionTag = 0xD011 ;
74+
7075// This is used to tell V8 how to read transferred host objects, like other
7176// `MessagePort`s and `SharedArrayBuffer`s, and make new JS objects out of them.
7277class DeserializerDelegate : public ValueDeserializer ::Delegate {
@@ -84,12 +89,63 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
8489 wasm_modules_(wasm_modules),
8590 shared_value_conveyor_(shared_value_conveyor) {}
8691
92+ MaybeLocal<Object> ReadDOMException (Isolate* isolate,
93+ Local<Context> context,
94+ v8::ValueDeserializer* deserializer) {
95+ Local<Value> name, message, stack;
96+ if (!deserializer->ReadValue (context).ToLocal (&name) ||
97+ !deserializer->ReadValue (context).ToLocal (&message) ||
98+ !deserializer->ReadValue (context).ToLocal (&stack)) {
99+ return MaybeLocal<Object>();
100+ }
101+
102+ bool has_code = false ;
103+ Local<Value> code;
104+ has_code = deserializer->ReadValue (context).ToLocal (&code);
105+
106+ // V8 disallows executing JS code in the deserialization process, so we
107+ // cannot create a DOMException object directly. Instead, we create a
108+ // placeholder object that will be converted to a DOMException object
109+ // later on.
110+ Local<Object> placeholder = Object::New (isolate);
111+ if (placeholder
112+ ->Set (context,
113+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_name" ),
114+ name)
115+ .IsNothing () ||
116+ placeholder
117+ ->Set (context,
118+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_message" ),
119+ message)
120+ .IsNothing () ||
121+ (has_code &&
122+ placeholder
123+ ->Set (context,
124+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_code" ),
125+ code)
126+ .IsNothing ()) ||
127+ placeholder
128+ ->Set (context,
129+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_stack" ),
130+ stack)
131+ .IsNothing () ||
132+ placeholder
133+ ->Set (context,
134+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_placeholder" ),
135+ v8::True (isolate))
136+ .IsNothing ()) {
137+ return MaybeLocal<Object>();
138+ }
139+
140+ return placeholder;
141+ }
142+
87143 MaybeLocal<Object> ReadHostObject (Isolate* isolate) override {
88144 // Identifying the index in the message's BaseObject array is sufficient.
89145 uint32_t id;
90146 if (!deserializer->ReadUint32 (&id))
91147 return MaybeLocal<Object>();
92- if (id != kNormalObject ) {
148+ if (id != kNormalObject && id != kDOMExceptionTag ) {
93149 CHECK_LT (id, host_objects_.size ());
94150 Local<Object> object = host_objects_[id]->object (isolate);
95151 if (env_->js_transferable_constructor_template ()->HasInstance (object)) {
@@ -100,6 +156,9 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
100156 }
101157 EscapableHandleScope scope (isolate);
102158 Local<Context> context = isolate->GetCurrentContext ();
159+ if (id == kDOMExceptionTag ) {
160+ return ReadDOMException (isolate, context, deserializer);
161+ }
103162 Local<Value> object;
104163 if (!deserializer->ReadValue (context).ToLocal (&object))
105164 return MaybeLocal<Object>();
@@ -137,6 +196,72 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
137196
138197} // anonymous namespace
139198
199+ MaybeLocal<Object> ConvertDOMExceptionData (Environment* env,
200+ Local<Value> dom_exception) {
201+ if (!dom_exception->IsObject ()) return MaybeLocal<Object>();
202+ Local<Object> dom_exception_obj = dom_exception.As <Object>();
203+ Local<Context> context = env->context ();
204+ Isolate* isolate = context->GetIsolate ();
205+
206+ Local<String> marker_key =
207+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_placeholder" );
208+ Local<Value> marker_val;
209+ if (!dom_exception_obj->Get (context, marker_key).ToLocal (&marker_val) ||
210+ !marker_val->IsTrue ()) {
211+ return MaybeLocal<Object>();
212+ }
213+
214+ Local<String> name_key =
215+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_name" );
216+ Local<String> message_key =
217+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_message" );
218+ Local<String> code_key =
219+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_code" );
220+ Local<String> stack_key =
221+ FIXED_ONE_BYTE_STRING (isolate, " __domexception_stack" );
222+
223+ Local<Value> name, message, code, stack;
224+ if (!dom_exception_obj->Get (context, name_key).ToLocal (&name) ||
225+ !dom_exception_obj->Get (context, message_key).ToLocal (&message) ||
226+ !dom_exception_obj->Get (context, stack_key).ToLocal (&stack)) {
227+ return MaybeLocal<Object>();
228+ }
229+ bool has_code = dom_exception_obj->Get (context, code_key).ToLocal (&code);
230+ Local<Function> dom_exception_ctor;
231+ if (!GetDOMException (context).ToLocal (&dom_exception_ctor)) {
232+ return MaybeLocal<Object>();
233+ }
234+
235+ // Create arguments for the constructor according to the JS implementation
236+ // First arg: message
237+ // Second arg: options object with name and potentially code
238+ Local<Object> options = Object::New (isolate);
239+ if (options
240+ ->Set (context,
241+ FIXED_ONE_BYTE_STRING (isolate, " name" ),
242+ name)
243+ .IsNothing ()) {
244+ return MaybeLocal<Object>();
245+ }
246+
247+ if (has_code &&
248+ options
249+ ->Set (context,
250+ FIXED_ONE_BYTE_STRING (isolate, " code" ),
251+ code)
252+ .IsNothing ()) {
253+ return MaybeLocal<Object>();
254+ }
255+
256+ Local<Value> argv[2 ] = {message, options};
257+ Local<Object> final_dom_exception;
258+ if (!dom_exception_ctor->NewInstance (context, 2 , argv).ToLocal (&final_dom_exception) ||
259+ !final_dom_exception->Set (context, env->stack_string (), stack).IsJust ()) {
260+ return MaybeLocal<Object>();
261+ }
262+ return final_dom_exception;
263+ }
264+
140265MaybeLocal<Value> Message::Deserialize (Environment* env,
141266 Local<Context> context,
142267 Local<Value>* port_list) {
@@ -228,6 +353,12 @@ MaybeLocal<Value> Message::Deserialize(Environment* env,
228353 return {};
229354 }
230355
356+ Local<Object> converted_dom_exception;
357+ if (ConvertDOMExceptionData (env, return_value)
358+ .ToLocal (&converted_dom_exception)) {
359+ return handle_scope.Escape (converted_dom_exception);
360+ }
361+
231362 host_objects.clear ();
232363 return handle_scope.Escape (return_value);
233364}
@@ -297,6 +428,11 @@ void ThrowDataCloneException(Local<Context> context, Local<String> message) {
297428 isolate->ThrowException (exception);
298429}
299430
431+ Maybe<bool > IsDOMException (Environment* env,
432+ Local<Object> obj) {
433+ return obj->HasPrivate (env->context (), env->is_dom_exception ());
434+ }
435+
300436// This tells V8 how to serialize objects that it does not understand
301437// (e.g. C++ objects) into the output buffer, in a way that our own
302438// DeserializerDelegate understands how to unpack.
@@ -316,6 +452,11 @@ class SerializerDelegate : public ValueSerializer::Delegate {
316452 return Just (true );
317453 }
318454
455+ Maybe<bool > is_dom_exception = IsDOMException (env_, object);
456+ if (!is_dom_exception.IsNothing () && is_dom_exception.FromJust ()) {
457+ return Just (true );
458+ }
459+
319460 return Just (JSTransferable::IsJSTransferable (env_, context_, object));
320461 }
321462
@@ -331,6 +472,11 @@ class SerializerDelegate : public ValueSerializer::Delegate {
331472 return WriteHostObject (js_transferable);
332473 }
333474
475+ Maybe<bool > is_dom_exception = IsDOMException (env_, object);
476+ if (!is_dom_exception.IsNothing () && is_dom_exception.FromJust ()) {
477+ return WriteDOMException (context_, object);
478+ }
479+
334480 // Convert process.env to a regular object.
335481 auto env_proxy_ctor_template = env_->env_proxy_ctor_template ();
336482 if (!env_proxy_ctor_template.IsEmpty () &&
@@ -427,6 +573,28 @@ class SerializerDelegate : public ValueSerializer::Delegate {
427573 ValueSerializer* serializer = nullptr ;
428574
429575 private:
576+ Maybe<bool > WriteDOMException (Local<Context> context,
577+ Local<Object> exception) {
578+ serializer->WriteUint32 (kDOMExceptionTag );
579+
580+ Local<Value> name_val, message_val, code_val, stack_val;
581+ if (!exception->Get (context, env_->name_string ()).ToLocal (&name_val) ||
582+ !exception->Get (context, env_->message_string ())
583+ .ToLocal (&message_val) ||
584+ !exception->Get (context, env_->stack_string ()).ToLocal (&stack_val) ||
585+ !exception->Get (context, env_->code_string ()).ToLocal (&code_val)) {
586+ return Nothing<bool >();
587+ }
588+
589+ if (serializer->WriteValue (context, name_val).IsNothing () ||
590+ serializer->WriteValue (context, message_val).IsNothing () ||
591+ serializer->WriteValue (context, code_val).IsNothing () ||
592+ serializer->WriteValue (context, stack_val).IsNothing ()) {
593+ return Nothing<bool >();
594+ }
595+
596+ return Just (true );
597+ }
430598 Maybe<bool > WriteHostObject (BaseObjectPtr<BaseObject> host_object) {
431599 BaseObject::TransferMode mode = host_object->GetTransferMode ();
432600 if (mode == TransferMode::kDisallowCloneAndTransfer ) {
0 commit comments