mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
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).
This commit is contained in:
@@ -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,
|
||||
});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
38
src/browser/js/Array.zig
Normal file
38
src/browser/js/Array.zig
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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()),
|
||||
};
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
};
|
||||
|
||||
74
src/browser/js/Value.zig
Normal file
74
src/browser/js/Value.zig
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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 };
|
||||
// }
|
||||
// };
|
||||
@@ -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,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<script id=urlSearchParams>
|
||||
const inputs = [
|
||||
// @ZIGDOM [["over", "9000!!"], ["abc", 123], ["key1", ""], ["key2", ""]],
|
||||
// @ZIGDOM {over: "9000!!", abc: 123, key1: "", key2: ""},
|
||||
{over: "9000!!", abc: 123, key1: "", key2: ""},
|
||||
"over=9000!!&abc=123&key1&key2=",
|
||||
"?over=9000!!&abc=123&key1&key2=",
|
||||
]
|
||||
|
||||
@@ -69,10 +69,9 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
// Read observedAttributes static property from constructor
|
||||
if (constructor.getPropertyValue("observedAttributes") catch null) |observed_attrs| {
|
||||
if (observed_attrs.isArray()) {
|
||||
const len = observed_attrs.arrayLength();
|
||||
var i: u32 = 0;
|
||||
while (i < len) : (i += 1) {
|
||||
const attr_val = observed_attrs.arrayGet(i) catch continue;
|
||||
var js_arr = observed_attrs.toArray();
|
||||
for (0..js_arr.len()) |i| {
|
||||
const attr_val = js_arr.get(i) catch continue;
|
||||
const attr_name = attr_val.toString(page.arena) catch continue;
|
||||
const owned_attr = page.dupeString(attr_name) catch continue;
|
||||
definition.observed_attributes.put(page.arena, owned_attr, {}) catch continue;
|
||||
|
||||
@@ -34,6 +34,7 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
const Fetch = @This();
|
||||
|
||||
_page: *Page,
|
||||
_url: []const u8,
|
||||
_buf: std.ArrayList(u8),
|
||||
_response: *Response,
|
||||
_resolver: js.PersistentPromiseResolver,
|
||||
@@ -48,6 +49,7 @@ pub fn init(input: Input, page: *Page) !js.Promise {
|
||||
fetch.* = .{
|
||||
._page = page,
|
||||
._buf = .empty,
|
||||
._url = try page.arena.dupe(u8, request._url),
|
||||
._resolver = try page.js.createPromiseResolver(.page),
|
||||
._response = try Response.init(null, .{ .status = 0 }, page),
|
||||
};
|
||||
@@ -58,6 +60,7 @@ pub fn init(input: Input, page: *Page) !js.Promise {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.http, "fetch", .{ .url = request._url });
|
||||
}
|
||||
std.debug.print("fetch: {s}\n", .{request._url});
|
||||
|
||||
try http_client.request(.{
|
||||
.ctx = fetch,
|
||||
@@ -97,6 +100,7 @@ fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
||||
self._response._body = self._buf.items;
|
||||
std.debug.print("fetch-resolve: {s}\n", .{self._url});
|
||||
return self._resolver.resolve(self._response);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,17 +33,26 @@ _arena: Allocator,
|
||||
_params: KeyValueList,
|
||||
|
||||
const InitOpts = union(enum) {
|
||||
value: js.Value,
|
||||
query_string: []const u8,
|
||||
// @ZIGMOD: Array
|
||||
// @ZIGMOD: Object
|
||||
};
|
||||
|
||||
pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams {
|
||||
const arena = page.arena;
|
||||
const params: KeyValueList = blk: {
|
||||
const opts = opts_ orelse break :blk .empty;
|
||||
break :blk switch (opts) {
|
||||
.query_string => |str| try paramsFromString(arena, str, &page.buf),
|
||||
};
|
||||
switch (opts) {
|
||||
.query_string => |qs| break :blk try paramsFromString(arena, qs, &page.buf),
|
||||
.value => |js_val| {
|
||||
if (js_val.isObject()) {
|
||||
break :blk try paramsFromObject(arena, js_val.toObject());
|
||||
}
|
||||
if (js_val.isString()) {
|
||||
break :blk try paramsFromString(arena, try js_val.toString(arena), &page.buf);
|
||||
}
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return page._factory.create(URLSearchParams{
|
||||
@@ -178,6 +187,25 @@ fn paramsFromString(allocator: Allocator, input_: []const u8, buf: []u8) !KeyVal
|
||||
return params;
|
||||
}
|
||||
|
||||
fn paramsFromObject(arena: Allocator, js_obj: js.Object) !KeyValueList {
|
||||
var it = js_obj.nameIterator(arena);
|
||||
|
||||
var params = KeyValueList.init();
|
||||
try params.ensureTotalCapacity(arena, it.count);
|
||||
|
||||
while (try it.next()) |name| {
|
||||
const js_value = try js_obj.get(name);
|
||||
const value = try js_value.toString(arena);
|
||||
|
||||
try params._entries.append(arena, .{
|
||||
.name = try String.init(arena, name, .{}),
|
||||
.value = try String.init(arena, value, .{}),
|
||||
});
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
fn unescape(arena: Allocator, value: []const u8, buf: []u8) !String {
|
||||
if (value.len == 0) {
|
||||
return String.init(undefined, "", .{});
|
||||
|
||||
Reference in New Issue
Block a user