Add structuredClone() global function

Next.js hydration requires structuredClone(), which was not implemented.
Uses a JSON stringify/parse round-trip via V8 to deep-clone values,
covering all JSON-serializable types (objects, arrays, primitives).
This commit is contained in:
Pierre Tachoire
2026-03-11 17:59:46 +01:00
parent 7d835ef99d
commit 6747182945
2 changed files with 37 additions and 0 deletions

View File

@@ -115,6 +115,30 @@
} }
</script> </script>
<script id=structuredClone>
// Basic types
testing.expectEqual(42, structuredClone(42));
testing.expectEqual('hello', structuredClone('hello'));
testing.expectEqual(true, structuredClone(true));
testing.expectEqual(null, structuredClone(null));
// Object deep clone
const obj = { a: 1, b: { c: 2 } };
const cloned = structuredClone(obj);
testing.expectEqual(1, cloned.a);
testing.expectEqual(2, cloned.b.c);
cloned.b.c = 99;
testing.expectEqual(2, obj.b.c); // original unchanged
// Array deep clone
const arr = [1, [2, 3]];
const clonedArr = structuredClone(arr);
testing.expectEqual(1, clonedArr[0]);
testing.expectEqual(2, clonedArr[1][0]);
clonedArr[1][0] = 99;
testing.expectEqual(2, arr[1][0]); // original unchanged
</script>
<script id=screen> <script id=screen>
testing.expectEqual(1920, screen.width); testing.expectEqual(1920, screen.width);
testing.expectEqual(1080, screen.height); testing.expectEqual(1080, screen.height);

View File

@@ -412,6 +412,18 @@ pub fn atob(_: *const Window, input: []const u8, page: *Page) ![]const u8 {
return decoded; return decoded;
} }
pub fn structuredClone(_: *const Window, value: js.Value) !js.Value {
// Simplified structured clone using JSON round-trip.
// Handles JSON-serializable types (objects, arrays, strings, numbers, booleans, null).
const local = value.local;
const str_handle = js.v8.v8__JSON__Stringify(local.handle, value.handle, null) orelse return error.DataCloneError;
const cloned_handle = js.v8.v8__JSON__Parse(local.handle, str_handle) orelse return error.DataCloneError;
return js.Value{
.local = local,
.handle = cloned_handle,
};
}
pub fn getFrame(self: *Window, idx: usize) !?*Window { pub fn getFrame(self: *Window, idx: usize) !?*Window {
const page = self._page; const page = self._page;
const frames = page.frames.items; const frames = page.frames.items;
@@ -797,6 +809,7 @@ pub const JsApi = struct {
pub const btoa = bridge.function(Window.btoa, .{}); pub const btoa = bridge.function(Window.btoa, .{});
pub const atob = bridge.function(Window.atob, .{ .dom_exception = true }); pub const atob = bridge.function(Window.atob, .{ .dom_exception = true });
pub const reportError = bridge.function(Window.reportError, .{}); pub const reportError = bridge.function(Window.reportError, .{});
pub const structuredClone = bridge.function(Window.structuredClone, .{});
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{}); pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
pub const getSelection = bridge.function(Window.getSelection, .{}); pub const getSelection = bridge.function(Window.getSelection, .{});