Skip to content

Commit b29491f

Browse files
committed
lib,src: preserve domexception in v8 serialize/deserialize
1 parent 4d867af commit b29491f

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

lib/v8.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ const {
4747
Serializer,
4848
Deserializer,
4949
} = internalBinding('serdes');
50+
const {
51+
DOMException,
52+
} = internalBinding('messaging');
53+
const {
54+
messaging_clone_symbol,
55+
messaging_deserialize_symbol,
56+
} = internalBinding('symbols');
5057
const {
5158
namespace: startupSnapshot,
5259
} = require('internal/v8/startup_snapshot');
@@ -381,6 +388,8 @@ function arrayBufferViewIndexToType(index) {
381388
return undefined;
382389
}
383390

391+
const kDOMExceptionHostObjectTag = 14;
392+
384393
class DefaultSerializer extends Serializer {
385394
constructor() {
386395
super();
@@ -395,6 +404,14 @@ class DefaultSerializer extends Serializer {
395404
* @returns {void}
396405
*/
397406
_writeHostObject(abView) {
407+
// Route DOMException through the same hooks used by structuredClone
408+
if (abView instanceof DOMException) {
409+
const { data } = abView[messaging_clone_symbol]();
410+
this.writeUint32(kDOMExceptionHostObjectTag);
411+
this.writeValue(data);
412+
return;
413+
}
414+
398415
// Keep track of how to handle different ArrayBufferViews. The default
399416
// Serializer for Node does not use the V8 methods for serializing those
400417
// objects because Node's `Buffer` objects use pooled allocation in many
@@ -426,6 +443,14 @@ class DefaultDeserializer extends Deserializer {
426443
*/
427444
_readHostObject() {
428445
const typeIndex = this.readUint32();
446+
// Route DOMException through the same hooks used by structuredClone
447+
if (typeIndex === kDOMExceptionHostObjectTag) {
448+
const data = this.readValue();
449+
const domException = new DOMException();
450+
domException[messaging_deserialize_symbol](data);
451+
return domException;
452+
}
453+
429454
const ctor = arrayBufferViewIndexToType(typeIndex);
430455
const byteLength = this.readUint32();
431456
const byteOffset = this._readRawBytes(byteLength);

src/node_serdes.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class SerializerContext : public BaseObject,
3737

3838
~SerializerContext() override = default;
3939

40+
bool HasCustomHostObject(Isolate* isolate) override { return true; }
41+
Maybe<bool> IsHostObject(Isolate* isolate, Local<Object> object) override;
4042
void ThrowDataCloneError(Local<String> message) override;
4143
Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override;
4244
Maybe<uint32_t> GetSharedArrayBufferId(
@@ -145,6 +147,27 @@ Maybe<uint32_t> SerializerContext::GetSharedArrayBufferId(
145147
return id->Uint32Value(env()->context());
146148
}
147149

150+
Maybe<bool> SerializerContext::IsHostObject(Isolate* isolate,
151+
Local<Object> object) {
152+
Local<Object> per_context_bindings;
153+
Local<Value> domexception_ctor_val;
154+
if (!GetPerContextExports(env()->context()).ToLocal(&per_context_bindings) ||
155+
!per_context_bindings
156+
->Get(env()->context(),
157+
FIXED_ONE_BYTE_STRING(env()->isolate(), "DOMException"))
158+
.ToLocal(&domexception_ctor_val) ||
159+
!domexception_ctor_val->IsFunction()) {
160+
return Just(object->InternalFieldCount() != 0);
161+
}
162+
163+
bool is_domexception = false;
164+
if (!object->InstanceOf(env()->context(), domexception_ctor_val.As<Function>())
165+
.To(&is_domexception)) {
166+
return Nothing<bool>();
167+
}
168+
return Just(is_domexception || object->InternalFieldCount() != 0);
169+
}
170+
148171
Maybe<bool> SerializerContext::WriteHostObject(Isolate* isolate,
149172
Local<Object> input) {
150173
Local<Value> args[1] = { input };
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
const assert = require('node:assert');
4+
const { serialize, deserialize } = require('node:v8');
5+
6+
// Simple case, should preserve message/name/stack like structuredClone
7+
{
8+
const e = AbortSignal.abort().reason;
9+
10+
const cloned = deserialize(serialize(e));
11+
12+
assert(cloned instanceof DOMException);
13+
assert.strictEqual(cloned.name, e.name);
14+
assert.strictEqual(cloned.message, e.message);
15+
16+
assert.strictEqual(typeof cloned.stack, 'string');
17+
assert(cloned.stack.includes(e.name));
18+
}
19+
20+
// Nested case
21+
{
22+
const e = AbortSignal.abort().reason;
23+
const obj = { e };
24+
25+
const clonedObj = deserialize(serialize(obj));
26+
assert(clonedObj.e instanceof DOMException);
27+
assert.strictEqual(clonedObj.e.name, e.name);
28+
assert.strictEqual(clonedObj.e.message, e.message);
29+
}

0 commit comments

Comments
 (0)