From 8f2921f61f7552b3ed0f62b12131b443e4c93421 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 25 Dec 2025 12:32:06 +0100 Subject: [PATCH 1/3] add test for big json number with fetch/xhr --- src/browser/tests/net/fetch.html | 4 +++- src/browser/tests/net/xhr.html | 3 +++ src/testing.zig | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/browser/tests/net/fetch.html b/src/browser/tests/net/fetch.html index a8159959..2d8c6266 100644 --- a/src/browser/tests/net/fetch.html +++ b/src/browser/tests/net/fetch.html @@ -35,7 +35,9 @@ const json = await response.json(); testing.expectEqual('9000!!!', json.over); - + testing.expectEqual("number", typeof json.updated_at); + testing.expectEqual(1765867200000, json.updated_at); + testing.expectEqual({over: '9000!!!',updated_at:1765867200000}, json); }); diff --git a/src/browser/tests/net/xhr.html b/src/browser/tests/net/xhr.html index 82e9b6d1..dbb55653 100644 --- a/src/browser/tests/net/xhr.html +++ b/src/browser/tests/net/xhr.html @@ -75,6 +75,9 @@ testing.expectEqual(200, req3.status); testing.expectEqual('OK', req3.statusText); testing.expectEqual('9000!!!', req3.response.over); + testing.expectEqual("number", typeof json.updated_at); + testing.expectEqual(1765867200000, json.updated_at); + testing.expectEqual({over: '9000!!!',updated_at:1765867200000}, json); }); diff --git a/src/testing.zig b/src/testing.zig index cd615aaf..3bc24a8e 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -516,7 +516,7 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void { } if (std.mem.eql(u8, path, "/xhr/json")) { - return req.respond("{\"over\":\"9000!!!\"}", .{ + return req.respond("{\"over\":\"9000!!!\",\"updated_at\":1765867200000}", .{ .extra_headers = &.{ .{ .name = "Content-Type", .value = "application/json" }, }, From d50f6b830a687c0d903e83adcbd960bdd7d38b25 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 25 Dec 2025 12:39:09 +0100 Subject: [PATCH 2/3] add Value.persist --- src/browser/js/Context.zig | 8 ++++++++ src/browser/js/Value.zig | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index a11023a9..0d2a7040 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -32,6 +32,7 @@ const ScriptManager = @import("../ScriptManager.zig"); const Allocator = std.mem.Allocator; const PersistentObject = v8.Persistent(v8.Object); +const PersistentValue = v8.Persistent(v8.Value); const PersistentModule = v8.Persistent(v8.Module); const PersistentPromise = v8.Persistent(v8.Promise); const PersistentFunction = v8.Persistent(v8.Function); @@ -85,6 +86,9 @@ identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty, // we now simply persist every time persist() is called. js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty, +// js_value_list tracks persisted js values. +js_value_list: std.ArrayListUnmanaged(PersistentValue) = .empty, + // Various web APIs depend on having a persistent promise resolver. They // require for this PromiseResolver to be valid for a lifetime longer than // the function that resolves/rejects them. @@ -161,6 +165,10 @@ pub fn deinit(self: *Context) void { p.deinit(); } + for (self.js_value_list.items) |*p| { + p.deinit(); + } + for (self.persisted_promise_resolvers.items) |*p| { p.deinit(); } diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index 8cba1688..329fb128 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -23,6 +23,8 @@ const v8 = js.v8; const Allocator = std.mem.Allocator; +const PersistentValue = v8.Persistent(v8.Value); + const Value = @This(); js_val: v8.Value, context: *js.Context, @@ -49,6 +51,16 @@ pub fn fromJson(ctx: *js.Context, json: []const u8) !Value { return Value{ .context = ctx, .js_val = value }; } +pub fn persist(self: Value) !Value { + const js_val = self.js_val; + var context = self.context; + + const persisted = PersistentValue.init(context.isolate, js_val); + try context.js_value_list.append(context.arena, persisted); + + return Value{ .context = context, .js_val = persisted.toValue() }; +} + pub fn toObject(self: Value) js.Object { return .{ .context = self.context, From 7cc2c2344ea1a3b66723108aaf8f395f2c5643d7 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 25 Dec 2025 12:48:24 +0100 Subject: [PATCH 3/3] use V8 json parser with xhr/fetch webAPIs The pure zig JSON parser didn't generate the same type of values than JS JSON.parse command. Using directly V8's JSON parser gives the assurance to have the right JS types. Moreover, it avoid data transformations between Zig and V8. --- src/browser/webapi/net/Response.zig | 10 +++------- src/browser/webapi/net/XMLHttpRequest.zig | 7 ++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/browser/webapi/net/Response.zig b/src/browser/webapi/net/Response.zig index 3e423691..4995e600 100644 --- a/src/browser/webapi/net/Response.zig +++ b/src/browser/webapi/net/Response.zig @@ -120,15 +120,11 @@ pub fn getText(self: *const Response, page: *Page) !js.Promise { pub fn getJson(self: *Response, page: *Page) !js.Promise { const body = self._body orelse ""; - const value = std.json.parseFromSliceLeaky( - std.json.Value, - page.call_arena, - body, - .{}, - ) catch |err| { + const value = js.Value.fromJson(page.js, body) catch |err| { return page.js.rejectPromise(.{@errorName(err)}); }; - return page.js.resolvePromise(value); + const pvalue = try value.persist(); + return page.js.resolvePromise(pvalue); } pub const JsApi = struct { diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index fc216016..0d903249 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -67,7 +67,7 @@ const ReadyState = enum(u8) { const Response = union(ResponseType) { text: []const u8, - json: std.json.Value, + json: js.Value, document: *Node.Document, }; @@ -254,8 +254,9 @@ pub fn getResponse(self: *XMLHttpRequest, page: *Page) !?Response { const res: Response = switch (self._response_type) { .text => .{ .text = data }, .json => blk: { - const parsed = try std.json.parseFromSliceLeaky(std.json.Value, page.call_arena, data, .{}); - break :blk .{ .json = parsed }; + const value = try js.Value.fromJson(page.js, data); + const pvalue = try value.persist(); + break :blk .{ .json = pvalue }; }, .document => blk: { const document = try page._factory.node(Node.Document{ ._proto = undefined, ._type = .generic });