From 4f8a6b62b8dd281e82b76ecb10b0969b291f6d17 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 12 Mar 2026 14:00:55 +0800 Subject: [PATCH] Add window.structuredClone Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/156 Uses V8::Serializer and V8::Deserializer which handles built-in types, e.g. regex. But it doesn't handle Zig types by default. This is something we need to hook in, using the delegate callbacks. Which we can do after. Meant to replace https://github.com/lightpanda-io/browser/pull/1785 --- src/browser/js/Value.zig | 40 ++++++++ src/browser/tests/window/window.html | 137 +++++++++++++++++++++++++++ src/browser/webapi/Window.zig | 5 + 3 files changed, 182 insertions(+) diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 309bdb6b..8e05690b 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -245,6 +245,46 @@ pub fn toJson(self: Value, allocator: Allocator) ![]u8 { return js.String.toSliceWithAlloc(.{ .local = local, .handle = str_handle }, allocator); } +// Currently does not support host objects (Blob, File, etc.) or transferables +// which require delegate callbacks to be implemented. +pub fn structuredClone(self: Value) !Value { + const local = self.local; + const v8_context = local.handle; + const v8_isolate = local.isolate.handle; + + const size, const data = blk: { + const serializer = v8.v8__ValueSerializer__New(v8_isolate, null) orelse return error.JsException; + defer v8.v8__ValueSerializer__DELETE(serializer); + + var write_result: v8.MaybeBool = undefined; + v8.v8__ValueSerializer__WriteHeader(serializer); + v8.v8__ValueSerializer__WriteValue(serializer, v8_context, self.handle, &write_result); + if (!write_result.has_value or !write_result.value) { + return error.JsException; + } + + var size: usize = undefined; + const data = v8.v8__ValueSerializer__Release(serializer, &size) orelse return error.JsException; + break :blk .{ size, data }; + }; + + defer v8.v8__ValueSerializer__FreeBuffer(data); + + const cloned_handle = blk: { + const deserializer = v8.v8__ValueDeserializer__New(v8_isolate, data, size, null) orelse return error.JsException; + defer v8.v8__ValueDeserializer__DELETE(deserializer); + + var read_header_result: v8.MaybeBool = undefined; + v8.v8__ValueDeserializer__ReadHeader(deserializer, v8_context, &read_header_result); + if (!read_header_result.has_value or !read_header_result.value) { + return error.JsException; + } + break :blk v8.v8__ValueDeserializer__ReadValue(deserializer, v8_context) orelse return error.JsException; + }; + + return .{ .local = local, .handle = cloned_handle }; +} + pub fn persist(self: Value) !Global { return self._persist(true); } diff --git a/src/browser/tests/window/window.html b/src/browser/tests/window/window.html index 5506e327..01025b86 100644 --- a/src/browser/tests/window/window.html +++ b/src/browser/tests/window/window.html @@ -125,6 +125,143 @@ testing.expectEqual(screen, window.screen); + +