* fix(js): structuredClone cycles through Error.cause and clones own __proto__ (#419, #420)
The structuredClone polyfill had two correctness/parity gaps in _structuredClone:
- The Error branch recursed into `cause` before recording the clone in `seen`,
so a cycle through the error (e.cause === e) recursed until the stack
overflowed. Chrome clones such graphs and preserves identity. Fix: seen.set
the clone before recursing into cause. (#419)
- The generic-object branch built the clone on the source's prototype and
copied properties by assignment, so an own enumerable `__proto__` data
property (exactly what JSON.parse('{"__proto__":…}') produces) went through
the inherited __proto__ setter and reparented the clone instead of copying
the property. Fix: clone plain objects onto Object.prototype (like Chrome)
and define each property instead of assigning it. (#420)
Adds two parity tests to structured_clone_crypto_parity.rs; full obscura-cdp
suite (73) green.
* perf(js): keep structuredClone fast path for all keys but __proto__
The own-__proto__ fix copied every own property with Object.defineProperty
to avoid triggering the inherited prototype setter. That is ~3x slower
than plain assignment per key, so cloning a large object regressed
(~75ms -> ~228ms for a 1000-key object x200).
Only the __proto__ key needs defineProperty; every other key is safe to
assign. Special-case it so the fast path applies to all other keys.
Measured back to parity with main (85ms vs 85ms); the own-__proto__
correctness test still passes.
---------
Co-authored-by: SGavrl <[email protected]>
_structuredClone dispatches platform-object hooks as hook(value, seen), but the
CryptoKey hook took only `src` and never touched the memo, and hook branches
return before the generic seen.set. So one CryptoKey referenced twice in a graph
cloned into two distinct objects (and re-registered its key material each time),
where structuredClone preserves reference identity within a graph.
Consult and populate `seen` in the hook. Adds a parity test asserting a key
reached twice clones to one shared object that crypto.subtle still accepts.
* fix(js): real structuredClone so CryptoKey and buffers survive cloning
structuredClone was JSON.parse(JSON.stringify), which silently drops
ArrayBuffer and TypedArray to {}. A cloned CryptoKey also lost its WeakMap
entry, so crypto.subtle.sign/encrypt/deriveBits threw "Argument is not a
valid CryptoKey" on the clone.
Replace the JSON fallback with a recursive cloner covering ArrayBuffer,
TypedArray, DataView, Map, Set, Date, RegExp, Error, and plain objects,
with cycle and own symbol-keyed property handling. CryptoKey opts in via a
clone hook (keyed by Symbol.toStringTag) that rebuilds the key through
makeKey, so the clone re-enters the WeakMap and stays usable.
Fixes#389.
* fix(js): structuredClone handles DataView and rejects functions and symbols
The TypedArray branch called value.slice(), which DataView lacks, so
cloning any DataView threw TypeError, a new crash in the buffers
category this feature targets. Copy the underlying buffer slice over
the view range and wrap a fresh DataView.
The function/symbol rejection lived after the primitive early-return
and was dead code, so functions and symbols were passed through by
reference instead of throwing DataCloneError (HTML structured clone).
Check first and throw.
Register ArrayBuffer, TypedArray, and DataView in the seen map so
repeated references share one clone, and move the cycle check up.
---------
Co-authored-by: SGavrl <[email protected]>