mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
Allow this argument to TokenList forEach
JsObject can now be used as a normal parameter. It'll receive the opaque value. This is largely useful when a Zig function takes an argument which it needs to pass back into a callback. JsThis is now a thin wrapper around JsObject for functions that was the JsObject of the receiver. This is for advanced usage where the Zig function wants to manipulate the v8.Object that represents the zig value. postAttach is an example of such usage.
This commit is contained in:
@@ -24,7 +24,7 @@ const utils = @import("utils.z");
|
||||
const Element = @import("element.zig").Element;
|
||||
const Union = @import("element.zig").Union;
|
||||
|
||||
const JsObject = @import("../env.zig").JsObject;
|
||||
const JsThis = @import("../env.zig").JsThis;
|
||||
|
||||
const Walker = @import("walker.zig").Walker;
|
||||
const WalkerDepthFirst = @import("walker.zig").WalkerDepthFirst;
|
||||
@@ -443,15 +443,15 @@ pub const HTMLCollection = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn postAttach(self: *HTMLCollection, js_obj: JsObject) !void {
|
||||
pub fn postAttach(self: *HTMLCollection, js_this: JsThis) !void {
|
||||
const len = try self.get_length();
|
||||
for (0..len) |i| {
|
||||
const node = try self.item(@intCast(i)) orelse unreachable;
|
||||
const e = @as(*parser.Element, @ptrCast(node));
|
||||
try js_obj.setIndex(@intCast(i), e);
|
||||
try js_this.setIndex(@intCast(i), e);
|
||||
|
||||
if (try item_name(e)) |name| {
|
||||
try js_obj.set(name, e);
|
||||
try js_this.set(name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const parser = @import("../netsurf.zig");
|
||||
const SessionState = @import("../env.zig").SessionState;
|
||||
|
||||
const Env = @import("../env.zig").Env;
|
||||
const JsObject = @import("../env.zig").JsObject;
|
||||
const JsThis = @import("../env.zig").JsThis;
|
||||
const NodeList = @import("nodelist.zig").NodeList;
|
||||
|
||||
pub const Interfaces = .{
|
||||
@@ -184,9 +184,9 @@ pub const MutationRecords = struct {
|
||||
return null;
|
||||
};
|
||||
}
|
||||
pub fn postAttach(self: *const MutationRecords, js_obj: JsObject) !void {
|
||||
pub fn postAttach(self: *const MutationRecords, js_this: JsThis) !void {
|
||||
if (self.first) |mr| {
|
||||
try js_obj.set("0", mr);
|
||||
try js_this.set("0", mr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
|
||||
const parser = @import("../netsurf.zig");
|
||||
|
||||
const JsObject = @import("../env.zig").JsObject;
|
||||
const JsThis = @import("../env.zig").JsThis;
|
||||
const Callback = @import("../env.zig").Callback;
|
||||
const SessionState = @import("../env.zig").SessionState;
|
||||
|
||||
@@ -177,11 +177,11 @@ pub const NodeList = struct {
|
||||
}
|
||||
|
||||
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
|
||||
pub fn postAttach(self: *NodeList, js_obj: JsObject) !void {
|
||||
pub fn postAttach(self: *NodeList, js_this: JsThis) !void {
|
||||
const len = self.get_length();
|
||||
for (0..len) |i| {
|
||||
const node = try self._item(@intCast(i)) orelse unreachable;
|
||||
try js_obj.setIndex(i, node);
|
||||
try js_this.setIndex(i, node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ const parser = @import("../netsurf.zig");
|
||||
const iterator = @import("../iterator/iterator.zig");
|
||||
|
||||
const Callback = @import("../env.zig").Callback;
|
||||
const JsObject = @import("../env.zig").JsObject;
|
||||
const SessionState = @import("../env.zig").SessionState;
|
||||
const DOMException = @import("exceptions.zig").DOMException;
|
||||
|
||||
@@ -138,11 +139,11 @@ pub const DOMTokenList = struct {
|
||||
}
|
||||
|
||||
// TODO handle thisArg
|
||||
pub fn _forEach(self: *parser.TokenList, cbk: Callback) !void {
|
||||
pub fn _forEach(self: *parser.TokenList, cbk: Callback, this_arg: JsObject) !void {
|
||||
var entries = _entries(self);
|
||||
while (try entries._next()) |entry| {
|
||||
var result: Callback.Result = undefined;
|
||||
cbk.tryCall(.{ entry.@"1", entry.@"0", self }, &result) catch {
|
||||
cbk.tryCallWithThis(this_arg, .{ entry.@"1", entry.@"0", self }, &result) catch {
|
||||
log.err("callback error: {s}", .{result.exception});
|
||||
log.debug("stack:\n{s}", .{result.stack orelse "???"});
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ const Interfaces = generate.Tuple(.{
|
||||
@import("xmlserializer/xmlserializer.zig").Interfaces,
|
||||
});
|
||||
|
||||
pub const JsThis = Env.JsThis;
|
||||
pub const JsObject = Env.JsObject;
|
||||
pub const Callback = Env.Callback;
|
||||
pub const Env = js.Env(*SessionState, Interfaces{});
|
||||
|
||||
@@ -902,6 +902,27 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
_ = self.scope_arena.reset(.{ .retain_with_limit = 1024 * 64 });
|
||||
}
|
||||
|
||||
// Given an anytype, turns it into a v8.Object. The anytype could be:
|
||||
// 1 - A V8.object already
|
||||
// 2 - Our this JsObject wrapper around a V8.Object
|
||||
// 3 - A zig instance that has previously been given to V8
|
||||
// (i.e., the value has to be known to the executor)
|
||||
fn valueToExistingObject(self: *const Executor, value: anytype) !v8.Object {
|
||||
if (@TypeOf(value) == v8.Object) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (@TypeOf(value) == JsObject) {
|
||||
return value.js_obj;
|
||||
}
|
||||
|
||||
const persistent_object = self.scope.?.identity_map.get(@intFromPtr(value)) orelse {
|
||||
return error.InvalidThisForCallback;
|
||||
};
|
||||
|
||||
return persistent_object.castToObject();
|
||||
}
|
||||
|
||||
// Wrap a v8.Value, largely so that we can provide a convenient
|
||||
// toString function
|
||||
fn createValue(self: *const Executor, value: v8.Value) Value {
|
||||
@@ -1003,7 +1024,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
gop.value_ptr.* = js_persistent;
|
||||
|
||||
if (@hasDecl(ptr.child, "postAttach")) {
|
||||
const obj_wrap = JsObject{ .js_obj = js_obj, .executor = self };
|
||||
const obj_wrap = JsThis{ .obj = .{ .js_obj = js_obj, .executor = self } };
|
||||
switch (@typeInfo(@TypeOf(ptr.child.postAttach)).@"fn".params.len) {
|
||||
2 => try value.postAttach(obj_wrap),
|
||||
3 => try value.postAttach(self.state, obj_wrap),
|
||||
@@ -1094,7 +1115,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
pub const Callback = struct {
|
||||
id: usize,
|
||||
executor: *Executor,
|
||||
this: ?v8.Object = null,
|
||||
_this: ?v8.Object = null,
|
||||
func: PersistentFunction,
|
||||
|
||||
// We use this when mapping a JS value to a Zig object. We can't
|
||||
@@ -1110,22 +1131,23 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
};
|
||||
|
||||
pub fn setThis(self: *Callback, value: anytype) !void {
|
||||
const persistent_object = self.executor.scope.?.identity_map.get(@intFromPtr(value)) orelse {
|
||||
return error.InvalidThisForCallback;
|
||||
};
|
||||
self.this = persistent_object.castToObject();
|
||||
self._this = try self.executor.valueToExistingObject(value);
|
||||
}
|
||||
|
||||
pub fn call(self: *const Callback, args: anytype) !void {
|
||||
return self.callWithThis(self.this orelse self.executor.context.getGlobal(), args);
|
||||
return self.callWithThis(self.getThis(), args);
|
||||
}
|
||||
|
||||
pub fn tryCall(self: *const Callback, args: anytype, result: *Result) !void {
|
||||
return self.tryCallWithThis(self.getThis(), args, result);
|
||||
}
|
||||
|
||||
pub fn tryCallWithThis(self: *const Callback, this: anytype, args: anytype, result: *Result) !void {
|
||||
var try_catch: TryCatch = undefined;
|
||||
try_catch.init(self.executor);
|
||||
defer try_catch.deinit();
|
||||
|
||||
self.call(args) catch |err| {
|
||||
self.callWithThis(this, args) catch |err| {
|
||||
if (try_catch.hasCaught()) {
|
||||
const allocator = self.executor.scope.?.call_arena;
|
||||
result.stack = try_catch.stack(allocator) catch null;
|
||||
@@ -1138,9 +1160,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
};
|
||||
}
|
||||
|
||||
fn callWithThis(self: *const @This(), js_this: v8.Object, args: anytype) !void {
|
||||
pub fn callWithThis(self: *const Callback, this: anytype, args: anytype) !void {
|
||||
const executor = self.executor;
|
||||
|
||||
const js_this = try executor.valueToExistingObject(this);
|
||||
|
||||
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
||||
const fields = @typeInfo(@TypeOf(aargs)).@"struct".fields;
|
||||
var js_args: [fields.len]v8.Value = undefined;
|
||||
@@ -1154,8 +1178,12 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
}
|
||||
}
|
||||
|
||||
fn getThis(self: *const Callback) v8.Object {
|
||||
return self._this orelse self.executor.context.getGlobal();
|
||||
}
|
||||
|
||||
// debug/helper to print the source of the JS callback
|
||||
fn printFunc(self: *const @This()) !void {
|
||||
fn printFunc(self: Callback) !void {
|
||||
const executor = self.executor;
|
||||
const value = self.func.castToFunction().toValue();
|
||||
const src = try valueToString(executor.call_arena.allocator(), value, executor.isolate, executor.context);
|
||||
@@ -1198,6 +1226,28 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
}
|
||||
};
|
||||
|
||||
// This only exists so that we know whether a function wants the opaque
|
||||
// JS argument (JsObject), or if it wants the receiver as an opaque
|
||||
// value.
|
||||
// JsObject is normally used when a method wants an opaque JS object
|
||||
// that it'll pass into a callback.
|
||||
// JsThis is used when the function wants to do advanced manipulation
|
||||
// of the v8.Object bound to the instance. For example, postAttach is an
|
||||
// example of using JsThis.
|
||||
pub const JsThis = struct {
|
||||
obj: JsObject,
|
||||
|
||||
const _JSTHIS_ID_KLUDGE = true;
|
||||
|
||||
pub fn setIndex(self: JsThis, index: usize, value: anytype) !void {
|
||||
return self.obj.setIndex(index, value);
|
||||
}
|
||||
|
||||
pub fn set(self: JsThis, key: []const u8, value: anytype) !void {
|
||||
return self.obj.set(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
pub const TryCatch = struct {
|
||||
inner: v8.TryCatch,
|
||||
executor: *const Executor,
|
||||
@@ -1761,14 +1811,14 @@ fn Caller(comptime E: type) type {
|
||||
break :blk params[0 .. params.len - 1];
|
||||
}
|
||||
|
||||
// If the last parameter is a JsObject, set it, and exclude it
|
||||
// If the last parameter is a special JsThis, set it, and exclude it
|
||||
// from our params slice, because we don't want to bind it to
|
||||
// a JS argument
|
||||
if (comptime isJsObject(params[params.len - 1].type.?)) {
|
||||
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{
|
||||
.handle = info.getThis(),
|
||||
if (comptime isJsThis(params[params.len - 1].type.?)) {
|
||||
@field(args, std.fmt.comptimePrint("{d}", .{params.len - 1 + offset})) = .{ .obj = .{
|
||||
.js_obj = info.getThis(),
|
||||
.executor = self.executor,
|
||||
};
|
||||
} };
|
||||
|
||||
// AND the 2nd last parameter is state
|
||||
if (params.len > 1 and comptime isState(params[params.len - 2].type.?)) {
|
||||
@@ -1827,9 +1877,9 @@ fn Caller(comptime E: type) type {
|
||||
}
|
||||
|
||||
if (comptime isState(param.type.?)) {
|
||||
@compileError("State must be the last parameter (or 2nd last if there's a JsObject): " ++ named_function.full_name);
|
||||
} else if (comptime isJsObject(param.type.?)) {
|
||||
@compileError("JsObject must be the last parameter: " ++ named_function.full_name);
|
||||
@compileError("State must be the last parameter (or 2nd last if there's a JsThis): " ++ named_function.full_name);
|
||||
} else if (comptime isJsThis(param.type.?)) {
|
||||
@compileError("JsThis must be the last parameter: " ++ named_function.full_name);
|
||||
} else if (i >= js_parameter_count) {
|
||||
if (@typeInfo(param.type.?) != .optional) {
|
||||
return error.InvalidArgument;
|
||||
@@ -1929,9 +1979,20 @@ fn Caller(comptime E: type) type {
|
||||
if (!js_value.isObject()) {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
||||
const js_obj = js_value.castTo(v8.Object);
|
||||
|
||||
if (comptime isJsObject(T)) {
|
||||
// Caller wants an opaque JsObject. Probably a parameter
|
||||
// that it needs to pass back into a callback
|
||||
return E.JsObject{
|
||||
.js_obj = js_obj,
|
||||
.executor = self.executor,
|
||||
};
|
||||
}
|
||||
|
||||
const context = self.context;
|
||||
const isolate = self.isolate;
|
||||
const js_obj = js_value.castTo(v8.Object);
|
||||
|
||||
var value: T = undefined;
|
||||
inline for (s.fields) |field| {
|
||||
@@ -2017,6 +2078,10 @@ fn Caller(comptime E: type) type {
|
||||
fn isJsObject(comptime T: type) bool {
|
||||
return @typeInfo(T) == .@"struct" and @hasDecl(T, "_JSOBJECT_ID_KLUDGE");
|
||||
}
|
||||
|
||||
fn isJsThis(comptime T: type) bool {
|
||||
return @typeInfo(T) == .@"struct" and @hasDecl(T, "_JSTHIS_ID_KLUDGE");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user