Skip to content

feat(core): add cloneable resource registry for structured clone#32672

Merged
bartlomieju merged 2 commits intodenoland:mainfrom
bartlomieju:feat/cloneable-resource-registry
Mar 12, 2026
Merged

feat(core): add cloneable resource registry for structured clone#32672
bartlomieju merged 2 commits intodenoland:mainfrom
bartlomieju:feat/cloneable-resource-registry

Conversation

@bartlomieju
Copy link
Member

@bartlomieju bartlomieju commented Mar 12, 2026

Summary

  • Adds infrastructure for custom JS objects to support structured cloning via postMessage/MessageChannel
  • Objects set [core.hostObjectBrand] to a serializer function returning { type: "<name>", ...data }, then register a deserializer via core.registerCloneableResource(name, fn)
  • Marks op_deserialize as reentrant so deserializer callbacks can invoke ops
  • Passes registered cloneable deserializers during message port deserialization

This enables objects like CryptoKey (#12734) and X509Certificate to be cloned across message ports, addressing #12067.

For reference, here's how this approach compares to Node.js's implementation of structured cloning for custom objects:

Node.js approach

  • Hooks into V8's serializer delegate at the C++ level (src/node_messaging.ccWriteHostObject/ReadHostObject)
  • Objects call markTransferMode(this, cloneable, transferable) which sets a private symbol with bitflags (kCloneable = 0x1, kTransferable = 0x2)
  • During serialization, the delegate calls obj[kClone]() which returns { data, deserializeInfo: "module:ClassName" } (e.g. "internal/crypto/keys:InternalCryptoKey")
  • During deserialization, uses dynamic require() module loading to discover the constructor from the deserializeInfo string — no explicit registry needed
  • Two-phase deserialization: first creates an empty instance via the constructor, then calls obj[kDeserialize](data) to populate it
  • Supports both C++ BaseObjects (via virtual methods) and JS objects (via symbols) in a unified framework

Our approach

  • Hooks into the same V8 mechanism but from the Rust/JS side — [core.hostObjectBrand] symbol triggers write_host_object/read_host_object in ops_builtin_v8.rs (this infrastructure already existed)
  • Objects set [core.hostObjectBrand] to a serializer function that returns { type: "<name>", ...data }
  • Uses an explicit registry (core.registerCloneableResource(name, deserializerFn)) mapping type name → deserializer function
  • Single-phase deserialization: the deserializer function creates and returns the final object directly (simpler, no separate create/populate phases)

Ref: #12067
Ref: #12734

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: denoland#12067
Ref: denoland#12734

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tests cover:
- structuredClone with a cloneable object
- serialize/deserialize round-trip with cloneable object
- cloneable object nested inside a plain object
- duplicate registration throws

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@kajukitli kajukitli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changes. No issues found.

@bartlomieju bartlomieju merged commit 4650330 into denoland:main Mar 12, 2026
221 of 223 checks passed
@bartlomieju bartlomieju deleted the feat/cloneable-resource-registry branch March 12, 2026 21:13
bartlomieju added a commit to bartlomieju/deno that referenced this pull request Mar 12, 2026
Uses the cloneable resource registry from denoland#32672 to enable structured
cloning of CryptoKey objects. This allows CryptoKeys (including
non-extractable ones) to be passed to Workers via postMessage and
cloned with structuredClone().

Closes denoland#12734

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants