mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
Remove Global v8::Local<V8::Context>
When we create a js.Context, we create the underlying v8.Context and store it for the duration of the page lifetime. This works because we have a global HandleScope - the v8.Context (which is really a v8::Local<v8::Context>) is that to the global HandleScope, effectively making it a global. If we want to remove our global HandleScope, then we can no longer pin the v8.Context in our js.Context. Our js.Context now only holds a v8.Global of the v8.Context (v8::Global<v8::Context). This PR introduces a new type, js.Local, which takes over a lot of the functionality previously found in either js.Caller or js.Context. The simplest way to think about it is: 1 - For v8 -> zig calls, we create a js.Caller (as always) 2 - For zig -> v8 calls, we go through the js.Context (as always) 3 - The shared functionality, which works on a v8.Context, now belongs to js.Local For #1 (v8 -> zig), creating a js.Local for a js.Caller is really simple and centralized. v8 largely gives us everything we need from the FunctionCallbackInfo or PropertyCallbackInfo. For #2, it's messier, because we can only create a local v8::Context if we have a HandleScope, which we may or may not. Unfortunately, in many cases, what to do becomes the responsibility of the caller and much of the code has to become aware of this local-ness. What does it means for our code? The impact is on WebAPIs that store .Global. Because the global can't do anything. You always need to convert that .Global to a local (e.g. js.Function.Global -> js.Function). If you're 100% sure the WebAPI is only being invoked by a v8 callback, you can use `page.js.local.?.toLocal(some_global).call(...)` to get the local value. If you're 100% sure the WebAPI is only being invoked by Zig, you need to create `js.Local.Scope` to get access to a local: ```zig var ls: js.Local.Scope = undefined; page.js.localScope(&ls); defer ls.deinit(); ls.toLocal(some_global).call(...) // can also access `&ls.local` for APIs that require a *const js.Local ``` For functions that can be invoked by either V8 or Zig, you should generally push the responsibility to the caller by accepting a `local: *const js.Local`. If the caller is a v8 callback, it can pass `page.js.local.?`. If the caller is a Zig callback, it can create a `Local.Scope`. As an alternative, it is possible to simply pass the *Page, and check `if page.js.local == null` and, if so, create a Local.Scope. But this should only be done for performance reasons. We currently only do this in 1 place, and it's because the Zig caller doesn't know whether a Local will actually be needed and it's potentially called on every element creating from the parser.
This commit is contained in:
@@ -19,555 +19,15 @@
|
||||
const std = @import("std");
|
||||
const js = @import("js.zig");
|
||||
const log = @import("../../log.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const v8 = js.v8;
|
||||
|
||||
const Caller = @import("Caller.zig");
|
||||
const Context = @import("Context.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
const CALL_ARENA_RETAIN = 1024 * 16;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
// ============================================================================
|
||||
// Internal Callback Info Wrappers
|
||||
// ============================================================================
|
||||
// These wrap the raw v8 C API to provide a cleaner interface.
|
||||
// They are not exported - internal to this module only.
|
||||
|
||||
const Value = struct {
|
||||
handle: *const v8.Value,
|
||||
|
||||
fn isArray(self: Value) bool {
|
||||
return v8.v8__Value__IsArray(self.handle);
|
||||
}
|
||||
|
||||
fn isTypedArray(self: Value) bool {
|
||||
return v8.v8__Value__IsTypedArray(self.handle);
|
||||
}
|
||||
|
||||
fn isFunction(self: Value) bool {
|
||||
return v8.v8__Value__IsFunction(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
const Name = struct {
|
||||
handle: *const v8.Name,
|
||||
};
|
||||
|
||||
const FunctionCallbackInfo = struct {
|
||||
handle: *const v8.FunctionCallbackInfo,
|
||||
|
||||
fn length(self: FunctionCallbackInfo) u32 {
|
||||
return @intCast(v8.v8__FunctionCallbackInfo__Length(self.handle));
|
||||
}
|
||||
|
||||
fn getArg(self: FunctionCallbackInfo, index: u32) Value {
|
||||
return .{ .handle = v8.v8__FunctionCallbackInfo__INDEX(self.handle, @intCast(index)).? };
|
||||
}
|
||||
|
||||
fn getThis(self: FunctionCallbackInfo) *const v8.Object {
|
||||
return v8.v8__FunctionCallbackInfo__This(self.handle).?;
|
||||
}
|
||||
|
||||
fn getReturnValue(self: FunctionCallbackInfo) ReturnValue {
|
||||
var rv: v8.ReturnValue = undefined;
|
||||
v8.v8__FunctionCallbackInfo__GetReturnValue(self.handle, &rv);
|
||||
return .{ .handle = rv };
|
||||
}
|
||||
|
||||
fn isConstructCall(self: FunctionCallbackInfo) bool {
|
||||
return v8.v8__FunctionCallbackInfo__IsConstructCall(self.handle);
|
||||
}
|
||||
};
|
||||
|
||||
const PropertyCallbackInfo = struct {
|
||||
handle: *const v8.PropertyCallbackInfo,
|
||||
|
||||
fn getThis(self: PropertyCallbackInfo) *const v8.Object {
|
||||
return v8.v8__PropertyCallbackInfo__This(self.handle).?;
|
||||
}
|
||||
|
||||
fn getReturnValue(self: PropertyCallbackInfo) ReturnValue {
|
||||
var rv: v8.ReturnValue = undefined;
|
||||
v8.v8__PropertyCallbackInfo__GetReturnValue(self.handle, &rv);
|
||||
return .{ .handle = rv };
|
||||
}
|
||||
};
|
||||
|
||||
const ReturnValue = struct {
|
||||
handle: v8.ReturnValue,
|
||||
|
||||
fn set(self: ReturnValue, value: anytype) void {
|
||||
const T = @TypeOf(value);
|
||||
if (T == Value) {
|
||||
self.setValueHandle(value.handle);
|
||||
} else if (T == *const v8.Object) {
|
||||
self.setValueHandle(@ptrCast(value));
|
||||
} else if (T == *const v8.Value) {
|
||||
self.setValueHandle(value);
|
||||
} else if (T == js.Value) {
|
||||
self.setValueHandle(value.handle);
|
||||
} else {
|
||||
@compileError("Unsupported type for ReturnValue.set: " ++ @typeName(T));
|
||||
}
|
||||
}
|
||||
|
||||
fn setValueHandle(self: ReturnValue, handle: *const v8.Value) void {
|
||||
v8.v8__ReturnValue__Set(self.handle, handle);
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Caller - Responsible for calling Zig functions from JS invocations
|
||||
// ============================================================================
|
||||
|
||||
pub const Caller = struct {
|
||||
context: *Context,
|
||||
isolate: js.Isolate,
|
||||
call_arena: Allocator,
|
||||
|
||||
// Takes the raw v8 isolate and extracts the context from it.
|
||||
pub fn init(v8_isolate: *v8.Isolate) Caller {
|
||||
const isolate = js.Isolate{ .handle = v8_isolate };
|
||||
const v8_context_handle = v8.v8__Isolate__GetCurrentContext(v8_isolate);
|
||||
const embedder_data = v8.v8__Context__GetEmbedderData(v8_context_handle, 1);
|
||||
var lossless: bool = undefined;
|
||||
const context: *Context = @ptrFromInt(v8.v8__BigInt__Uint64Value(embedder_data, &lossless));
|
||||
|
||||
context.call_depth += 1;
|
||||
return .{
|
||||
.context = context,
|
||||
.isolate = isolate,
|
||||
.call_arena = context.call_arena,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Caller) void {
|
||||
const context = self.context;
|
||||
const call_depth = context.call_depth - 1;
|
||||
|
||||
// Because of callbacks, calls can be nested. Because of this, we
|
||||
// can't clear the call_arena after _every_ call. Imagine we have
|
||||
// arr.forEach((i) => { console.log(i); }
|
||||
//
|
||||
// First we call forEach. Inside of our forEach call,
|
||||
// we call console.log. If we reset the call_arena after this call,
|
||||
// it'll reset it for the `forEach` call after, which might still
|
||||
// need the data.
|
||||
//
|
||||
// Therefore, we keep a call_depth, and only reset the call_arena
|
||||
// when a top-level (call_depth == 0) function ends.
|
||||
if (call_depth == 0) {
|
||||
const arena: *ArenaAllocator = @ptrCast(@alignCast(context.call_arena.ptr));
|
||||
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
|
||||
}
|
||||
|
||||
context.call_depth = call_depth;
|
||||
}
|
||||
|
||||
pub const CallOpts = struct {
|
||||
dom_exception: bool = false,
|
||||
null_as_undefined: bool = false,
|
||||
as_typed_array: bool = false,
|
||||
};
|
||||
|
||||
pub fn constructor(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||
if (!info.isConstructCall()) {
|
||||
self.handleError(T, @TypeOf(func), error.InvalidArgument, info, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
self._constructor(func, info) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
};
|
||||
}
|
||||
|
||||
fn _constructor(self: *Caller, func: anytype, info: FunctionCallbackInfo) !void {
|
||||
const F = @TypeOf(func);
|
||||
const args = try self.getArgs(F, 0, info);
|
||||
const res = @call(.auto, func, args);
|
||||
|
||||
const ReturnType = @typeInfo(F).@"fn".return_type orelse {
|
||||
@compileError(@typeName(F) ++ " has a constructor without a return type");
|
||||
};
|
||||
|
||||
const new_this_handle = info.getThis();
|
||||
var this = js.Object{ .ctx = self.context, .handle = new_this_handle };
|
||||
if (@typeInfo(ReturnType) == .error_union) {
|
||||
const non_error_res = res catch |err| return err;
|
||||
this = try self.context.mapZigInstanceToJs(new_this_handle, non_error_res);
|
||||
} else {
|
||||
this = try self.context.mapZigInstanceToJs(new_this_handle, res);
|
||||
}
|
||||
|
||||
// If we got back a different object (existing wrapper), copy the prototype
|
||||
// from new object. (this happens when we're upgrading an CustomElement)
|
||||
if (this.handle != new_this_handle) {
|
||||
const prototype_handle = v8.v8__Object__GetPrototype(new_this_handle).?;
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__SetPrototype(this.handle, self.context.handle, prototype_handle, &out);
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(out.has_value and out.value);
|
||||
}
|
||||
}
|
||||
|
||||
info.getReturnValue().set(this.handle);
|
||||
}
|
||||
|
||||
pub fn method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||
self._method(T, func, info, opts) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
};
|
||||
}
|
||||
|
||||
fn _method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
||||
const F = @TypeOf(func);
|
||||
var handle_scope: js.HandleScope = undefined;
|
||||
handle_scope.init(self.isolate);
|
||||
defer handle_scope.deinit();
|
||||
|
||||
var args = try self.getArgs(F, 1, info);
|
||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
||||
const res = @call(.auto, func, args);
|
||||
info.getReturnValue().set(try self.context.zigValueToJs(res, opts));
|
||||
}
|
||||
|
||||
pub fn function(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||
self._function(func, info, opts) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
};
|
||||
}
|
||||
|
||||
fn _function(self: *Caller, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
||||
const F = @TypeOf(func);
|
||||
const context = self.context;
|
||||
const args = try self.getArgs(F, 0, info);
|
||||
const res = @call(.auto, func, args);
|
||||
info.getReturnValue().set(try context.zigValueToJs(res, opts));
|
||||
}
|
||||
|
||||
pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
return self._getIndex(T, func, idx, info, opts) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
// not intercepted
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args = try self.getArgs(F, 2, info);
|
||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
||||
@field(args, "1") = idx;
|
||||
const ret = @call(.auto, func, args);
|
||||
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
||||
}
|
||||
|
||||
pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
return self._getNamedIndex(T, func, name, info, opts) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
// not intercepted
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args = try self.getArgs(F, 2, info);
|
||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
const ret = @call(.auto, func, args);
|
||||
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
||||
}
|
||||
|
||||
pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, js_value: Value, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
return self._setNamedIndex(T, func, name, js_value, info, opts) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
// not intercepted
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, js_value: Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args: ParameterTypes(F) = undefined;
|
||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
@field(args, "2") = try self.context.jsValueToZig(@TypeOf(@field(args, "2")), js.Value{ .ctx = self.context, .handle = js_value.handle });
|
||||
if (@typeInfo(F).@"fn".params.len == 4) {
|
||||
@field(args, "3") = self.context.page;
|
||||
}
|
||||
const ret = @call(.auto, func, args);
|
||||
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
||||
}
|
||||
|
||||
pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
return self._deleteNamedIndex(T, func, name, info, opts) catch |err| {
|
||||
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args: ParameterTypes(F) = undefined;
|
||||
@field(args, "0") = try Context.typeTaggedAnyOpaque(*T, info.getThis());
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
if (@typeInfo(F).@"fn".params.len == 3) {
|
||||
@field(args, "2") = self.context.page;
|
||||
}
|
||||
const ret = @call(.auto, func, args);
|
||||
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
||||
}
|
||||
|
||||
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, comptime getter: bool, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
// need to unwrap this error immediately for when opts.null_as_undefined == true
|
||||
// and we need to compare it to null;
|
||||
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
|
||||
.error_union => |eu| blk: {
|
||||
break :blk ret catch |err| {
|
||||
// We can't compare err == error.NotHandled if error.NotHandled
|
||||
// isn't part of the possible error set. So we first need to check
|
||||
// if error.NotHandled is part of the error set.
|
||||
if (isInErrorSet(error.NotHandled, eu.error_set)) {
|
||||
if (err == error.NotHandled) {
|
||||
// not intercepted
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
self.handleError(T, F, err, info, opts);
|
||||
// not intercepted
|
||||
return 0;
|
||||
};
|
||||
},
|
||||
else => ret,
|
||||
};
|
||||
|
||||
if (comptime getter) {
|
||||
info.getReturnValue().set(try self.context.zigValueToJs(non_error_ret, opts));
|
||||
}
|
||||
// intercepted
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn isInErrorSet(err: anyerror, comptime T: type) bool {
|
||||
inline for (@typeInfo(T).error_set.?) |e| {
|
||||
if (err == @field(anyerror, e.name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn nameToString(self: *Caller, name: Name) ![]const u8 {
|
||||
return self.context.valueToString(js.Value{ .ctx = self.context, .handle = @ptrCast(name.handle) }, .{});
|
||||
}
|
||||
|
||||
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
||||
const isolate = self.isolate;
|
||||
|
||||
if (comptime @import("builtin").mode == .Debug and @TypeOf(info) == FunctionCallbackInfo) {
|
||||
if (log.enabled(.js, .warn)) {
|
||||
self.logFunctionCallError(@typeName(T), @typeName(F), err, info);
|
||||
}
|
||||
}
|
||||
|
||||
const js_err: *const v8.Value = switch (err) {
|
||||
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
||||
error.OutOfMemory => isolate.createError("out of memory"),
|
||||
error.IllegalConstructor => isolate.createError("Illegal Contructor"),
|
||||
else => blk: {
|
||||
if (comptime opts.dom_exception) {
|
||||
const DOMException = @import("../webapi/DOMException.zig");
|
||||
if (DOMException.fromError(err)) |ex| {
|
||||
const value = self.context.zigValueToJs(ex, .{}) catch break :blk isolate.createError("internal error");
|
||||
break :blk value.handle;
|
||||
}
|
||||
}
|
||||
break :blk isolate.createError(@errorName(err));
|
||||
},
|
||||
};
|
||||
|
||||
const js_exception = isolate.throwException(js_err);
|
||||
info.getReturnValue().setValueHandle(js_exception);
|
||||
}
|
||||
|
||||
// If we call a method in javascript: cat.lives('nine');
|
||||
//
|
||||
// Then we'd expect a Zig function with 2 parameters: a self and the string.
|
||||
// In this case, offset == 1. Offset is always 1 for setters or methods.
|
||||
//
|
||||
// Offset is always 0 for constructors.
|
||||
//
|
||||
// For constructors, setters and methods, we can further increase offset + 1
|
||||
// if the first parameter is an instance of Page.
|
||||
//
|
||||
// Finally, if the JS function is called with _more_ parameters and
|
||||
// the last parameter in Zig is an array, we'll try to slurp the additional
|
||||
// parameters into the array.
|
||||
fn getArgs(self: *const Caller, comptime F: type, comptime offset: usize, info: anytype) !ParameterTypes(F) {
|
||||
const context = self.context;
|
||||
var args: ParameterTypes(F) = undefined;
|
||||
|
||||
const params = @typeInfo(F).@"fn".params[offset..];
|
||||
// Except for the constructor, the first parameter is always `self`
|
||||
// This isn't something we'll bind from JS, so skip it.
|
||||
const params_to_map = blk: {
|
||||
if (params.len == 0) {
|
||||
return args;
|
||||
}
|
||||
|
||||
// If the last parameter is the Page, set it, and exclude it
|
||||
// from our params slice, because we don't want to bind it to
|
||||
// a JS argument
|
||||
if (comptime isPage(params[params.len - 1].type.?)) {
|
||||
@field(args, tupleFieldName(params.len - 1 + offset)) = self.context.page;
|
||||
break :blk params[0 .. params.len - 1];
|
||||
}
|
||||
|
||||
// we have neither a Page nor a JsObject. All params must be
|
||||
// bound to a JavaScript value.
|
||||
break :blk params;
|
||||
};
|
||||
|
||||
if (params_to_map.len == 0) {
|
||||
return args;
|
||||
}
|
||||
|
||||
const js_parameter_count = info.length();
|
||||
const last_js_parameter = params_to_map.len - 1;
|
||||
var is_variadic = false;
|
||||
|
||||
{
|
||||
// This is going to get complicated. If the last Zig parameter
|
||||
// is a slice AND the corresponding javascript parameter is
|
||||
// NOT an an array, then we'll treat it as a variadic.
|
||||
|
||||
const last_parameter_type = params_to_map[params_to_map.len - 1].type.?;
|
||||
const last_parameter_type_info = @typeInfo(last_parameter_type);
|
||||
if (last_parameter_type_info == .pointer and last_parameter_type_info.pointer.size == .slice) {
|
||||
const slice_type = last_parameter_type_info.pointer.child;
|
||||
const corresponding_js_value = info.getArg(@as(u32, @intCast(last_js_parameter)));
|
||||
if (corresponding_js_value.isArray() == false and corresponding_js_value.isTypedArray() == false and slice_type != u8) {
|
||||
is_variadic = true;
|
||||
if (js_parameter_count == 0) {
|
||||
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
||||
} else if (js_parameter_count >= params_to_map.len) {
|
||||
const arr = try self.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1);
|
||||
for (arr, last_js_parameter..) |*a, i| {
|
||||
const js_value = info.getArg(@as(u32, @intCast(i)));
|
||||
a.* = try context.jsValueToZig(slice_type, js.Value{ .ctx = context, .handle = js_value.handle });
|
||||
}
|
||||
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = arr;
|
||||
} else {
|
||||
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline for (params_to_map, 0..) |param, i| {
|
||||
const field_index = comptime i + offset;
|
||||
if (comptime i == params_to_map.len - 1) {
|
||||
if (is_variadic) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime isPage(param.type.?)) {
|
||||
@compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F));
|
||||
} else if (i >= js_parameter_count) {
|
||||
if (@typeInfo(param.type.?) != .optional) {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
@field(args, tupleFieldName(field_index)) = null;
|
||||
} else {
|
||||
const js_value = info.getArg(@as(u32, @intCast(i)));
|
||||
@field(args, tupleFieldName(field_index)) = context.jsValueToZig(param.type.?, js.Value{ .ctx = context, .handle = js_value.handle }) catch {
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// This is extracted to speed up compilation. When left inlined in handleError,
|
||||
// this can add as much as 10 seconds of compilation time.
|
||||
fn logFunctionCallError(self: *Caller, type_name: []const u8, func: []const u8, err: anyerror, info: FunctionCallbackInfo) void {
|
||||
const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
|
||||
log.info(.js, "function call error", .{
|
||||
.type = type_name,
|
||||
.func = func,
|
||||
.err = err,
|
||||
.args = args_dump,
|
||||
.stack = self.context.stackTrace() catch |err1| @errorName(err1),
|
||||
});
|
||||
}
|
||||
|
||||
fn serializeFunctionArgs(self: *Caller, info: FunctionCallbackInfo) ![]const u8 {
|
||||
const context = self.context;
|
||||
var buf = std.Io.Writer.Allocating.init(context.call_arena);
|
||||
|
||||
const separator = log.separator();
|
||||
for (0..info.length()) |i| {
|
||||
try buf.writer.print("{s}{d} - ", .{ separator, i + 1 });
|
||||
const val = info.getArg(@intCast(i));
|
||||
try context.debugValue(js.Value{ .ctx = context, .handle = val.handle }, &buf.writer);
|
||||
}
|
||||
return buf.written();
|
||||
}
|
||||
|
||||
// Takes a function, and returns a tuple for its argument. Used when we
|
||||
// @call a function
|
||||
fn ParameterTypes(comptime F: type) type {
|
||||
const params = @typeInfo(F).@"fn".params;
|
||||
var fields: [params.len]std.builtin.Type.StructField = undefined;
|
||||
|
||||
inline for (params, 0..) |param, i| {
|
||||
fields[i] = .{
|
||||
.name = tupleFieldName(i),
|
||||
.type = param.type.?,
|
||||
.default_value_ptr = null,
|
||||
.is_comptime = false,
|
||||
.alignment = @alignOf(param.type.?),
|
||||
};
|
||||
}
|
||||
|
||||
return @Type(.{ .@"struct" = .{
|
||||
.layout = .auto,
|
||||
.decls = &.{},
|
||||
.fields = &fields,
|
||||
.is_tuple = true,
|
||||
} });
|
||||
}
|
||||
|
||||
fn tupleFieldName(comptime i: usize) [:0]const u8 {
|
||||
return switch (i) {
|
||||
0 => "0",
|
||||
1 => "1",
|
||||
2 => "2",
|
||||
3 => "3",
|
||||
4 => "4",
|
||||
5 => "5",
|
||||
6 => "6",
|
||||
7 => "7",
|
||||
8 => "8",
|
||||
9 => "9",
|
||||
else => std.fmt.comptimePrint("{d}", .{i}),
|
||||
};
|
||||
}
|
||||
|
||||
fn isPage(comptime T: type) bool {
|
||||
return T == *Page or T == *const Page;
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Bridge Builder Functions
|
||||
// ============================================================================
|
||||
|
||||
pub fn Builder(comptime T: type) type {
|
||||
return struct {
|
||||
pub const @"type" = T;
|
||||
@@ -609,8 +69,9 @@ pub fn Builder(comptime T: type) type {
|
||||
@compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet");
|
||||
}
|
||||
|
||||
pub fn prototypeChain() [prototypeChainLength(T)]js.PrototypeChainEntry {
|
||||
var entries: [prototypeChainLength(T)]js.PrototypeChainEntry = undefined;
|
||||
const PrototypeChainEntry = @import("TaggedOpaque.zig").PrototypeChainEntry;
|
||||
pub fn prototypeChain() [prototypeChainLength(T)]PrototypeChainEntry {
|
||||
var entries: [prototypeChainLength(T)]PrototypeChainEntry = undefined;
|
||||
|
||||
entries[0] = .{ .offset = 0, .index = JsApiLookup.getId(T.JsApi) };
|
||||
|
||||
@@ -643,11 +104,11 @@ pub const Constructor = struct {
|
||||
return .{ .func = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
caller.constructor(T, func, info, .{
|
||||
caller.constructor(T, func, handle.?, .{
|
||||
.dom_exception = opts.dom_exception,
|
||||
});
|
||||
}
|
||||
@@ -672,18 +133,18 @@ pub const Function = struct {
|
||||
.func = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
if (comptime opts.static) {
|
||||
caller.function(T, func, info, .{
|
||||
caller.function(T, func, handle.?, .{
|
||||
.dom_exception = opts.dom_exception,
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
} else {
|
||||
caller.method(T, func, info, .{
|
||||
caller.method(T, func, handle.?, .{
|
||||
.dom_exception = opts.dom_exception,
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
@@ -716,17 +177,17 @@ pub const Accessor = struct {
|
||||
accessor.getter = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
if (comptime opts.static) {
|
||||
caller.function(T, getter, info, .{
|
||||
caller.function(T, getter, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
} else {
|
||||
caller.method(T, getter, info, .{
|
||||
caller.method(T, getter, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -739,13 +200,11 @@ pub const Accessor = struct {
|
||||
accessor.setter = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
std.debug.assert(info.length() == 1);
|
||||
|
||||
caller.method(T, setter, info, .{
|
||||
caller.method(T, setter, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -769,11 +228,11 @@ pub const Indexed = struct {
|
||||
return .{ .getter = struct {
|
||||
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
||||
return caller.getIndex(T, getter, idx, info, .{
|
||||
return caller.getIndex(T, getter, idx, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -796,11 +255,11 @@ pub const NamedIndexed = struct {
|
||||
const getter_fn = struct {
|
||||
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
||||
return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{
|
||||
return caller.getNamedIndex(T, getter, c_name.?, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -810,11 +269,11 @@ pub const NamedIndexed = struct {
|
||||
const setter_fn = if (@typeInfo(@TypeOf(setter)) == .null) null else struct {
|
||||
fn wrap(c_name: ?*const v8.Name, c_value: ?*const v8.Value, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
||||
return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{
|
||||
return caller.setNamedIndex(T, setter, c_name.?, c_value.?, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -824,11 +283,11 @@ pub const NamedIndexed = struct {
|
||||
const deleter_fn = if (@typeInfo(@TypeOf(deleter)) == .null) null else struct {
|
||||
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
||||
return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{
|
||||
return caller.deleteNamedIndex(T, deleter, c_name.?, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -858,7 +317,7 @@ pub const Iterator = struct {
|
||||
.async = opts.async,
|
||||
.func = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
const info = Caller.FunctionCallbackInfo{ .handle = handle.? };
|
||||
info.getReturnValue().set(info.getThis());
|
||||
}
|
||||
}.wrap,
|
||||
@@ -870,11 +329,10 @@ pub const Iterator = struct {
|
||||
.func = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
caller.method(T, struct_or_func, info, .{});
|
||||
caller.method(T, struct_or_func, handle.?, .{});
|
||||
}
|
||||
}.wrap,
|
||||
};
|
||||
@@ -892,11 +350,11 @@ pub const Callable = struct {
|
||||
return .{ .func = struct {
|
||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
||||
var caller = Caller.init(v8_isolate);
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
||||
caller.method(T, func, info, .{
|
||||
caller.method(T, func, handle.?, .{
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
}
|
||||
@@ -909,52 +367,52 @@ pub const Property = union(enum) {
|
||||
};
|
||||
|
||||
pub fn unknownPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||
const isolate_handle = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
const context = Context.fromIsolate(.{ .handle = isolate_handle });
|
||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||
var caller: Caller = undefined;
|
||||
caller.init(v8_isolate);
|
||||
defer caller.deinit();
|
||||
|
||||
const property: []const u8 = context.valueToString(.{ .ctx = context, .handle = c_name.? }, .{}) catch {
|
||||
const local = caller.local;
|
||||
const property: []const u8 = local.valueHandleToString(@ptrCast(c_name.?), .{}) catch {
|
||||
return 0;
|
||||
};
|
||||
|
||||
const ignored = std.StaticStringMap(void).initComptime(.{
|
||||
.{ "process", {} },
|
||||
.{ "ShadyDOM", {} },
|
||||
.{ "ShadyCSS", {} },
|
||||
const page = local.ctx.page;
|
||||
const document = page.document;
|
||||
|
||||
.{ "litNonce", {} },
|
||||
.{ "litHtmlVersions", {} },
|
||||
.{ "litElementVersions", {} },
|
||||
.{ "litHtmlPolyfillSupport", {} },
|
||||
.{ "litElementHydrateSupport", {} },
|
||||
.{ "litElementPolyfillSupport", {} },
|
||||
.{ "reactiveElementVersions", {} },
|
||||
if (document.getElementById(property, page)) |el| {
|
||||
const js_val = local.zigValueToJs(el, .{}) catch return 0;
|
||||
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
|
||||
pc.getReturnValue().set(js_val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
.{ "recaptcha", {} },
|
||||
.{ "grecaptcha", {} },
|
||||
.{ "___grecaptcha_cfg", {} },
|
||||
.{ "__recaptcha_api", {} },
|
||||
.{ "__google_recaptcha_client", {} },
|
||||
if (comptime IS_DEBUG) {
|
||||
const ignored = std.StaticStringMap(void).initComptime(.{
|
||||
.{ "process", {} },
|
||||
.{ "ShadyDOM", {} },
|
||||
.{ "ShadyCSS", {} },
|
||||
|
||||
.{ "CLOSURE_FLAGS", {} },
|
||||
});
|
||||
.{ "litNonce", {} },
|
||||
.{ "litHtmlVersions", {} },
|
||||
.{ "litElementVersions", {} },
|
||||
.{ "litHtmlPolyfillSupport", {} },
|
||||
.{ "litElementHydrateSupport", {} },
|
||||
.{ "litElementPolyfillSupport", {} },
|
||||
.{ "reactiveElementVersions", {} },
|
||||
|
||||
if (!ignored.has(property)) {
|
||||
const page = context.page;
|
||||
const document = page.document;
|
||||
.{ "recaptcha", {} },
|
||||
.{ "grecaptcha", {} },
|
||||
.{ "___grecaptcha_cfg", {} },
|
||||
.{ "__recaptcha_api", {} },
|
||||
.{ "__google_recaptcha_client", {} },
|
||||
|
||||
if (document.getElementById(property, page)) |el| {
|
||||
const js_value = context.zigValueToJs(el, .{}) catch {
|
||||
return 0;
|
||||
};
|
||||
var pc = PropertyCallbackInfo{ .handle = handle.? };
|
||||
pc.getReturnValue().set(js_value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
.{ "CLOSURE_FLAGS", {} },
|
||||
});
|
||||
if (!ignored.has(property)) {
|
||||
log.debug(.unknown_prop, "unknown global property", .{
|
||||
.info = "but the property can exist in pure JS",
|
||||
.stack = context.stackTrace() catch "???",
|
||||
.stack = local.stackTrace() catch "???",
|
||||
.property = property,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user