From c3c465347d5443f3cfd640a015daa67d39adcf15 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Tue, 31 Mar 2026 16:13:55 +0800 Subject: [PATCH] Provide a failing callback to ValueSerializer for host objects V8 needs our help when serializing host (e.g. a Zig dom instance) objects. We don't currently have this implemented, so this provides the callback that throws an error. Our wrapper returns Nothing when no callback is provided, which v8 doesn't allow (via assertion). --- src/browser/js/Value.zig | 28 +++++++++++++++++++++++++--- src/browser/tests/window/window.html | 23 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index af5dd031..1491bcdd 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -245,15 +245,37 @@ 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. +// Throws a DataCloneError for host objects (Blob, File, etc.) that cannot be serialized. +// Does not support transferables which require additional delegate callbacks. pub fn structuredClone(self: Value) !Value { const local = self.local; const v8_context = local.handle; const v8_isolate = local.isolate.handle; + const SerializerDelegate = struct { + // Called when V8 encounters a host object it doesn't know how to serialize. + // Returns false to indicate the object cannot be cloned, and throws a DataCloneError. + // V8 asserts has_exception() after this returns false, so we must throw here. + fn writeHostObject(_: ?*anyopaque, isolate: ?*v8.Isolate, _: ?*const v8.Object) callconv(.c) v8.MaybeBool { + const iso = isolate orelse return .{ .has_value = true, .value = false }; + const message = v8.v8__String__NewFromUtf8(iso, "The object cannot be cloned.", v8.kNormal, -1); + const error_value = v8.v8__Exception__Error(message) orelse return .{ .has_value = true, .value = false }; + _ = v8.v8__Isolate__ThrowException(iso, error_value); + return .{ .has_value = true, .value = false }; + } + + // Called by V8 to report serialization errors. The exception should already be thrown. + fn throwDataCloneError(_: ?*anyopaque, _: ?*const v8.String) callconv(.c) void {} + }; + const size, const data = blk: { - const serializer = v8.v8__ValueSerializer__New(v8_isolate, null) orelse return error.JsException; + const serializer = v8.v8__ValueSerializer__New(v8_isolate, &.{ + .data = null, + .get_shared_array_buffer_id = null, + .write_host_object = SerializerDelegate.writeHostObject, + .throw_data_clone_error = SerializerDelegate.throwDataCloneError, + }) orelse return error.JsException; + defer v8.v8__ValueSerializer__DELETE(serializer); var write_result: v8.MaybeBool = undefined; diff --git a/src/browser/tests/window/window.html b/src/browser/tests/window/window.html index 9623f1ef..e9e2e621 100644 --- a/src/browser/tests/window/window.html +++ b/src/browser/tests/window/window.html @@ -1,5 +1,6 @@ +