mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-31 01:28:55 +00:00
267 lines
8.8 KiB
Zig
267 lines
8.8 KiB
Zig
// Copyright (C) 2023-2026 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 log = @import("../../log.zig");
|
|
|
|
const Function = @This();
|
|
|
|
local: *const js.Local,
|
|
this: ?*const v8.Object = null,
|
|
handle: *const v8.Function,
|
|
|
|
pub const Result = struct {
|
|
stack: ?[]const u8,
|
|
exception: []const u8,
|
|
};
|
|
|
|
pub fn withThis(self: *const Function, value: anytype) !Function {
|
|
const local = self.local;
|
|
const this_obj = if (@TypeOf(value) == js.Object)
|
|
value.handle
|
|
else
|
|
(try local.zigValueToJs(value, .{})).handle;
|
|
|
|
return .{
|
|
.local = local,
|
|
.this = this_obj,
|
|
.handle = self.handle,
|
|
};
|
|
}
|
|
|
|
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
|
|
const local = self.local;
|
|
|
|
var try_catch: js.TryCatch = undefined;
|
|
try_catch.init(local);
|
|
defer try_catch.deinit();
|
|
|
|
// This creates a new instance using this Function as a constructor.
|
|
// const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{}));
|
|
const handle = v8.v8__Function__NewInstance(self.handle, local.handle, 0, null) orelse {
|
|
caught.* = try_catch.caughtOrError(local.call_arena, error.Unknown);
|
|
return error.JsConstructorFailed;
|
|
};
|
|
|
|
return .{
|
|
.local = local,
|
|
.handle = handle,
|
|
};
|
|
}
|
|
|
|
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
|
|
var caught: js.TryCatch.Caught = undefined;
|
|
return self._tryCallWithThis(T, self.getThis(), args, &caught, .{}) catch |err| {
|
|
log.warn(.js, "call caught", .{ .err = err, .caught = caught });
|
|
return err;
|
|
};
|
|
}
|
|
|
|
pub fn callRethrow(self: *const Function, comptime T: type, args: anytype) !T {
|
|
var caught: js.TryCatch.Caught = undefined;
|
|
return self._tryCallWithThis(T, self.getThis(), args, &caught, .{ .rethrow = true }) catch |err| {
|
|
log.warn(.js, "call caught", .{ .err = err, .caught = caught });
|
|
return err;
|
|
};
|
|
}
|
|
|
|
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
|
|
var caught: js.TryCatch.Caught = undefined;
|
|
return self._tryCallWithThis(T, this, args, &caught, .{}) catch |err| {
|
|
log.warn(.js, "callWithThis caught", .{ .err = err, .caught = caught });
|
|
return err;
|
|
};
|
|
}
|
|
|
|
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *js.TryCatch.Caught) !T {
|
|
return self._tryCallWithThis(T, self.getThis(), args, caught, .{});
|
|
}
|
|
|
|
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
|
|
return self._tryCallWithThis(T, this, args, caught, .{});
|
|
}
|
|
|
|
const CallOpts = struct {
|
|
rethrow: bool = false,
|
|
};
|
|
fn _tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught, comptime opts: CallOpts) !T {
|
|
caught.* = .{};
|
|
const local = self.local;
|
|
|
|
// When we're calling a function from within JavaScript itself, this isn't
|
|
// necessary. We're within a Caller instantiation, which will already have
|
|
// incremented the call_depth and it won't decrement it until the Caller is
|
|
// done.
|
|
// But some JS functions are initiated from Zig code, and not v8. For
|
|
// example, Observers, some event and window callbacks. In those cases, we
|
|
// need to increase the call_depth so that the call_arena remains valid for
|
|
// the duration of the function call. If we don't do this, the call_arena
|
|
// will be reset after each statement of the function which executes Zig code.
|
|
const ctx = local.ctx;
|
|
const call_depth = ctx.call_depth;
|
|
ctx.call_depth = call_depth + 1;
|
|
defer ctx.call_depth = call_depth;
|
|
|
|
const js_this = blk: {
|
|
if (@TypeOf(this) == js.Object) {
|
|
break :blk this;
|
|
}
|
|
break :blk try local.zigValueToJs(this, .{});
|
|
};
|
|
|
|
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
|
|
|
const js_args: []const *const v8.Value = switch (@typeInfo(@TypeOf(aargs))) {
|
|
.@"struct" => |s| blk: {
|
|
const fields = s.fields;
|
|
var js_args: [fields.len]*const v8.Value = undefined;
|
|
inline for (fields, 0..) |f, i| {
|
|
js_args[i] = (try local.zigValueToJs(@field(aargs, f.name), .{})).handle;
|
|
}
|
|
const cargs: [fields.len]*const v8.Value = js_args;
|
|
break :blk &cargs;
|
|
},
|
|
.pointer => blk: {
|
|
var values = try local.call_arena.alloc(*const v8.Value, args.len);
|
|
for (args, 0..) |a, i| {
|
|
values[i] = (try local.zigValueToJs(a, .{})).handle;
|
|
}
|
|
break :blk values;
|
|
},
|
|
else => @compileError("JS Function called with invalid paremter type"),
|
|
};
|
|
|
|
const c_args = @as(?[*]const ?*v8.Value, @ptrCast(js_args.ptr));
|
|
|
|
var try_catch: js.TryCatch = undefined;
|
|
try_catch.init(local);
|
|
defer try_catch.deinit();
|
|
|
|
const handle = v8.v8__Function__Call(self.handle, local.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
|
|
if ((comptime opts.rethrow) and try_catch.hasCaught()) {
|
|
try_catch.rethrow();
|
|
return error.TryCatchRethrow;
|
|
}
|
|
caught.* = try_catch.caughtOrError(local.call_arena, error.JsException);
|
|
return error.JsException;
|
|
};
|
|
|
|
if (@typeInfo(T) == .void) {
|
|
return {};
|
|
}
|
|
return local.jsValueToZig(T, .{ .local = local, .handle = handle });
|
|
}
|
|
|
|
fn getThis(self: *const Function) js.Object {
|
|
const handle = if (self.this) |t| t else v8.v8__Context__Global(self.local.handle).?;
|
|
return .{
|
|
.local = self.local,
|
|
.handle = handle,
|
|
};
|
|
}
|
|
|
|
pub fn src(self: *const Function) ![]const u8 {
|
|
return self.local.valueToString(.{ .local = self.local, .handle = @ptrCast(self.handle) }, .{});
|
|
}
|
|
|
|
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
|
|
const local = self.local;
|
|
const key = local.isolate.initStringHandle(name);
|
|
const handle = v8.v8__Object__Get(self.handle, self.local.handle, key) orelse {
|
|
return error.JsException;
|
|
};
|
|
|
|
return .{
|
|
.local = local,
|
|
.handle = handle,
|
|
};
|
|
}
|
|
|
|
pub fn persist(self: *const Function) !Global {
|
|
return self._persist(true);
|
|
}
|
|
|
|
pub fn temp(self: *const Function) !Temp {
|
|
return self._persist(false);
|
|
}
|
|
|
|
fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Global else Temp) {
|
|
var ctx = self.local.ctx;
|
|
|
|
var global: v8.Global = undefined;
|
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
|
if (comptime is_global) {
|
|
try ctx.trackGlobal(global);
|
|
return .{ .handle = global, .temps = {} };
|
|
}
|
|
try ctx.trackTemp(global);
|
|
return .{ .handle = global, .temps = &ctx.identity.temps };
|
|
}
|
|
|
|
pub fn tempWithThis(self: *const Function, value: anytype) !Temp {
|
|
const with_this = try self.withThis(value);
|
|
return with_this.temp();
|
|
}
|
|
|
|
pub fn persistWithThis(self: *const Function, value: anytype) !Global {
|
|
const with_this = try self.withThis(value);
|
|
return with_this.persist();
|
|
}
|
|
|
|
pub const Temp = G(.temp);
|
|
pub const Global = G(.global);
|
|
|
|
const GlobalType = enum(u8) {
|
|
temp,
|
|
global,
|
|
};
|
|
|
|
fn G(comptime global_type: GlobalType) type {
|
|
return struct {
|
|
handle: v8.Global,
|
|
temps: if (global_type == .temp) *std.AutoHashMapUnmanaged(usize, v8.Global) else void,
|
|
|
|
const Self = @This();
|
|
|
|
pub fn deinit(self: *Self) void {
|
|
v8.v8__Global__Reset(&self.handle);
|
|
}
|
|
|
|
pub fn local(self: *const Self, l: *const js.Local) Function {
|
|
return .{
|
|
.local = l,
|
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
|
};
|
|
}
|
|
|
|
pub fn isEqual(self: *const Self, other: Function) bool {
|
|
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
|
}
|
|
|
|
pub fn release(self: *const Self) void {
|
|
if (self.temps.fetchRemove(self.handle.data_ptr)) |kv| {
|
|
var g = kv.value;
|
|
v8.v8__Global__Reset(&g);
|
|
}
|
|
}
|
|
};
|
|
}
|