Skip to content

Commit b3c6f5e

Browse files
bartlomiejuclaude
andcommitted
feat(core): add cloneable resource registry for structured clone support
Adds infrastructure for custom JS objects to support structured cloning via `postMessage`/`MessageChannel`. This enables objects like `CryptoKey` and `X509Certificate` to be cloned across message ports. The mechanism works by: 1. Objects set `[core.hostObjectBrand]` to a serializer function that returns `{ type: "<name>", ...data }` 2. Extensions register a deserializer via `core.registerCloneableResource(name, deserializerFn)` 3. During deserialization, the registry is consulted to reconstruct the original object Changes: - `libs/core/01_core.js`: Add `registerCloneableResource` / `getCloneableDeserializers` registry, auto-pass deserializers in `structuredClone` - `libs/core/ops_builtin_v8.rs`: Mark `op_deserialize` as reentrant so deserializer callbacks can invoke ops - `ext/web/13_message_port.js`: Pass cloneable deserializers during message deserialization Ref: #12067 Ref: #12734 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b31680f commit b3c6f5e

File tree

3 files changed

+18
-2
lines changed

3 files changed

+18
-2
lines changed

ext/web/13_message_port.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,12 @@ function deserializeJsMessageData(messageData) {
409409
};
410410
}
411411

412+
const deserializers = core.getCloneableDeserializers();
413+
if (!options) {
414+
options = { deserializers };
415+
} else {
416+
options.deserializers = deserializers;
417+
}
412418
const data = core.deserialize(messageData.data, options);
413419

414420
for (let i = 0; i < arrayBufferIdsInTransferables.length; ++i) {

libs/core/01_core.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,14 @@
683683
transferableResources[name] = { send, receive };
684684
};
685685
const getTransferableResource = (name) => transferableResources[name];
686+
const cloneableDeserializers = { __proto__: null };
687+
const registerCloneableResource = (name, deserialize) => {
688+
if (cloneableDeserializers[name]) {
689+
throw new Error(`${name} is already registered`);
690+
}
691+
cloneableDeserializers[name] = deserialize;
692+
};
693+
const getCloneableDeserializers = () => cloneableDeserializers;
686694

687695
// A helper function that will bind our own console implementation
688696
// with default implementation of Console from V8. This will cause
@@ -1025,11 +1033,13 @@
10251033
hostObjectBrand,
10261034
registerTransferableResource,
10271035
getTransferableResource,
1036+
registerCloneableResource,
1037+
getCloneableDeserializers,
10281038
encode: (text) => op_encode(text),
10291039
encodeBinaryString: (buffer) => op_encode_binary_string(buffer),
10301040
decode: (buffer) => op_decode(buffer),
10311041
structuredClone: (value, deserializers) =>
1032-
op_structured_clone(value, deserializers),
1042+
op_structured_clone(value, deserializers ?? cloneableDeserializers),
10331043
serialize: (
10341044
value,
10351045
options,

libs/core/ops_builtin_v8.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ pub fn op_serialize<'s, 'i>(
848848
}
849849
}
850850

851-
#[op2]
851+
#[op2(reentrant)]
852852
pub fn op_deserialize<'s, 'i>(
853853
scope: &mut v8::PinScope<'s, 'i>,
854854
#[buffer] zero_copy: JsBuffer,

0 commit comments

Comments
 (0)