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.
This commit is contained in:
Pierre Tachoire
2025-12-24 15:19:41 +01:00
parent 66342b35db
commit 1dcccef080
3 changed files with 14 additions and 29 deletions

View File

@@ -254,17 +254,12 @@ pub fn _json(self: *Response, page: *Page) !js.Promise {
self.body_used = true; self.body_used = true;
if (self.body) |body| { if (self.body) |body| {
const p = std.json.parseFromSliceLeaky( const value = js.Value.fromJson(page.js, body) catch |e| {
std.json.Value,
page.call_arena,
body,
.{},
) catch |e| {
log.info(.browser, "invalid json", .{ .err = e, .source = "Request" }); log.info(.browser, "invalid json", .{ .err = e, .source = "Request" });
return error.SyntaxError; return error.SyntaxError;
}; };
return page.js.resolvePromise(p); return page.js.resolvePromise(value);
} }
return page.js.resolvePromise(null); return page.js.resolvePromise(null);
} }

View File

@@ -179,17 +179,12 @@ pub fn _json(self: *Response, page: *Page) !js.Promise {
if (self.body) |body| { if (self.body) |body| {
self.body_used = true; self.body_used = true;
const p = std.json.parseFromSliceLeaky( const value = js.Value.fromJson(page.js, body) catch |e| {
std.json.Value,
page.call_arena,
body,
.{},
) catch |e| {
log.info(.browser, "invalid json", .{ .err = e, .source = "Response" }); log.info(.browser, "invalid json", .{ .err = e, .source = "Response" });
return error.SyntaxError; return error.SyntaxError;
}; };
return page.js.resolvePromise(p); return page.js.resolvePromise(value);
} }
return page.js.resolvePromise(null); return page.js.resolvePromise(null);
} }

View File

@@ -31,6 +31,7 @@ const Mime = @import("../mime.zig").Mime;
const parser = @import("../netsurf.zig"); const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page; const Page = @import("../page.zig").Page;
const Http = @import("../../http/Http.zig"); const Http = @import("../../http/Http.zig");
const js = @import("../js/js.zig");
// XHR interfaces // XHR interfaces
// https://xhr.spec.whatwg.org/#interface-xmlhttprequest // https://xhr.spec.whatwg.org/#interface-xmlhttprequest
@@ -128,21 +129,19 @@ pub const XMLHttpRequest = struct {
JSON, JSON,
}; };
const JSONValue = std.json.Value;
const Response = union(ResponseType) { const Response = union(ResponseType) {
Empty: void, Empty: void,
Text: []const u8, Text: []const u8,
ArrayBuffer: void, ArrayBuffer: void,
Blob: void, Blob: void,
Document: *parser.Document, Document: *parser.Document,
JSON: JSONValue, JSON: js.Value,
}; };
const ResponseObj = union(enum) { const ResponseObj = union(enum) {
Document: *parser.Document, Document: *parser.Document,
Failure: void, Failure: void,
JSON: JSONValue, JSON: js.Value,
fn deinit(self: ResponseObj) void { fn deinit(self: ResponseObj) void {
switch (self) { switch (self) {
@@ -605,7 +604,7 @@ pub const XMLHttpRequest = struct {
} }
// https://xhr.spec.whatwg.org/#the-response-attribute // https://xhr.spec.whatwg.org/#the-response-attribute
pub fn get_response(self: *XMLHttpRequest) !?Response { pub fn get_response(self: *XMLHttpRequest, page: *Page) !?Response {
if (self.response_type == .Empty or self.response_type == .Text) { if (self.response_type == .Empty or self.response_type == .Text) {
if (self.state == .loading or self.state == .done) { if (self.state == .loading or self.state == .done) {
return .{ .Text = try self.get_responseText() }; return .{ .Text = try self.get_responseText() };
@@ -652,7 +651,7 @@ pub const XMLHttpRequest = struct {
// TODO Let jsonObject be the result of running parse JSON from bytes // TODO Let jsonObject be the result of running parse JSON from bytes
// on thiss received bytes. If that threw an exception, then return // on thiss received bytes. If that threw an exception, then return
// null. // null.
self.setResponseObjJSON(); self.setResponseObjJSON(page);
} }
if (self.response_obj) |obj| { if (self.response_obj) |obj| {
@@ -691,22 +690,18 @@ pub const XMLHttpRequest = struct {
}; };
} }
// setResponseObjJSON parses the received bytes as a std.json.Value. // setResponseObjJSON parses the received bytes as a js.Value.
fn setResponseObjJSON(self: *XMLHttpRequest) void { fn setResponseObjJSON(self: *XMLHttpRequest, page: *Page) void {
// TODO should we use parseFromSliceLeaky if we expect the allocator is const value = js.Value.fromJson(
// already an arena? page.js,
const p = std.json.parseFromSliceLeaky(
JSONValue,
self.arena,
self.response_bytes.items, self.response_bytes.items,
.{},
) catch |e| { ) catch |e| {
log.warn(.http, "invalid json", .{ .err = e, .url = self.url, .source = "xhr" }); log.warn(.http, "invalid json", .{ .err = e, .url = self.url, .source = "xhr" });
self.response_obj = .{ .Failure = {} }; self.response_obj = .{ .Failure = {} };
return; return;
}; };
self.response_obj = .{ .JSON = p }; self.response_obj = .{ .JSON = value };
} }
pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 { pub fn get_responseText(self: *XMLHttpRequest) ![]const u8 {