From 613428c54c0f12485af1b08815f71202530fa008 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 30 Nov 2025 12:48:15 +0800 Subject: [PATCH] Execute script.onload/onerror Add object-support for URLSearchParams. Start to treat js.Value as a first class object (instead of js.Object, where appropriate). --- src/browser/ScriptManager.zig | 39 ++++++++--- src/browser/js/Array.zig | 38 ++++++++++ src/browser/js/Context.zig | 25 +++++-- src/browser/js/Object.zig | 29 +++++--- src/browser/js/Value.zig | 74 ++++++++++++++++++++ src/browser/js/js.zig | 54 +------------- src/browser/tests/net/url_search_params.html | 2 +- src/browser/webapi/CustomElementRegistry.zig | 7 +- src/browser/webapi/net/Fetch.zig | 4 ++ src/browser/webapi/net/URLSearchParams.zig | 38 ++++++++-- 10 files changed, 226 insertions(+), 84 deletions(-) create mode 100644 src/browser/js/Array.zig create mode 100644 src/browser/js/Value.zig diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index ca098e9f..bc5468bd 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -726,10 +726,10 @@ const Script = struct { .kind = self.kind, .cacheable = cacheable, }); - self.executeCallback(script_element._on_error, page); + self.executeCallback("error", script_element._on_error, page); return; }; - self.executeCallback(script_element._on_load, page); + self.executeCallback("load", script_element._on_load, page); return; } @@ -752,13 +752,17 @@ const Script = struct { }; if (comptime IS_DEBUG) { - log.info(.browser, "executed script", .{.src = url}); + log.debug(.browser, "executed script", .{ + .src = url, + .success = success, + .on_load = script_element._on_load != null + }); } defer page.tick(); if (success) { - self.executeCallback(script_element._on_load, page); + self.executeCallback("load", script_element._on_load, page); return; } @@ -776,16 +780,31 @@ const Script = struct { .cacheable = cacheable, }); - self.executeCallback(script_element._on_error, page); + self.executeCallback("error", script_element._on_error, page); } - fn executeCallback(self: *const Script, cb_: ?js.Function, page: *Page) void { + fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function, page: *Page) void { const cb = cb_ orelse return; - // @ZIGDOM execute the callback - _ = cb; - _ = self; - _ = page; + const Event = @import("webapi/Event.zig"); + const event = Event.init(typ, .{}, page) catch |err| { + log.warn(.js, "script internal callback", .{ + .url = self.url, + .type = typ, + .err = err, + }); + return; + }; + + var result: js.Function.Result = undefined; + cb.tryCall(void, .{event}, &result) catch { + log.warn(.js, "script callback", .{ + .url = self.url, + .type = typ, + .err = result.exception, + .stack = result.stack, + }); + }; } }; diff --git a/src/browser/js/Array.zig b/src/browser/js/Array.zig new file mode 100644 index 00000000..95bc0e32 --- /dev/null +++ b/src/browser/js/Array.zig @@ -0,0 +1,38 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const js = @import("js.zig"); +const v8 = js.v8; + +const Array = @This(); +js_arr: v8.Array, +context: *js.Context, + +pub fn len(self: Array) usize { + return @intCast(self.js_arr.length()); +} + +pub fn get(self: Array, index: usize) !js.Value { + const idx_key = v8.Integer.initU32(self.context.isolate, @intCast(index)); + const js_obj = self.js_arr.castTo(v8.Object); + return .{ + .context = self.context, + .js_val = try js_obj.getValue(self.context.v8_context, idx_key.toValue()), + }; +} diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index a2f358dc..eea764b2 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -392,9 +392,9 @@ pub fn createException(self: *const Context, e: v8.Value) js.Exception { // Wrap a v8.Value, largely so that we can provide a convenient // toString function -pub fn createValue(self: *const Context, value: v8.Value) js.Value { +pub fn createValue(self: *Context, value: v8.Value) js.Value { return .{ - .value = value, + .js_val = value, .context = self, }; } @@ -665,8 +665,7 @@ pub fn mapZigInstanceToJs(self: *Context, js_obj_: ?v8.Object, value: anytype) ! pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { switch (@typeInfo(T)) { .optional => |o| { - if (comptime o.child == js.Object) { - // If type type is a ?js.Object, then we want to pass + // If type type is a ?js.Value or a ?js.Object, then we want to pass // a js.Object, not null. Consider a function, // _doSomething(arg: ?Env.JsObjet) void { ... } // @@ -681,6 +680,14 @@ pub fn jsValueToZig(self: *Context, comptime T: type, js_value: v8.Value) !T { // pass in `null` and the the doSomething won't // be able to tell if `null` was explicitly passed // or whether no parameter was passed. + if (comptime o.child == js.Value) { + return js.Value{ + .context = self, + .js_val = js_value, + }; + } + + if (comptime o.child == js.Object) { return js.Object{ .context = self, .js_obj = js_value.castTo(v8.Object), @@ -831,6 +838,16 @@ fn jsValueToStruct(self: *Context, comptime T: type, js_value: v8.Value) !?T { return .{ .string = try self.valueToString(js_value, .{ .allocator = self.arena }) }; } + + if (comptime T == js.Value) { + // Caller wants an opaque js.Object. Probably a parameter + // that it needs to pass back into a callback + return js.Value{ + .context = self, + .js_val = js_value, + }; + } + const js_obj = js_value.castTo(v8.Object); if (comptime T == js.Object) { diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig index 53bcafe7..222f2b75 100644 --- a/src/browser/js/Object.zig +++ b/src/browser/js/Object.zig @@ -135,7 +135,7 @@ pub fn isNullOrUndefined(self: Object) bool { return self.js_obj.toValue().isNullOrUndefined(); } -pub fn nameIterator(self: Object) js.ValueIterator { +pub fn nameIterator(self: Object, allocator: Allocator) NameIterator { const context = self.context; const js_obj = self.js_obj; @@ -145,6 +145,7 @@ pub fn nameIterator(self: Object) js.ValueIterator { return .{ .count = count, .context = context, + .allocator = allocator, .js_obj = array.castTo(v8.Object), }; } @@ -153,10 +154,22 @@ pub fn toZig(self: Object, comptime T: type) !T { return self.context.jsValueToZig(T, self.js_obj.toValue()); } -pub fn TriState(comptime T: type) type { - return union(enum) { - null: void, - undefined: void, - value: T, - }; -} +pub const NameIterator = struct { + count: u32, + idx: u32 = 0, + js_obj: v8.Object, + allocator: Allocator, + context: *const Context, + + pub fn next(self: *NameIterator) !?[]const u8 { + const idx = self.idx; + if (idx == self.count) { + return null; + } + self.idx += 1; + + const context = self.context; + const js_val = try self.js_obj.getAtIndex(context.v8_context, idx); + return try context.valueToString(js_val, .{ .allocator = self.allocator }); + } +}; diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig new file mode 100644 index 00000000..143d221a --- /dev/null +++ b/src/browser/js/Value.zig @@ -0,0 +1,74 @@ +// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); +const js = @import("js.zig"); + +const v8 = js.v8; + +const Allocator = std.mem.Allocator; + +const Value = @This(); +js_val: v8.Value, +context: *js.Context, + +pub fn isObject(self: Value) bool { + return self.js_val.isObject(); +} + +pub fn isString(self: Value) bool { + return self.js_val.isString(); +} + +pub fn isArray(self: Value) bool { + return self.js_val.isArray(); +} + +pub fn toString(self: Value, allocator: Allocator) ![]const u8 { + return self.context.valueToString(self.js_val, .{ .allocator = allocator }); +} + +pub fn toObject(self: Value) js.Object { + return .{ + .context = self.context, + .js_obj = self.js_val.castTo(v8.Object), + }; +} + +pub fn toArray(self: Value) js.Array { + return .{ + .context = self.context, + .js_arr = self.js_val.castTo(v8.Array), + }; +} + +// pub const Value = struct { +// value: v8.Value, +// context: *const Context, + +// // the caller needs to deinit the string returned +// pub fn toString(self: Value, allocator: Allocator) ![]const u8 { +// return self.context.valueToString(self.value, .{ .allocator = allocator }); +// } + +// pub fn fromJson(ctx: *Context, json: []const u8) !Value { +// const json_string = v8.String.initUtf8(ctx.isolate, json); +// const value = try v8.Json.parse(ctx.v8_context, json_string); +// return Value{ .context = ctx, .value = value }; +// } +// }; diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 71e19286..4f993d8d 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -29,6 +29,8 @@ pub const Inspector = @import("Inspector.zig"); // TODO: Is "This" really necessary? pub const This = @import("This.zig"); +pub const Value = @import("Value.zig"); +pub const Array = @import("Array.zig"); pub const Object = @import("Object.zig"); pub const TryCatch = @import("TryCatch.zig"); pub const Function = @import("Function.zig"); @@ -150,58 +152,6 @@ pub const Exception = struct { } }; -pub const Value = struct { - value: v8.Value, - context: *const Context, - - // the caller needs to deinit the string returned - pub fn toString(self: Value, allocator: Allocator) ![]const u8 { - return self.context.valueToString(self.value, .{ .allocator = allocator }); - } - - pub fn fromJson(ctx: *Context, json: []const u8) !Value { - const json_string = v8.String.initUtf8(ctx.isolate, json); - const value = try v8.Json.parse(ctx.v8_context, json_string); - return Value{ .context = ctx, .value = value }; - } - - pub fn isArray(self: Value) bool { - return self.value.isArray(); - } - - pub fn arrayLength(self: Value) u32 { - std.debug.assert(self.value.isArray()); - return self.value.castTo(v8.Array).length(); - } - - pub fn arrayGet(self: Value, index: u32) !Value { - std.debug.assert(self.value.isArray()); - const array_obj = self.value.castTo(v8.Array).castTo(v8.Object); - const idx_key = v8.Integer.initU32(self.context.isolate, index); - const elem_val = try array_obj.getValue(self.context.v8_context, idx_key.toValue()); - return self.context.createValue(elem_val); - } -}; - -pub const ValueIterator = struct { - count: u32, - idx: u32 = 0, - js_obj: v8.Object, - context: *const Context, - - pub fn next(self: *ValueIterator) !?Value { - const idx = self.idx; - if (idx == self.count) { - return null; - } - self.idx += 1; - - const context = self.context; - const js_val = try self.js_obj.getAtIndex(context.v8_context, idx); - return context.createValue(js_val); - } -}; - pub fn UndefinedOr(comptime T: type) type { return union(enum) { undefined: void, diff --git a/src/browser/tests/net/url_search_params.html b/src/browser/tests/net/url_search_params.html index bece1c64..54b66b3d 100644 --- a/src/browser/tests/net/url_search_params.html +++ b/src/browser/tests/net/url_search_params.html @@ -21,7 +21,7 @@