mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 07:33:16 +00:00
Merge pull request #1374 from lightpanda-io/fix_context_lifetime
Fix context lifetime
This commit is contained in:
@@ -367,14 +367,18 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
||||
event._target = getAdjustedTarget(original_target, current_target);
|
||||
}
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
switch (listener.function) {
|
||||
.value => |value| try value.local().callWithThis(void, current_target, .{event}),
|
||||
.value => |value| try ls.toLocal(value).callWithThis(void, current_target, .{event}),
|
||||
.string => |string| {
|
||||
const str = try page.call_arena.dupeZ(u8, string.str());
|
||||
try self.page.js.eval(str, null);
|
||||
try ls.local.eval(str, null);
|
||||
},
|
||||
.object => |*obj_global| {
|
||||
const obj = obj_global.local();
|
||||
.object => |obj_global| {
|
||||
const obj = ls.toLocal(obj_global);
|
||||
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
||||
try handleEvent.callWithThis(void, obj, .{event});
|
||||
}
|
||||
|
||||
@@ -562,21 +562,24 @@ fn _documentIsComplete(self: *Page) !void {
|
||||
const event = try Event.initTrusted("load", .{}, self);
|
||||
// this event is weird, it's dispatched directly on the window, but
|
||||
// with the document as the target
|
||||
|
||||
var ls: JS.Local.Scope = undefined;
|
||||
self.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
event._target = self.document.asEventTarget();
|
||||
const on_load = if (self.window._on_load) |*g| g.local() else null;
|
||||
try self._event_manager.dispatchWithFunction(
|
||||
self.window.asEventTarget(),
|
||||
event,
|
||||
on_load,
|
||||
ls.toLocal(self.window._on_load),
|
||||
.{ .inject_target = false, .context = "page load" },
|
||||
);
|
||||
|
||||
const pageshow_event = try PageTransitionEvent.initTrusted("pageshow", .{}, self);
|
||||
const on_pageshow = if (self.window._on_pageshow) |*g| g.local() else null;
|
||||
try self._event_manager.dispatchWithFunction(
|
||||
self.window.asEventTarget(),
|
||||
pageshow_event.asEvent(),
|
||||
on_pageshow,
|
||||
ls.toLocal(self.window._on_pageshow),
|
||||
.{ .context = "page show" },
|
||||
);
|
||||
}
|
||||
@@ -748,10 +751,6 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
||||
var timer = try std.time.Timer.start();
|
||||
var ms_remaining = wait_ms;
|
||||
|
||||
var try_catch: JS.TryCatch = undefined;
|
||||
try_catch.init(self.js);
|
||||
defer try_catch.deinit();
|
||||
|
||||
var scheduler = &self.scheduler;
|
||||
var http_client = self._session.browser.http_client;
|
||||
|
||||
@@ -808,10 +807,6 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
||||
// it AFTER.
|
||||
const ms_to_next_task = try scheduler.run();
|
||||
|
||||
if (try_catch.caught(self.call_arena)) |caught| {
|
||||
log.info(.js, "page wait", .{ .caught = caught, .src = "scheduler" });
|
||||
}
|
||||
|
||||
const http_active = http_client.active;
|
||||
const total_network_activity = http_active + http_client.intercepted;
|
||||
if (self._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||
@@ -2005,8 +2000,12 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
||||
self._upgrading_element = node;
|
||||
defer self._upgrading_element = prev_upgrading;
|
||||
|
||||
var ls: JS.Local.Scope = undefined;
|
||||
self.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: JS.TryCatch.Caught = undefined;
|
||||
_ = def.constructor.local().newInstance(&caught) catch |err| {
|
||||
_ = ls.toLocal(def.constructor).newInstance(&caught) catch |err| {
|
||||
log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught });
|
||||
return node;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
const log = @import("../log.zig");
|
||||
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
||||
|
||||
|
||||
@@ -271,11 +271,15 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
||||
});
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
log.debug(.http, "script queue", .{
|
||||
.ctx = ctx,
|
||||
.url = remote_url.?,
|
||||
.element = element,
|
||||
.stack = page.js.stackTrace() catch "???",
|
||||
.stack = ls.local.stackTrace() catch "???",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -357,11 +361,15 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
||||
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self.page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
log.debug(.http, "script queue", .{
|
||||
.url = url,
|
||||
.ctx = "module",
|
||||
.referrer = referrer,
|
||||
.stack = self.page.js.stackTrace() catch "???",
|
||||
.stack = ls.local.stackTrace() catch "???",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -448,11 +456,15 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
||||
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self.page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
log.debug(.http, "script queue", .{
|
||||
.url = url,
|
||||
.ctx = "dynamic module",
|
||||
.referrer = referrer,
|
||||
.stack = self.page.js.stackTrace() catch "???",
|
||||
.stack = ls.local.stackTrace() catch "???",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -785,6 +797,12 @@ pub const Script = struct {
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const local = &ls.local;
|
||||
|
||||
// Handle importmap special case here: the content is a JSON containing
|
||||
// imports.
|
||||
if (self.kind == .importmap) {
|
||||
@@ -795,25 +813,24 @@ pub const Script = struct {
|
||||
.kind = self.kind,
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
self.executeCallback("error", script_element._on_error, page);
|
||||
self.executeCallback("error", local.toLocal(script_element._on_error), page);
|
||||
return;
|
||||
};
|
||||
self.executeCallback("load", script_element._on_load, page);
|
||||
self.executeCallback("load", local.toLocal(script_element._on_load), page);
|
||||
return;
|
||||
}
|
||||
|
||||
const js_context = page.js;
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
try_catch.init(js_context);
|
||||
try_catch.init(local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
const success = blk: {
|
||||
const content = self.source.content();
|
||||
switch (self.kind) {
|
||||
.javascript => _ = js_context.eval(content, url) catch break :blk false,
|
||||
.javascript => _ = local.eval(content, url) catch break :blk false,
|
||||
.module => {
|
||||
// We don't care about waiting for the evaluation here.
|
||||
js_context.module(false, content, url, cacheable) catch break :blk false;
|
||||
page.js.module(false, local, content, url, cacheable) catch break :blk false;
|
||||
},
|
||||
.importmap => unreachable, // handled before the try/catch.
|
||||
}
|
||||
@@ -833,7 +850,7 @@ pub const Script = struct {
|
||||
}
|
||||
|
||||
if (success) {
|
||||
self.executeCallback("load", script_element._on_load, page);
|
||||
self.executeCallback("load", local.toLocal(script_element._on_load), page);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -844,12 +861,11 @@ pub const Script = struct {
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
|
||||
self.executeCallback("error", script_element._on_error, page);
|
||||
self.executeCallback("error", local.toLocal(script_element._on_error), page);
|
||||
}
|
||||
|
||||
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function.Global, page: *Page) void {
|
||||
const cb_global = cb_ orelse return;
|
||||
const cb = cb_global.local();
|
||||
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function, page: *Page) void {
|
||||
const cb = cb_ orelse return;
|
||||
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const event = Event.initTrusted(typ, .{}, page) catch |err| {
|
||||
|
||||
@@ -22,7 +22,7 @@ const v8 = js.v8;
|
||||
|
||||
const Array = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.Array,
|
||||
|
||||
pub fn len(self: Array) usize {
|
||||
@@ -30,39 +30,37 @@ pub fn len(self: Array) usize {
|
||||
}
|
||||
|
||||
pub fn get(self: Array, index: u32) !js.Value {
|
||||
const ctx = self.ctx;
|
||||
const ctx = self.local.ctx;
|
||||
|
||||
const idx = js.Integer.init(ctx.isolate.handle, index);
|
||||
const handle = v8.v8__Object__Get(@ptrCast(self.handle), ctx.handle, idx.handle) orelse {
|
||||
const handle = v8.v8__Object__Get(@ptrCast(self.handle), self.local.handle, idx.handle) orelse {
|
||||
return error.JsException;
|
||||
};
|
||||
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
|
||||
const ctx = self.ctx;
|
||||
|
||||
const js_value = try ctx.zigValueToJs(value, opts);
|
||||
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.Caller.CallOpts) !bool {
|
||||
const js_value = try self.local.zigValueToJs(value, opts);
|
||||
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__SetAtIndex(@ptrCast(self.handle), ctx.handle, index, js_value.handle, &out);
|
||||
v8.v8__Object__SetAtIndex(@ptrCast(self.handle), self.local.handle, index, js_value.handle, &out);
|
||||
return out.has_value;
|
||||
}
|
||||
|
||||
pub fn toObject(self: Array) js.Object {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toValue(self: Array) js.Value {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
|
||||
569
src/browser/js/Caller.zig
Normal file
569
src/browser/js/Caller.zig
Normal file
@@ -0,0 +1,569 @@
|
||||
// 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 log = @import("../../log.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const js = @import("js.zig");
|
||||
const bridge = @import("bridge.zig");
|
||||
const Context = @import("Context.zig");
|
||||
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||
|
||||
const v8 = js.v8;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
const CALL_ARENA_RETAIN = 1024 * 16;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
const Caller = @This();
|
||||
local: js.Local,
|
||||
prev_local: ?*const js.Local,
|
||||
|
||||
// Takes the raw v8 isolate and extracts the context from it.
|
||||
pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void {
|
||||
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 ctx: *Context = @ptrFromInt(v8.v8__BigInt__Uint64Value(embedder_data, &lossless));
|
||||
|
||||
ctx.call_depth += 1;
|
||||
self.* = Caller{
|
||||
.local = .{
|
||||
.ctx = ctx,
|
||||
.handle = v8_context_handle.?,
|
||||
.call_arena = ctx.call_arena,
|
||||
.isolate = .{ .handle = v8_isolate },
|
||||
},
|
||||
.prev_local = ctx.local,
|
||||
};
|
||||
ctx.local = &self.local;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Caller) void {
|
||||
const ctx = self.local.ctx;
|
||||
const call_depth = ctx.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(ctx.call_arena.ptr));
|
||||
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
|
||||
}
|
||||
|
||||
ctx.call_depth = call_depth;
|
||||
ctx.local = self.prev_local;
|
||||
}
|
||||
|
||||
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, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle };
|
||||
|
||||
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{ .local = &self.local, .handle = new_this_handle };
|
||||
if (@typeInfo(ReturnType) == .error_union) {
|
||||
const non_error_res = res catch |err| return err;
|
||||
this = try self.local.mapZigInstanceToJs(new_this_handle, non_error_res);
|
||||
} else {
|
||||
this = try self.local.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.local.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, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle };
|
||||
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 args = try self.getArgs(F, 1, info);
|
||||
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||
const res = @call(.auto, func, args);
|
||||
info.getReturnValue().set(try self.local.zigValueToJs(res, opts));
|
||||
}
|
||||
|
||||
pub fn function(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = FunctionCallbackInfo{ .handle = handle };
|
||||
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 args = try self.getArgs(F, 0, info);
|
||||
const res = @call(.auto, func, args);
|
||||
info.getReturnValue().set(try self.local.zigValueToJs(res, opts));
|
||||
}
|
||||
|
||||
pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle };
|
||||
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 TaggedOpaque.fromJS(*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: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle };
|
||||
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: *const v8.Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args = try self.getArgs(F, 2, info);
|
||||
@field(args, "0") = try TaggedOpaque.fromJS(*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: *const v8.Name, js_value: *const v8.Value, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle };
|
||||
return self._setNamedIndex(T, func, name, .{ .local = &self.local, .handle = 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: *const v8.Name, js_value: js.Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args: ParameterTypes(F) = undefined;
|
||||
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
@field(args, "2") = try self.local.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
|
||||
if (@typeInfo(F).@"fn".params.len == 4) {
|
||||
@field(args, "3") = self.local.ctx.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: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(self.local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const info = PropertyCallbackInfo{ .handle = handle };
|
||||
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: *const v8.Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||
const F = @TypeOf(func);
|
||||
var args: ParameterTypes(F) = undefined;
|
||||
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
if (@typeInfo(F).@"fn".params.len == 3) {
|
||||
@field(args, "2") = self.local.ctx.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.local.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: *const Caller, name: *const v8.Name) ![]const u8 {
|
||||
return self.local.valueHandleToString(@ptrCast(name), .{});
|
||||
}
|
||||
|
||||
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
||||
const isolate = self.local.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.local.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 local = &self.local;
|
||||
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)) = local.ctx.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(@intCast(last_js_parameter), local);
|
||||
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 local.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1);
|
||||
for (arr, last_js_parameter..) |*a, i| {
|
||||
a.* = try local.jsValueToZig(slice_type, info.getArg(@intCast(i), local));
|
||||
}
|
||||
@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_val = info.getArg(@intCast(i), local);
|
||||
@field(args, tupleFieldName(field_index)) = local.jsValueToZig(param.type.?, js_val) 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.local.stackTrace() catch |err1| @errorName(err1),
|
||||
});
|
||||
}
|
||||
|
||||
fn serializeFunctionArgs(self: *Caller, info: FunctionCallbackInfo) ![]const u8 {
|
||||
const local = &self.local;
|
||||
var buf = std.Io.Writer.Allocating.init(local.call_arena);
|
||||
|
||||
const separator = log.separator();
|
||||
for (0..info.length()) |i| {
|
||||
try buf.writer.print("{s}{d} - ", .{ separator, i + 1 });
|
||||
const js_value = info.getArg(@intCast(i), local);
|
||||
try local.debugValue(js_value, &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;
|
||||
}
|
||||
|
||||
// These wrap the raw v8 C API to provide a cleaner interface.
|
||||
pub const FunctionCallbackInfo = struct {
|
||||
handle: *const v8.FunctionCallbackInfo,
|
||||
|
||||
pub fn length(self: FunctionCallbackInfo) u32 {
|
||||
return @intCast(v8.v8__FunctionCallbackInfo__Length(self.handle));
|
||||
}
|
||||
|
||||
pub fn getArg(self: FunctionCallbackInfo, index: u32, local: *const js.Local) js.Value {
|
||||
return .{ .local = local, .handle = v8.v8__FunctionCallbackInfo__INDEX(self.handle, @intCast(index)).? };
|
||||
}
|
||||
|
||||
pub fn getThis(self: FunctionCallbackInfo) *const v8.Object {
|
||||
return v8.v8__FunctionCallbackInfo__This(self.handle).?;
|
||||
}
|
||||
|
||||
pub 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);
|
||||
}
|
||||
};
|
||||
|
||||
pub const PropertyCallbackInfo = struct {
|
||||
handle: *const v8.PropertyCallbackInfo,
|
||||
|
||||
pub fn getThis(self: PropertyCallbackInfo) *const v8.Object {
|
||||
return v8.v8__PropertyCallbackInfo__This(self.handle).?;
|
||||
}
|
||||
|
||||
pub 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,
|
||||
|
||||
pub fn set(self: ReturnValue, value: anytype) void {
|
||||
const T = @TypeOf(value);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setValueHandle(self: ReturnValue, handle: *const v8.Value) void {
|
||||
v8.v8__ReturnValue__Set(self.handle, handle);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -189,19 +189,27 @@ pub fn dumpMemoryStats(self: *Env) void {
|
||||
|
||||
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
|
||||
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
|
||||
const isolate_handle = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
|
||||
const js_isolate = js.Isolate{ .handle = isolate_handle };
|
||||
const context = Context.fromIsolate(js_isolate);
|
||||
const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
|
||||
const js_isolate = js.Isolate{ .handle = v8_isolate };
|
||||
const ctx = Context.fromIsolate(js_isolate);
|
||||
|
||||
const local = js.Local{
|
||||
.ctx = ctx,
|
||||
.isolate = js_isolate,
|
||||
.handle = v8.v8__Isolate__GetCurrentContext(v8_isolate).?,
|
||||
.call_arena = ctx.call_arena,
|
||||
};
|
||||
|
||||
const value =
|
||||
if (v8.v8__PromiseRejectMessage__GetValue(&message_handle)) |v8_value|
|
||||
context.valueToString(.{ .ctx = context, .handle = v8_value }, .{}) catch |err| @errorName(err)
|
||||
// @HandleScope - no reason to create a js.Context here
|
||||
local.valueHandleToString(v8_value, .{}) catch |err| @errorName(err)
|
||||
else
|
||||
"no value";
|
||||
|
||||
log.debug(.js, "unhandled rejection", .{
|
||||
.value = value,
|
||||
.stack = context.stackTrace() catch |err| @errorName(err) orelse "???",
|
||||
.stack = local.stackTrace() catch |err| @errorName(err) orelse "???",
|
||||
.note = "This should be updated to call window.unhandledrejection",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ context_arena: ArenaAllocator,
|
||||
// does all the work, but having all page-specific data structures
|
||||
// grouped together helps keep things clean.
|
||||
context: ?Context = null,
|
||||
persisted_context: ?js.Global(Context) = null,
|
||||
|
||||
// no init, must be initialized via env.newExecutionWorld()
|
||||
|
||||
@@ -77,43 +76,41 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
||||
const isolate = env.isolate;
|
||||
const arena = self.context_arena.allocator();
|
||||
|
||||
const persisted_context: js.Global(Context) = blk: {
|
||||
var temp_scope: js.HandleScope = undefined;
|
||||
temp_scope.init(isolate);
|
||||
defer temp_scope.deinit();
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
// Getting this into the snapshot is tricky (anything involving the
|
||||
// global is tricky). Easier to do here
|
||||
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate.handle, env.templates);
|
||||
v8.v8__ObjectTemplate__SetNamedHandler(global_template, &.{
|
||||
.getter = bridge.unknownPropertyCallback,
|
||||
.setter = null,
|
||||
.query = null,
|
||||
.deleter = null,
|
||||
.enumerator = null,
|
||||
.definer = null,
|
||||
.descriptor = null,
|
||||
.data = null,
|
||||
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
||||
});
|
||||
// Getting this into the snapshot is tricky (anything involving the
|
||||
// global is tricky). Easier to do here
|
||||
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate.handle, env.templates);
|
||||
v8.v8__ObjectTemplate__SetNamedHandler(global_template, &.{
|
||||
.getter = bridge.unknownPropertyCallback,
|
||||
.setter = null,
|
||||
.query = null,
|
||||
.deleter = null,
|
||||
.enumerator = null,
|
||||
.definer = null,
|
||||
.descriptor = null,
|
||||
.data = null,
|
||||
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
||||
});
|
||||
|
||||
const context_handle = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
||||
break :blk js.Global(Context).init(isolate.handle, context_handle);
|
||||
};
|
||||
const v8_context = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
||||
|
||||
// Create the v8::Context and wrap it in a v8::Global
|
||||
var context_global: v8.Global = undefined;
|
||||
v8.v8__Global__New(isolate.handle, v8_context, &context_global);
|
||||
|
||||
// our window wrapped in a v8::Global
|
||||
const global_obj = v8.v8__Context__Global(v8_context).?;
|
||||
var global_global: v8.Global = undefined;
|
||||
v8.v8__Global__New(isolate.handle, global_obj, &global_global);
|
||||
|
||||
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
|
||||
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
|
||||
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
|
||||
const v8_context = persisted_context.local();
|
||||
var handle_scope: ?js.HandleScope = null;
|
||||
if (enter) {
|
||||
handle_scope = @as(js.HandleScope, undefined);
|
||||
handle_scope.?.init(isolate);
|
||||
v8.v8__Context__Enter(v8_context);
|
||||
}
|
||||
errdefer if (enter) {
|
||||
v8.v8__Context__Exit(v8_context);
|
||||
handle_scope.?.deinit();
|
||||
};
|
||||
|
||||
const context_id = env.context_id;
|
||||
@@ -122,24 +119,24 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
||||
self.context = Context{
|
||||
.page = page,
|
||||
.id = context_id,
|
||||
.entered = enter,
|
||||
.isolate = isolate,
|
||||
.handle = v8_context,
|
||||
.handle = context_global,
|
||||
.templates = env.templates,
|
||||
.handle_scope = handle_scope,
|
||||
.script_manager = &page._script_manager,
|
||||
.call_arena = page.call_arena,
|
||||
.arena = arena,
|
||||
};
|
||||
self.persisted_context = persisted_context;
|
||||
|
||||
var context = &self.context.?;
|
||||
try context.identity_map.putNoClobber(arena, @intFromPtr(page.window), global_global);
|
||||
|
||||
// Store a pointer to our context inside the v8 context so that, given
|
||||
// a v8 context, we can get our context out
|
||||
const data = isolate.initBigInt(@intFromPtr(context));
|
||||
v8.v8__Context__SetEmbedderData(context.handle, 1, @ptrCast(data.handle));
|
||||
const data = isolate.initBigInt(@intFromPtr(&self.context.?));
|
||||
v8.v8__Context__SetEmbedderData(v8_context, 1, @ptrCast(data.handle));
|
||||
|
||||
try context.setupGlobal();
|
||||
return context;
|
||||
return &self.context.?;
|
||||
}
|
||||
|
||||
pub fn removeContext(self: *ExecutionWorld) void {
|
||||
@@ -147,9 +144,6 @@ pub fn removeContext(self: *ExecutionWorld) void {
|
||||
context.deinit();
|
||||
self.context = null;
|
||||
|
||||
self.persisted_context.?.deinit();
|
||||
self.persisted_context = null;
|
||||
|
||||
self.env.isolate.notifyContextDisposed();
|
||||
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const Function = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
this: ?*const v8.Object = null,
|
||||
handle: *const v8.Function,
|
||||
|
||||
@@ -34,34 +34,35 @@ pub const Result = struct {
|
||||
};
|
||||
|
||||
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 self.ctx.zigValueToJs(value, .{})).handle;
|
||||
(try local.zigValueToJs(value, .{})).handle;
|
||||
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = local,
|
||||
.this = this_obj,
|
||||
.handle = self.handle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
|
||||
const ctx = self.ctx;
|
||||
const local = self.local;
|
||||
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
try_catch.init(ctx);
|
||||
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, ctx.handle, 0, null) orelse {
|
||||
caught.* = try_catch.caughtOrError(ctx.call_arena, error.Unknown);
|
||||
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 .{
|
||||
.ctx = ctx,
|
||||
.local = local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
@@ -77,17 +78,17 @@ pub fn tryCall(self: *const Function, comptime T: type, args: anytype, caught: *
|
||||
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
|
||||
try_catch.init(self.ctx);
|
||||
try_catch.init(self.local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
return self.callWithThis(T, this, args) catch |err| {
|
||||
caught.* = try_catch.caughtOrError(self.ctx.call_arena, err);
|
||||
caught.* = try_catch.caughtOrError(self.local.ctx.call_arena, err);
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
|
||||
const ctx = self.ctx;
|
||||
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
|
||||
@@ -98,6 +99,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
||||
// 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;
|
||||
@@ -106,7 +108,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
||||
if (@TypeOf(this) == js.Object) {
|
||||
break :blk this;
|
||||
}
|
||||
break :blk try ctx.zigValueToJs(this, .{});
|
||||
break :blk try local.zigValueToJs(this, .{});
|
||||
};
|
||||
|
||||
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
||||
@@ -116,15 +118,15 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
||||
const fields = s.fields;
|
||||
var js_args: [fields.len]*const v8.Value = undefined;
|
||||
inline for (fields, 0..) |f, i| {
|
||||
js_args[i] = (try ctx.zigValueToJs(@field(aargs, f.name), .{})).handle;
|
||||
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 ctx.call_arena.alloc(*const v8.Value, args.len);
|
||||
var values = try local.call_arena.alloc(*const v8.Value, args.len);
|
||||
for (args, 0..) |a, i| {
|
||||
values[i] = (try ctx.zigValueToJs(a, .{})).handle;
|
||||
values[i] = (try local.zigValueToJs(a, .{})).handle;
|
||||
}
|
||||
break :blk values;
|
||||
},
|
||||
@@ -132,7 +134,7 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
||||
};
|
||||
|
||||
const c_args = @as(?[*]const ?*v8.Value, @ptrCast(js_args.ptr));
|
||||
const handle = v8.v8__Function__Call(self.handle, ctx.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
|
||||
const handle = v8.v8__Function__Call(self.handle, local.handle, js_this.handle, @as(c_int, @intCast(js_args.len)), c_args) orelse {
|
||||
// std.debug.print("CB ERR: {s}\n", .{self.src() catch "???"});
|
||||
return error.JSExecCallback;
|
||||
};
|
||||
@@ -140,13 +142,13 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
||||
if (@typeInfo(T) == .void) {
|
||||
return {};
|
||||
}
|
||||
return ctx.jsValueToZig(T, .{ .ctx = ctx, .handle = handle });
|
||||
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.ctx.handle).?;
|
||||
const handle = if (self.this) |t| t else v8.v8__Context__Global(self.local.handle).?;
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
@@ -156,30 +158,24 @@ pub fn src(self: *const Function) ![]const u8 {
|
||||
}
|
||||
|
||||
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
|
||||
const ctx = self.ctx;
|
||||
const key = ctx.isolate.initStringHandle(name);
|
||||
const handle = v8.v8__Object__Get(self.handle, ctx.handle, key) orelse {
|
||||
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 .{
|
||||
.ctx = ctx,
|
||||
.local = local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn persist(self: *const Function) !Global {
|
||||
var ctx = self.ctx;
|
||||
|
||||
var ctx = self.local.ctx;
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||
|
||||
try ctx.global_functions.append(ctx.arena, global);
|
||||
|
||||
return .{
|
||||
.handle = global,
|
||||
.ctx = ctx,
|
||||
};
|
||||
return .{ .handle = global };
|
||||
}
|
||||
|
||||
pub fn persistWithThis(self: *const Function, value: anytype) !Global {
|
||||
@@ -189,16 +185,15 @@ pub fn persistWithThis(self: *const Function, value: anytype) !Global {
|
||||
|
||||
pub const Global = struct {
|
||||
handle: v8.Global,
|
||||
ctx: *js.Context,
|
||||
|
||||
pub fn deinit(self: *Global) void {
|
||||
v8.v8__Global__Reset(&self.handle);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Global) Function {
|
||||
pub fn local(self: *const Global, l: *const js.Local) Function {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
||||
.local = l,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const js = @import("js.zig");
|
||||
const v8 = js.v8;
|
||||
|
||||
const Context = @import("Context.zig");
|
||||
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const RndGen = std.Random.DefaultPrng;
|
||||
@@ -36,7 +36,7 @@ client: Client,
|
||||
channel: Channel,
|
||||
session: Session,
|
||||
rnd: RndGen = RndGen.init(0),
|
||||
default_context: ?*const v8.Context = null,
|
||||
default_context: ?v8.Global,
|
||||
|
||||
// We expect allocator to be an arena
|
||||
// Note: This initializes the pre-allocated inspector in-place
|
||||
@@ -96,9 +96,9 @@ pub fn init(self: *Inspector, isolate: *v8.Isolate, ctx: anytype) !void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Inspector) void {
|
||||
var temp_scope: v8.HandleScope = undefined;
|
||||
v8.v8__HandleScope__CONSTRUCT(&temp_scope, self.isolate);
|
||||
defer v8.v8__HandleScope__DESTRUCT(&temp_scope);
|
||||
var hs: v8.HandleScope = undefined;
|
||||
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate);
|
||||
defer v8.v8__HandleScope__DESTRUCT(&hs);
|
||||
|
||||
self.session.deinit();
|
||||
self.client.deinit();
|
||||
@@ -128,7 +128,7 @@ pub fn send(self: *const Inspector, msg: []const u8) void {
|
||||
// - is_default_context: Whether the execution context is default, should match the auxData
|
||||
pub fn contextCreated(
|
||||
self: *Inspector,
|
||||
context: *const Context,
|
||||
local: *const js.Local,
|
||||
name: []const u8,
|
||||
origin: []const u8,
|
||||
aux_data: []const u8,
|
||||
@@ -143,11 +143,11 @@ pub fn contextCreated(
|
||||
aux_data.ptr,
|
||||
aux_data.len,
|
||||
CONTEXT_GROUP_ID,
|
||||
context.handle,
|
||||
local.handle,
|
||||
);
|
||||
|
||||
if (is_default_context) {
|
||||
self.default_context = context.handle;
|
||||
self.default_context = local.ctx.handle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,41 +158,42 @@ pub fn contextCreated(
|
||||
// we'll create it and track it for cleanup when the context ends.
|
||||
pub fn getRemoteObject(
|
||||
self: *const Inspector,
|
||||
context: *Context,
|
||||
local: *const js.Local,
|
||||
group: []const u8,
|
||||
value: anytype,
|
||||
) !RemoteObject {
|
||||
const js_value = try context.zigValueToJs(value, .{});
|
||||
const js_val = try local.zigValueToJs(value, .{});
|
||||
|
||||
// We do not want to expose this as a parameter for now
|
||||
const generate_preview = false;
|
||||
return self.session.wrapObject(
|
||||
context.isolate.handle,
|
||||
context.handle,
|
||||
js_value.handle,
|
||||
local.isolate.handle,
|
||||
local.handle,
|
||||
js_val.handle,
|
||||
group,
|
||||
generate_preview,
|
||||
);
|
||||
}
|
||||
|
||||
// Gets a value by object ID regardless of which context it is in.
|
||||
// Our TaggedAnyOpaque stores the "resolved" ptr value (the most specific _type,
|
||||
// Our TaggedOpaque stores the "resolved" ptr value (the most specific _type,
|
||||
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
|
||||
// the pointer to the Node, so we need to use the same resolution mechanism which
|
||||
// is used when we're calling a function to turn the Div into a Node, which is
|
||||
// what Context.typeTaggedAnyOpaque does.
|
||||
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8) !*anyopaque {
|
||||
// what TaggedOpaque.fromJS does.
|
||||
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8, local: *js.Local) !*anyopaque {
|
||||
// just to indicate that the caller is responsible for ensure there's a local environment
|
||||
_ = local;
|
||||
const unwrapped = try self.session.unwrapObject(allocator, object_id);
|
||||
// The values context and groupId are not used here
|
||||
const js_val = unwrapped.value;
|
||||
if (!v8.v8__Value__IsObject(js_val)) {
|
||||
return error.ObjectIdIsNotANode;
|
||||
}
|
||||
|
||||
const Node = @import("../webapi/Node.zig");
|
||||
// Cast to *const v8.Object for typeTaggedAnyOpaque
|
||||
return Context.typeTaggedAnyOpaque(*Node, @ptrCast(js_val)) catch {
|
||||
return error.ObjectIdIsNotANode;
|
||||
};
|
||||
return TaggedOpaque.fromJS(*Node, @ptrCast(js_val)) catch return error.ObjectIdIsNotANode;
|
||||
}
|
||||
|
||||
pub const RemoteObject = struct {
|
||||
@@ -399,7 +400,7 @@ fn fromData(data: *anyopaque) *Inspector {
|
||||
return @ptrCast(@alignCast(data));
|
||||
}
|
||||
|
||||
pub fn getTaggedAnyOpaque(value: *const v8.Value) ?*js.TaggedAnyOpaque {
|
||||
pub fn getTaggedOpaque(value: *const v8.Value) ?*TaggedOpaque {
|
||||
if (!v8.v8__Value__IsObject(value)) {
|
||||
return null;
|
||||
}
|
||||
@@ -469,7 +470,8 @@ pub export fn v8_inspector__Client__IMPL__ensureDefaultContextInGroup(
|
||||
data: *anyopaque,
|
||||
) callconv(.c) ?*const v8.Context {
|
||||
const inspector: *Inspector = @ptrCast(@alignCast(data));
|
||||
return inspector.default_context;
|
||||
const global_handle = inspector.default_context orelse return null;
|
||||
return v8.v8__Global__Get(&global_handle, inspector.isolate);
|
||||
}
|
||||
|
||||
pub export fn v8_inspector__Channel__IMPL__sendResponse(
|
||||
|
||||
1326
src/browser/js/Local.zig
Normal file
1326
src/browser/js/Local.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ const v8 = js.v8;
|
||||
|
||||
const Module = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.Module,
|
||||
|
||||
pub const Status = enum(u32) {
|
||||
@@ -39,21 +39,21 @@ pub fn getStatus(self: Module) Status {
|
||||
|
||||
pub fn getException(self: Module) js.Value {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = v8.v8__Module__GetException(self.handle).?,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getModuleRequests(self: Module) Requests {
|
||||
return .{
|
||||
.ctx = self.ctx.handle,
|
||||
.context_handle = self.local.handle,
|
||||
.handle = v8.v8__Module__GetModuleRequests(self.handle).?,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Module__InstantiateModule(self.handle, self.ctx.handle, cb, &out);
|
||||
v8.v8__Module__InstantiateModule(self.handle, self.local.handle, cb, &out);
|
||||
if (out.has_value) {
|
||||
return out.value;
|
||||
}
|
||||
@@ -61,15 +61,14 @@ pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
||||
}
|
||||
|
||||
pub fn evaluate(self: Module) !js.Value {
|
||||
const ctx = self.ctx;
|
||||
const res = v8.v8__Module__Evaluate(self.handle, ctx.handle) orelse return error.JsException;
|
||||
const res = v8.v8__Module__Evaluate(self.handle, self.local.handle) orelse return error.JsException;
|
||||
|
||||
if (self.getStatus() == .kErrored) {
|
||||
return error.JsException;
|
||||
}
|
||||
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.local = self.local,
|
||||
.handle = res,
|
||||
};
|
||||
}
|
||||
@@ -80,7 +79,7 @@ pub fn getIdentityHash(self: Module) u32 {
|
||||
|
||||
pub fn getModuleNamespace(self: Module) js.Value {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = v8.v8__Module__GetModuleNamespace(self.handle).?,
|
||||
};
|
||||
}
|
||||
@@ -90,28 +89,24 @@ pub fn getScriptId(self: Module) u32 {
|
||||
}
|
||||
|
||||
pub fn persist(self: Module) !Global {
|
||||
var ctx = self.ctx;
|
||||
var ctx = self.local.ctx;
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||
try ctx.global_modules.append(ctx.arena, global);
|
||||
return .{
|
||||
.handle = global,
|
||||
.ctx = ctx,
|
||||
};
|
||||
return .{ .handle = global };
|
||||
}
|
||||
|
||||
pub const Global = struct {
|
||||
handle: v8.Global,
|
||||
ctx: *js.Context,
|
||||
|
||||
pub fn deinit(self: *Global) void {
|
||||
v8.v8__Global__Reset(&self.handle);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Global) Module {
|
||||
pub fn local(self: *const Global, l: *const js.Local) Module {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
||||
.local = l,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,15 +116,15 @@ pub const Global = struct {
|
||||
};
|
||||
|
||||
const Requests = struct {
|
||||
ctx: *const v8.Context,
|
||||
handle: *const v8.FixedArray,
|
||||
context_handle: *const v8.Context,
|
||||
|
||||
pub fn len(self: Requests) usize {
|
||||
return @intCast(v8.v8__FixedArray__Length(self.handle));
|
||||
}
|
||||
|
||||
pub fn get(self: Requests, idx: usize) Request {
|
||||
return .{ .handle = v8.v8__FixedArray__Get(self.handle, self.ctx, @intCast(idx)).? };
|
||||
return .{ .handle = v8.v8__FixedArray__Get(self.handle, self.context_handle, @intCast(idx)).? };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const Object = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.Object,
|
||||
|
||||
pub fn getId(self: Object) u32 {
|
||||
@@ -36,11 +36,11 @@ pub fn getId(self: Object) u32 {
|
||||
}
|
||||
|
||||
pub fn has(self: Object, key: anytype) bool {
|
||||
const ctx = self.ctx;
|
||||
const ctx = self.local.ctx;
|
||||
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__Has(self.handle, self.ctx.handle, key_handle, &out);
|
||||
v8.v8__Object__Has(self.handle, self.local.handle, key_handle, &out);
|
||||
if (out.has_value) {
|
||||
return out.value;
|
||||
}
|
||||
@@ -48,34 +48,34 @@ pub fn has(self: Object, key: anytype) bool {
|
||||
}
|
||||
|
||||
pub fn get(self: Object, key: anytype) !js.Value {
|
||||
const ctx = self.ctx;
|
||||
const ctx = self.local.ctx;
|
||||
|
||||
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, key_handle) orelse return error.JsException;
|
||||
const js_val_handle = v8.v8__Object__Get(self.handle, self.local.handle, key_handle) orelse return error.JsException;
|
||||
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.local = self.local,
|
||||
.handle = js_val_handle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
|
||||
const ctx = self.ctx;
|
||||
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.Caller.CallOpts) !bool {
|
||||
const ctx = self.local.ctx;
|
||||
|
||||
const js_value = try ctx.zigValueToJs(value, opts);
|
||||
const js_value = try self.local.zigValueToJs(value, opts);
|
||||
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__Set(self.handle, ctx.handle, key_handle, js_value.handle, &out);
|
||||
v8.v8__Object__Set(self.handle, self.local.handle, key_handle, js_value.handle, &out);
|
||||
return out.has_value;
|
||||
}
|
||||
|
||||
pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr: v8.PropertyAttribute) ?bool {
|
||||
const ctx = self.ctx;
|
||||
const ctx = self.local.ctx;
|
||||
const name_handle = ctx.isolate.initStringHandle(name);
|
||||
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Object__DefineOwnProperty(self.handle, ctx.handle, @ptrCast(name_handle), value.handle, attr, &out);
|
||||
v8.v8__Object__DefineOwnProperty(self.handle, self.local.handle, @ptrCast(name_handle), value.handle, attr, &out);
|
||||
|
||||
if (out.has_value) {
|
||||
return out.value;
|
||||
@@ -85,52 +85,49 @@ pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr:
|
||||
}
|
||||
|
||||
pub fn toString(self: Object) ![]const u8 {
|
||||
return self.ctx.valueToString(self.toValue(), .{});
|
||||
return self.local.ctx.valueToString(self.toValue(), .{});
|
||||
}
|
||||
|
||||
pub fn toValue(self: Object) js.Value {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Object, writer: *std.Io.Writer) !void {
|
||||
if (comptime IS_DEBUG) {
|
||||
return self.ctx.debugValue(self.toValue(), writer);
|
||||
return self.local.ctx.debugValue(self.toValue(), writer);
|
||||
}
|
||||
const str = self.toString() catch return error.WriteFailed;
|
||||
return writer.writeAll(str);
|
||||
}
|
||||
|
||||
pub fn persist(self: Object) !Global {
|
||||
var ctx = self.ctx;
|
||||
var ctx = self.local.ctx;
|
||||
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||
|
||||
try ctx.global_objects.append(ctx.arena, global);
|
||||
|
||||
return .{
|
||||
.handle = global,
|
||||
.ctx = ctx,
|
||||
};
|
||||
return .{ .handle = global };
|
||||
}
|
||||
|
||||
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
|
||||
if (self.isNullOrUndefined()) {
|
||||
return null;
|
||||
}
|
||||
const ctx = self.ctx;
|
||||
const local = self.local;
|
||||
|
||||
const js_name = ctx.isolate.initStringHandle(name);
|
||||
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, js_name) orelse return error.JsException;
|
||||
const js_name = local.isolate.initStringHandle(name);
|
||||
const js_val_handle = v8.v8__Object__Get(self.handle, local.handle, js_name) orelse return error.JsException;
|
||||
|
||||
if (v8.v8__Value__IsFunction(js_val_handle) == false) {
|
||||
return null;
|
||||
}
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.local = local,
|
||||
.handle = @ptrCast(js_val_handle),
|
||||
};
|
||||
}
|
||||
@@ -145,51 +142,48 @@ pub fn isNullOrUndefined(self: Object) bool {
|
||||
}
|
||||
|
||||
pub fn getOwnPropertyNames(self: Object) js.Array {
|
||||
const handle = v8.v8__Object__GetOwnPropertyNames(self.handle, self.ctx.handle).?;
|
||||
const handle = v8.v8__Object__GetOwnPropertyNames(self.handle, self.local.handle).?;
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getPropertyNames(self: Object) js.Array {
|
||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.ctx.handle).?;
|
||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.local.handle).?;
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn nameIterator(self: Object) NameIterator {
|
||||
const ctx = self.ctx;
|
||||
|
||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, ctx.handle).?;
|
||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.local.handle).?;
|
||||
const count = v8.v8__Array__Length(handle);
|
||||
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.local = self.local,
|
||||
.handle = handle,
|
||||
.count = count,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toZig(self: Object, comptime T: type) !T {
|
||||
const js_value = js.Value{ .ctx = self.ctx, .handle = @ptrCast(self.handle) };
|
||||
return self.ctx.jsValueToZig(T, js_value);
|
||||
const js_value = js.Value{ .local = self.local, .handle = @ptrCast(self.handle) };
|
||||
return self.local.jsValueToZig(T, js_value);
|
||||
}
|
||||
|
||||
pub const Global = struct {
|
||||
handle: v8.Global,
|
||||
ctx: *js.Context,
|
||||
|
||||
pub fn deinit(self: *Global) void {
|
||||
v8.v8__Global__Reset(&self.handle);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Global) Object {
|
||||
pub fn local(self: *const Global, l: *const js.Local) Object {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
||||
.local = l,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,7 +195,7 @@ pub const Global = struct {
|
||||
pub const NameIterator = struct {
|
||||
count: u32,
|
||||
idx: u32 = 0,
|
||||
ctx: *Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.Array,
|
||||
|
||||
pub fn next(self: *NameIterator) !?[]const u8 {
|
||||
@@ -211,8 +205,8 @@ pub const NameIterator = struct {
|
||||
}
|
||||
self.idx += 1;
|
||||
|
||||
const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.ctx.handle, idx) orelse return error.JsException;
|
||||
const js_val = js.Value{ .ctx = self.ctx, .handle = js_val_handle };
|
||||
return try self.ctx.valueToString(js_val, .{});
|
||||
const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.local.handle, idx) orelse return error.JsException;
|
||||
const js_val = js.Value{ .local = self.local, .handle = js_val_handle };
|
||||
return try self.local.valueToString(js_val, .{});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,63 +21,51 @@ const v8 = js.v8;
|
||||
|
||||
const Promise = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.Promise,
|
||||
|
||||
pub fn toObject(self: Promise) js.Object {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toValue(self: Promise) js.Value {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn thenAndCatch(self: Promise, on_fulfilled: js.Function, on_rejected: js.Function) !Promise {
|
||||
if (v8.v8__Promise__Then2(self.handle, self.ctx.handle, on_fulfilled.handle, on_rejected.handle)) |handle| {
|
||||
if (v8.v8__Promise__Then2(self.handle, self.local.handle, on_fulfilled.handle, on_rejected.handle)) |handle| {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = handle,
|
||||
};
|
||||
}
|
||||
return error.PromiseChainFailed;
|
||||
}
|
||||
pub fn persist(self: Promise) !Global {
|
||||
var ctx = self.ctx;
|
||||
var ctx = self.local.ctx;
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||
try ctx.global_promises.append(ctx.arena, global);
|
||||
return .{
|
||||
.handle = global,
|
||||
.ctx = ctx,
|
||||
};
|
||||
return .{ .handle = global };
|
||||
}
|
||||
|
||||
pub const Global = struct {
|
||||
handle: v8.Global,
|
||||
ctx: *js.Context,
|
||||
|
||||
pub fn deinit(self: *Global) void {
|
||||
v8.v8__Global__Reset(&self.handle);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Global) Promise {
|
||||
pub fn local(self: *const Global, l: *const js.Local) Promise {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
||||
.local = l,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isEqual(self: *const Global, other: Promise) bool {
|
||||
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
||||
}
|
||||
|
||||
pub fn promise(self: *const Global) Promise {
|
||||
return self.local();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,19 +22,19 @@ const log = @import("../../log.zig");
|
||||
|
||||
const PromiseResolver = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.PromiseResolver,
|
||||
|
||||
pub fn init(ctx: *js.Context) PromiseResolver {
|
||||
pub fn init(local: *const js.Local) PromiseResolver {
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.handle = v8.v8__Promise__Resolver__New(ctx.handle).?,
|
||||
.local = local,
|
||||
.handle = v8.v8__Promise__Resolver__New(local.handle).?,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn promise(self: PromiseResolver) js.Promise {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = v8.v8__Promise__Resolver__GetPromise(self.handle).?,
|
||||
};
|
||||
}
|
||||
@@ -46,15 +46,15 @@ pub fn resolve(self: PromiseResolver, comptime source: []const u8, value: anytyp
|
||||
}
|
||||
|
||||
fn _resolve(self: PromiseResolver, value: anytype) !void {
|
||||
const ctx: *js.Context = @constCast(self.ctx);
|
||||
const js_value = try ctx.zigValueToJs(value, .{});
|
||||
const local = self.local;
|
||||
const js_val = try local.zigValueToJs(value, .{});
|
||||
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Promise__Resolver__Resolve(self.handle, self.ctx.handle, js_value.handle, &out);
|
||||
v8.v8__Promise__Resolver__Resolve(self.handle, self.local.handle, js_val.handle, &out);
|
||||
if (!out.has_value or !out.value) {
|
||||
return error.FailedToResolvePromise;
|
||||
}
|
||||
ctx.runMicrotasks();
|
||||
local.ctx.runMicrotasks();
|
||||
}
|
||||
|
||||
pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void {
|
||||
@@ -64,44 +64,36 @@ pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype
|
||||
}
|
||||
|
||||
fn _reject(self: PromiseResolver, value: anytype) !void {
|
||||
const ctx = self.ctx;
|
||||
const js_value = try ctx.zigValueToJs(value, .{});
|
||||
const local = self.local;
|
||||
const js_val = try local.zigValueToJs(value, .{});
|
||||
|
||||
var out: v8.MaybeBool = undefined;
|
||||
v8.v8__Promise__Resolver__Reject(self.handle, ctx.handle, js_value.handle, &out);
|
||||
v8.v8__Promise__Resolver__Reject(self.handle, local.handle, js_val.handle, &out);
|
||||
if (!out.has_value or !out.value) {
|
||||
return error.FailedToRejectPromise;
|
||||
}
|
||||
ctx.runMicrotasks();
|
||||
local.ctx.runMicrotasks();
|
||||
}
|
||||
|
||||
pub fn persist(self: PromiseResolver) !Global {
|
||||
var ctx = self.ctx;
|
||||
var ctx = self.local.ctx;
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||
try ctx.global_promise_resolvers.append(ctx.arena, global);
|
||||
return .{
|
||||
.handle = global,
|
||||
.ctx = ctx,
|
||||
};
|
||||
return .{ .handle = global };
|
||||
}
|
||||
|
||||
pub const Global = struct {
|
||||
handle: v8.Global,
|
||||
ctx: *js.Context,
|
||||
|
||||
pub fn deinit(self: *Global) void {
|
||||
v8.v8__Global__Reset(&self.handle);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Global) PromiseResolver {
|
||||
pub fn local(self: *const Global, l: *const js.Local) PromiseResolver {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
||||
.local = l,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isEqual(self: *const Global, other: PromiseResolver) bool {
|
||||
return v8.v8__Global__IsEqual(&self.handle, other.handle);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ const v8 = js.v8;
|
||||
|
||||
const String = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.String,
|
||||
|
||||
pub const ToZigOpts = struct {
|
||||
@@ -41,8 +41,8 @@ pub fn toZigZ(self: String, opts: ToZigOpts) ![:0]u8 {
|
||||
}
|
||||
|
||||
fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
||||
const isolate = self.ctx.isolate.handle;
|
||||
const allocator = opts.allocator orelse self.ctx.call_arena;
|
||||
const isolate = self.local.isolate.handle;
|
||||
const allocator = opts.allocator orelse self.local.ctx.call_arena;
|
||||
const len: u32 = @intCast(v8.v8__String__Utf8Length(self.handle, isolate));
|
||||
const buf = if (null_terminate) try allocator.allocSentinel(u8, len, 0) else try allocator.alloc(u8, len);
|
||||
|
||||
|
||||
156
src/browser/js/TaggedOpaque.zig
Normal file
156
src/browser/js/TaggedOpaque.zig
Normal file
@@ -0,0 +1,156 @@
|
||||
// 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 js = @import("js.zig");
|
||||
const v8 = js.v8;
|
||||
const bridge = js.bridge;
|
||||
|
||||
// When we return a Zig object to V8, we put it on the heap and pass it into
|
||||
// v8 as an *anyopaque (i.e. void *). When V8 gives us back the value, say, as a
|
||||
// function parameter, we know what type it _should_ be.
|
||||
//
|
||||
// In a simple/perfect world, we could use this knowledge to cast the *anyopaque
|
||||
// to the parameter type:
|
||||
// const arg: @typeInfo(@TypeOf(function)).@"fn".params[0] = @ptrCast(v8_data);
|
||||
//
|
||||
// But there are 2 reasons we can't do that.
|
||||
//
|
||||
// == Reason 1 ==
|
||||
// The JS code might pass the wrong type:
|
||||
//
|
||||
// var cat = new Cat();
|
||||
// cat.setOwner(new Cat());
|
||||
//
|
||||
// The zig_setOwner method expects the 2nd parameter to be an *Owner, but
|
||||
// the JS code passed a *Cat.
|
||||
//
|
||||
// To solve this issue, we tag every returned value so that we can check what
|
||||
// type it is. In the above case, we'd expect an *Owner, but the tag would tell
|
||||
// us that we got a *Cat. We use the type index in our Types lookup as the tag.
|
||||
//
|
||||
// == Reason 2 ==
|
||||
// Because of prototype inheritance, even "correct" code can be a challenge. For
|
||||
// example, say the above JavaScript is fixed:
|
||||
//
|
||||
// var cat = new Cat();
|
||||
// cat.setOwner(new Owner("Leto"));
|
||||
//
|
||||
// The issue is that setOwner might not expect an *Owner, but rather a
|
||||
// *Person, which is the prototype for Owner. Now our Zig code is expecting
|
||||
// a *Person, but it was (correctly) given an *Owner.
|
||||
// For this reason, we also store the prototype chain.
|
||||
const TaggedOpaque = @This();
|
||||
|
||||
prototype_len: u16,
|
||||
prototype_chain: [*]const PrototypeChainEntry,
|
||||
|
||||
// Ptr to the Zig instance. Between the context where it's called (i.e.
|
||||
// we have the comptime parameter info for all functions), and the index field
|
||||
// we can figure out what type this is.
|
||||
value: *anyopaque,
|
||||
|
||||
// When we're asked to describe an object via the Inspector, we _must_ include
|
||||
// the proper subtype (and description) fields in the returned JSON.
|
||||
// V8 will give us a Value and ask us for the subtype. From the js.Value we
|
||||
// can get a js.Object, and from the js.Object, we can get out TaggedOpaque
|
||||
// which is where we store the subtype.
|
||||
subtype: ?bridge.SubType,
|
||||
|
||||
pub const PrototypeChainEntry = struct {
|
||||
index: bridge.JsApiLookup.BackingInt,
|
||||
offset: u16, // offset to the _proto field
|
||||
};
|
||||
|
||||
// Reverses the mapZigInstanceToJs, making sure that our TaggedOpaque
|
||||
// contains a ptr to the correct type.
|
||||
pub fn fromJS(comptime R: type, js_obj_handle: *const v8.Object) !R {
|
||||
const ti = @typeInfo(R);
|
||||
if (ti != .pointer) {
|
||||
@compileError("non-pointer Zig parameter type: " ++ @typeName(R));
|
||||
}
|
||||
|
||||
const T = ti.pointer.child;
|
||||
const JsApi = bridge.Struct(T).JsApi;
|
||||
|
||||
if (@hasDecl(JsApi.Meta, "empty_with_no_proto")) {
|
||||
// Empty structs aren't stored as TOAs and there's no data
|
||||
// stored in the JSObject's IntenrnalField. Why bother when
|
||||
// we can just return an empty struct here?
|
||||
return @constCast(@as(*const T, &.{}));
|
||||
}
|
||||
|
||||
const internal_field_count = v8.v8__Object__InternalFieldCount(js_obj_handle);
|
||||
// Special case for Window: the global object doesn't have internal fields
|
||||
// Window instance is stored in context.page.window instead
|
||||
if (internal_field_count == 0) {
|
||||
// Normally, this would be an error. All JsObject that map to a Zig type
|
||||
// are either `empty_with_no_proto` (handled above) or have an
|
||||
// interalFieldCount. The only exception to that is the Window...
|
||||
const isolate = v8.v8__Object__GetIsolate(js_obj_handle).?;
|
||||
const context = js.Context.fromIsolate(.{ .handle = isolate });
|
||||
|
||||
const Window = @import("../webapi/Window.zig");
|
||||
if (T == Window) {
|
||||
return context.page.window;
|
||||
}
|
||||
|
||||
// ... Or the window's prototype.
|
||||
// We could make this all comptime-fancy, but it's easier to hard-code
|
||||
// the EventTarget
|
||||
|
||||
const EventTarget = @import("../webapi/EventTarget.zig");
|
||||
if (T == EventTarget) {
|
||||
return context.page.window._proto;
|
||||
}
|
||||
|
||||
// Type not found in Window's prototype chain
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
||||
// if it isn't an empty struct, then the v8.Object should have an
|
||||
// InternalFieldCount > 0, since our toa pointer should be embedded
|
||||
// at index 0 of the internal field count.
|
||||
if (internal_field_count == 0) {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
||||
if (!bridge.JsApiLookup.has(JsApi)) {
|
||||
@compileError("unknown Zig type: " ++ @typeName(R));
|
||||
}
|
||||
|
||||
const internal_field_handle = v8.v8__Object__GetInternalField(js_obj_handle, 0).?;
|
||||
const tao: *TaggedOpaque = @ptrCast(@alignCast(v8.v8__External__Value(internal_field_handle)));
|
||||
const expected_type_index = bridge.JsApiLookup.getId(JsApi);
|
||||
|
||||
const prototype_chain = tao.prototype_chain[0..tao.prototype_len];
|
||||
if (prototype_chain[0].index == expected_type_index) {
|
||||
return @ptrCast(@alignCast(tao.value));
|
||||
}
|
||||
|
||||
// Ok, let's walk up the chain
|
||||
var ptr = @intFromPtr(tao.value);
|
||||
for (prototype_chain[1..]) |proto| {
|
||||
ptr += proto.offset; // the offset to the _proto field
|
||||
const proto_ptr: **anyopaque = @ptrFromInt(ptr);
|
||||
if (proto.index == expected_type_index) {
|
||||
return @ptrCast(@alignCast(proto_ptr.*));
|
||||
}
|
||||
ptr = @intFromPtr(proto_ptr.*);
|
||||
}
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
@@ -24,12 +24,12 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const TryCatch = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
handle: v8.TryCatch,
|
||||
local: *const js.Local,
|
||||
|
||||
pub fn init(self: *TryCatch, ctx: *js.Context) void {
|
||||
self.ctx = ctx;
|
||||
v8.v8__TryCatch__CONSTRUCT(&self.handle, ctx.isolate.handle);
|
||||
pub fn init(self: *TryCatch, l: *const js.Local) void {
|
||||
self.local = l;
|
||||
v8.v8__TryCatch__CONSTRUCT(&self.handle, l.isolate.handle);
|
||||
}
|
||||
|
||||
pub fn hasCaught(self: TryCatch) bool {
|
||||
@@ -41,26 +41,21 @@ pub fn caught(self: TryCatch, allocator: Allocator) ?Caught {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ctx = self.ctx;
|
||||
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(ctx.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
const l = self.local;
|
||||
const line: ?u32 = blk: {
|
||||
const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null;
|
||||
const l = v8.v8__Message__GetLineNumber(handle, ctx.handle);
|
||||
break :blk if (l < 0) null else @intCast(l);
|
||||
const line = v8.v8__Message__GetLineNumber(handle, l.handle);
|
||||
break :blk if (line < 0) null else @intCast(line);
|
||||
};
|
||||
|
||||
const exception: ?[]const u8 = blk: {
|
||||
const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null;
|
||||
break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err);
|
||||
break :blk l.valueHandleToString(@ptrCast(handle), .{ .allocator = allocator }) catch |err| @errorName(err);
|
||||
};
|
||||
|
||||
const stack: ?[]const u8 = blk: {
|
||||
const handle = v8.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse break :blk null;
|
||||
break :blk ctx.valueToString(.{ .ctx = ctx, .handle = handle }, .{ .allocator = allocator }) catch |err| @errorName(err);
|
||||
const handle = v8.v8__TryCatch__StackTrace(&self.handle, l.handle) orelse break :blk null;
|
||||
break :blk l.valueHandleToString(@ptrCast(handle), .{ .allocator = allocator }) catch |err| @errorName(err);
|
||||
};
|
||||
|
||||
return .{
|
||||
|
||||
@@ -27,7 +27,7 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const Value = @This();
|
||||
|
||||
ctx: *js.Context,
|
||||
local: *const js.Local,
|
||||
handle: *const v8.Value,
|
||||
|
||||
pub fn isObject(self: Value) bool {
|
||||
@@ -155,12 +155,12 @@ pub fn isPromise(self: Value) bool {
|
||||
}
|
||||
|
||||
pub fn toBool(self: Value) bool {
|
||||
return v8.v8__Value__BooleanValue(self.handle, self.ctx.isolate.handle);
|
||||
return v8.v8__Value__BooleanValue(self.handle, self.local.isolate.handle);
|
||||
}
|
||||
|
||||
pub fn typeOf(self: Value) js.String {
|
||||
const str_handle = v8.v8__Value__TypeOf(self.handle, self.ctx.isolate.handle).?;
|
||||
return js.String{ .ctx = self.ctx, .handle = str_handle };
|
||||
const str_handle = v8.v8__Value__TypeOf(self.handle, self.local.isolate.handle).?;
|
||||
return js.String{ .local = self.local, .handle = str_handle };
|
||||
}
|
||||
|
||||
pub fn toF32(self: Value) !f32 {
|
||||
@@ -169,7 +169,7 @@ pub fn toF32(self: Value) !f32 {
|
||||
|
||||
pub fn toF64(self: Value) !f64 {
|
||||
var maybe: v8.MaybeF64 = undefined;
|
||||
v8.v8__Value__NumberValue(self.handle, self.ctx.handle, &maybe);
|
||||
v8.v8__Value__NumberValue(self.handle, self.local.handle, &maybe);
|
||||
if (!maybe.has_value) {
|
||||
return error.JsException;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ pub fn toF64(self: Value) !f64 {
|
||||
|
||||
pub fn toI32(self: Value) !i32 {
|
||||
var maybe: v8.MaybeI32 = undefined;
|
||||
v8.v8__Value__Int32Value(self.handle, self.ctx.handle, &maybe);
|
||||
v8.v8__Value__Int32Value(self.handle, self.local.handle, &maybe);
|
||||
if (!maybe.has_value) {
|
||||
return error.JsException;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ pub fn toI32(self: Value) !i32 {
|
||||
|
||||
pub fn toU32(self: Value) !u32 {
|
||||
var maybe: v8.MaybeU32 = undefined;
|
||||
v8.v8__Value__Uint32Value(self.handle, self.ctx.handle, &maybe);
|
||||
v8.v8__Value__Uint32Value(self.handle, self.local.handle, &maybe);
|
||||
if (!maybe.has_value) {
|
||||
return error.JsException;
|
||||
}
|
||||
@@ -199,7 +199,7 @@ pub fn toPromise(self: Value) js.Promise {
|
||||
std.debug.assert(self.isPromise());
|
||||
}
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
@@ -212,53 +212,42 @@ pub fn toStringZ(self: Value, opts: js.String.ToZigOpts) ![:0]u8 {
|
||||
}
|
||||
|
||||
pub fn toJson(self: Value, allocator: Allocator) ![]u8 {
|
||||
const json_str_handle = v8.v8__JSON__Stringify(self.ctx.handle, self.handle, null) orelse return error.JsException;
|
||||
return self.ctx.jsStringToZig(json_str_handle, .{ .allocator = allocator });
|
||||
const json_str_handle = v8.v8__JSON__Stringify(self.local.handle, self.handle, null) orelse return error.JsException;
|
||||
return self.local.jsStringToZig(json_str_handle, .{ .allocator = allocator });
|
||||
}
|
||||
|
||||
fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
||||
const ctx = self.ctx;
|
||||
const l = self.local;
|
||||
|
||||
if (self.isSymbol()) {
|
||||
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?;
|
||||
return _toString(.{ .handle = @ptrCast(sym_handle), .ctx = ctx }, null_terminate, opts);
|
||||
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), l.isolate.handle).?;
|
||||
return _toString(.{ .handle = @ptrCast(sym_handle), .local = l }, null_terminate, opts);
|
||||
}
|
||||
|
||||
const str_handle = v8.v8__Value__ToString(self.handle, ctx.handle) orelse {
|
||||
const str_handle = v8.v8__Value__ToString(self.handle, l.handle) orelse {
|
||||
return error.JsException;
|
||||
};
|
||||
|
||||
const str = js.String{ .ctx = ctx, .handle = str_handle };
|
||||
const str = js.String{ .local = l, .handle = str_handle };
|
||||
if (comptime null_terminate) {
|
||||
return js.String.toZigZ(str, opts);
|
||||
}
|
||||
return js.String.toZig(str, opts);
|
||||
}
|
||||
|
||||
pub fn fromJson(ctx: *js.Context, json: []const u8) !Value {
|
||||
const v8_isolate = v8.Isolate{ .handle = ctx.isolate.handle };
|
||||
const json_string = v8.String.initUtf8(v8_isolate, json);
|
||||
const v8_context = v8.Context{ .handle = ctx.handle };
|
||||
const value = try v8.Json.parse(v8_context, json_string);
|
||||
return .{ .ctx = ctx, .handle = value.handle };
|
||||
}
|
||||
|
||||
pub fn persist(self: Value) !Global {
|
||||
var ctx = self.ctx;
|
||||
var ctx = self.local.ctx;
|
||||
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||
|
||||
try ctx.global_values.append(ctx.arena, global);
|
||||
|
||||
return .{
|
||||
.handle = global,
|
||||
.ctx = ctx,
|
||||
};
|
||||
return .{ .handle = global };
|
||||
}
|
||||
|
||||
pub fn toZig(self: Value, comptime T: type) !T {
|
||||
return self.ctx.jsValueToZig(T, self);
|
||||
return self.local.jsValueToZig(T, self);
|
||||
}
|
||||
|
||||
pub fn toObject(self: Value) js.Object {
|
||||
@@ -267,7 +256,7 @@ pub fn toObject(self: Value) js.Object {
|
||||
}
|
||||
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
@@ -278,7 +267,7 @@ pub fn toArray(self: Value) js.Array {
|
||||
}
|
||||
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.local = self.local,
|
||||
.handle = @ptrCast(self.handle),
|
||||
};
|
||||
}
|
||||
@@ -295,7 +284,7 @@ pub fn toBigInt(self: Value) js.BigInt {
|
||||
|
||||
pub fn format(self: Value, writer: *std.Io.Writer) !void {
|
||||
if (comptime IS_DEBUG) {
|
||||
return self.ctx.debugValue(self, writer);
|
||||
return self.local.debugValue(self, writer);
|
||||
}
|
||||
const str = self.toString(.{}) catch return error.WriteFailed;
|
||||
return writer.writeAll(str);
|
||||
@@ -303,16 +292,15 @@ pub fn format(self: Value, writer: *std.Io.Writer) !void {
|
||||
|
||||
pub const Global = struct {
|
||||
handle: v8.Global,
|
||||
ctx: *js.Context,
|
||||
|
||||
pub fn deinit(self: *Global) void {
|
||||
v8.v8__Global__Reset(&self.handle);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Global) Value {
|
||||
pub fn local(self: *const Global, l: *const js.Local) Value {
|
||||
return .{
|
||||
.ctx = self.ctx,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
||||
.local = l,
|
||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -20,555 +20,15 @@ const std = @import("std");
|
||||
const js = @import("js.zig");
|
||||
const lp = @import("lightpanda");
|
||||
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;
|
||||
@@ -610,8 +70,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) };
|
||||
|
||||
@@ -644,11 +105,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,
|
||||
});
|
||||
}
|
||||
@@ -673,18 +134,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,
|
||||
@@ -717,17 +178,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,
|
||||
});
|
||||
@@ -740,15 +201,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.? };
|
||||
if (comptime IS_DEBUG) {
|
||||
lp.assert(info.length() == 1, "bridge.setter", .{ .len = info.length() });
|
||||
}
|
||||
|
||||
caller.method(T, setter, info, .{
|
||||
caller.method(T, setter, handle.?, .{
|
||||
.as_typed_array = opts.as_typed_array,
|
||||
.null_as_undefined = opts.null_as_undefined,
|
||||
});
|
||||
@@ -772,11 +229,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,
|
||||
});
|
||||
@@ -799,11 +256,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,
|
||||
});
|
||||
@@ -813,11 +270,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,
|
||||
});
|
||||
@@ -827,11 +284,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,
|
||||
});
|
||||
@@ -861,7 +318,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,
|
||||
@@ -873,11 +330,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,
|
||||
};
|
||||
@@ -895,11 +351,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,
|
||||
});
|
||||
}
|
||||
@@ -912,52 +368,57 @@ 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;
|
||||
|
||||
var hs: js.HandleScope = undefined;
|
||||
hs.init(local.isolate);
|
||||
defer hs.deinit();
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// 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;
|
||||
|
||||
pub fn Global(comptime T: type) type {
|
||||
const H = @FieldType(T, "handle");
|
||||
|
||||
return struct {
|
||||
global: v8.Global,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(isolate: *v8.Isolate, handle: H) Self {
|
||||
var global: v8.Global = undefined;
|
||||
v8.v8__Global__New(isolate, handle, &global);
|
||||
return .{
|
||||
.global = global,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
v8.v8__Global__Reset(&self.global);
|
||||
}
|
||||
|
||||
pub fn local(self: *const Self) H {
|
||||
return @ptrCast(@alignCast(@as(*const anyopaque, @ptrFromInt(self.global.data_ptr))));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -24,7 +24,9 @@ const log = @import("../../log.zig");
|
||||
pub const Env = @import("Env.zig");
|
||||
pub const bridge = @import("bridge.zig");
|
||||
pub const ExecutionWorld = @import("ExecutionWorld.zig");
|
||||
pub const Caller = @import("Caller.zig");
|
||||
pub const Context = @import("Context.zig");
|
||||
pub const Local = @import("Local.zig");
|
||||
pub const Inspector = @import("Inspector.zig");
|
||||
pub const Snapshot = @import("Snapshot.zig");
|
||||
pub const Platform = @import("Platform.zig");
|
||||
@@ -43,7 +45,6 @@ pub const Module = @import("Module.zig");
|
||||
pub const BigInt = @import("BigInt.zig");
|
||||
pub const Number = @import("Number.zig");
|
||||
pub const Integer = @import("Integer.zig");
|
||||
pub const Global = @import("global.zig").Global;
|
||||
pub const PromiseResolver = @import("PromiseResolver.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -78,11 +79,11 @@ pub const ArrayBuffer = struct {
|
||||
};
|
||||
|
||||
pub const Exception = struct {
|
||||
ctx: *const Context,
|
||||
local: *const Local,
|
||||
handle: *const v8.Value,
|
||||
|
||||
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
|
||||
return self.context.valueToString(self.inner, .{ .allocator = allocator });
|
||||
return self.local.valueToString(self.handel, .{ .allocator = allocator });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,61 +216,6 @@ pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool,
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// When we return a Zig object to V8, we put it on the heap and pass it into
|
||||
// v8 as an *anyopaque (i.e. void *). When V8 gives us back the value, say, as a
|
||||
// function parameter, we know what type it _should_ be.
|
||||
//
|
||||
// In a simple/perfect world, we could use this knowledge to cast the *anyopaque
|
||||
// to the parameter type:
|
||||
// const arg: @typeInfo(@TypeOf(function)).@"fn".params[0] = @ptrCast(v8_data);
|
||||
//
|
||||
// But there are 2 reasons we can't do that.
|
||||
//
|
||||
// == Reason 1 ==
|
||||
// The JS code might pass the wrong type:
|
||||
//
|
||||
// var cat = new Cat();
|
||||
// cat.setOwner(new Cat());
|
||||
//
|
||||
// The zig_setOwner method expects the 2nd parameter to be an *Owner, but
|
||||
// the JS code passed a *Cat.
|
||||
//
|
||||
// To solve this issue, we tag every returned value so that we can check what
|
||||
// type it is. In the above case, we'd expect an *Owner, but the tag would tell
|
||||
// us that we got a *Cat. We use the type index in our Types lookup as the tag.
|
||||
//
|
||||
// == Reason 2 ==
|
||||
// Because of prototype inheritance, even "correct" code can be a challenge. For
|
||||
// example, say the above JavaScript is fixed:
|
||||
//
|
||||
// var cat = new Cat();
|
||||
// cat.setOwner(new Owner("Leto"));
|
||||
//
|
||||
// The issue is that setOwner might not expect an *Owner, but rather a
|
||||
// *Person, which is the prototype for Owner. Now our Zig code is expecting
|
||||
// a *Person, but it was (correctly) given an *Owner.
|
||||
// For this reason, we also store the prototype chain.
|
||||
pub const TaggedAnyOpaque = struct {
|
||||
prototype_len: u16,
|
||||
prototype_chain: [*]const PrototypeChainEntry,
|
||||
|
||||
// Ptr to the Zig instance. Between the context where it's called (i.e.
|
||||
// we have the comptime parameter info for all functions), and the index field
|
||||
// we can figure out what type this is.
|
||||
value: *anyopaque,
|
||||
|
||||
// When we're asked to describe an object via the Inspector, we _must_ include
|
||||
// the proper subtype (and description) fields in the returned JSON.
|
||||
// V8 will give us a Value and ask us for the subtype. From the js.Value we
|
||||
// can get a js.Object, and from the js.Object, we can get out TaggedAnyOpaque
|
||||
// which is where we store the subtype.
|
||||
subtype: ?bridge.SubType,
|
||||
};
|
||||
|
||||
pub const PrototypeChainEntry = struct {
|
||||
index: bridge.JsApiLookup.BackingInt,
|
||||
offset: u16, // offset to the _proto field
|
||||
};
|
||||
|
||||
// These are here, and not in Inspector.zig, because Inspector.zig isn't always
|
||||
// included (e.g. in the wpt build).
|
||||
@@ -281,7 +227,7 @@ pub export fn v8_inspector__Client__IMPL__valueSubtype(
|
||||
_: *v8.InspectorClientImpl,
|
||||
c_value: *const v8.Value,
|
||||
) callconv(.c) [*c]const u8 {
|
||||
const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null;
|
||||
const external_entry = Inspector.getTaggedOpaque(c_value) orelse return null;
|
||||
return if (external_entry.subtype) |st| @tagName(st) else null;
|
||||
}
|
||||
|
||||
@@ -298,11 +244,11 @@ pub export fn v8_inspector__Client__IMPL__descriptionForValueSubtype(
|
||||
|
||||
// We _must_ include a non-null description in order for the subtype value
|
||||
// to be included. Besides that, I don't know if the value has any meaning
|
||||
const external_entry = Inspector.getTaggedAnyOpaque(c_value) orelse return null;
|
||||
const external_entry = Inspector.getTaggedOpaque(c_value) orelse return null;
|
||||
return if (external_entry.subtype == null) null else "";
|
||||
}
|
||||
|
||||
test "TaggedAnyOpaque" {
|
||||
// If we grow this, fine, but it should be a conscious decision
|
||||
try std.testing.expectEqual(24, @sizeOf(TaggedAnyOpaque));
|
||||
try std.testing.expectEqual(24, @sizeOf(@import("TaggedOpaque.zig")));
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
testing.eventually(() => {
|
||||
testing.expectEqual(true, popstateEventFired);
|
||||
testing.expectEqual(state, popstateEventState);
|
||||
testing.expectEqual({testInProgress: true }, popstateEventState);
|
||||
})
|
||||
|
||||
history.back();
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="testing.js"></script>
|
||||
|
||||
<script id=history-after-nav>
|
||||
testing.expectEqual(true, history.state && history.state.testInProgress);
|
||||
</script>
|
||||
|
||||
@@ -38,7 +38,7 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
|
||||
}
|
||||
|
||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page: *Page) !void {
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -77,11 +77,10 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
|
||||
// Dispatch abort event
|
||||
const event = try Event.initTrusted("abort", .{}, page);
|
||||
const func = if (self._on_abort) |*g| g.local() else null;
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
func,
|
||||
local.toLocal(self._on_abort),
|
||||
.{ .context = "abort signal" },
|
||||
);
|
||||
}
|
||||
@@ -89,7 +88,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
// Static method to create an already-aborted signal
|
||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||
const signal = try init(page);
|
||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
||||
return signal;
|
||||
}
|
||||
|
||||
@@ -112,11 +111,13 @@ const ThrowIfAborted = union(enum) {
|
||||
undefined: void,
|
||||
};
|
||||
pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted {
|
||||
const local = page.js.local.?;
|
||||
|
||||
if (self._aborted) {
|
||||
const exception = switch (self._reason) {
|
||||
.string => |str| page.js.throw(str),
|
||||
.js_val => |js_val| page.js.throw(try js_val.local().toString(.{ .allocator = page.call_arena })),
|
||||
.undefined => page.js.throw("AbortError"),
|
||||
.string => |str| local.throw(str),
|
||||
.js_val => |js_val| local.throw(try local.toLocal(js_val).toString(.{ .allocator = page.call_arena })),
|
||||
.undefined => local.throw("AbortError"),
|
||||
};
|
||||
return .{ .exception = exception };
|
||||
}
|
||||
@@ -135,7 +136,11 @@ const TimeoutCallback = struct {
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
||||
self.signal.abort(.{ .string = "TimeoutError" }, self.page) catch |err| {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self.page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
self.signal.abort(.{ .string = "TimeoutError" }, &ls.local, self.page) catch |err| {
|
||||
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||
};
|
||||
return null;
|
||||
|
||||
@@ -206,7 +206,7 @@ fn writeBlobParts(
|
||||
/// Returns a Promise that resolves with the contents of the blob
|
||||
/// as binary data contained in an ArrayBuffer.
|
||||
pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
|
||||
return page.js.resolvePromise(js.ArrayBuffer{ .values = self._slice });
|
||||
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = self._slice });
|
||||
}
|
||||
|
||||
const ReadableStream = @import("streams/ReadableStream.zig");
|
||||
@@ -219,7 +219,7 @@ pub fn stream(self: *const Blob, page: *Page) !*ReadableStream {
|
||||
/// Returns a Promise that resolves with a string containing
|
||||
/// the contents of the blob, interpreted as UTF-8.
|
||||
pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
||||
return page.js.resolvePromise(self._slice);
|
||||
return page.js.local.?.resolvePromise(self._slice);
|
||||
}
|
||||
|
||||
/// Extension to Blob; works on Firefox and Safari.
|
||||
@@ -227,7 +227,7 @@ pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
||||
/// Returns a Promise that resolves with a Uint8Array containing
|
||||
/// the contents of the blob as an array of bytes.
|
||||
pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
|
||||
return page.js.resolvePromise(js.TypedArray(u8){ .values = self._slice });
|
||||
return page.js.local.?.resolvePromise(js.TypedArray(u8){ .values = self._slice });
|
||||
}
|
||||
|
||||
/// Returns a new Blob object which contains data
|
||||
|
||||
@@ -31,7 +31,7 @@ pub const init: Console = .{};
|
||||
|
||||
pub fn trace(_: *const Console, values: []js.Value, page: *Page) !void {
|
||||
logger.debug(.js, "console.trace", .{
|
||||
.stack = page.js.stackTrace() catch "???",
|
||||
.stack = page.js.local.?.stackTrace() catch "???",
|
||||
.args = ValueWriter{ .page = page, .values = values },
|
||||
});
|
||||
}
|
||||
@@ -138,7 +138,7 @@ const ValueWriter = struct {
|
||||
try writer.print("\n arg({d}): {f}", .{ i, value });
|
||||
}
|
||||
if (self.include_stack) {
|
||||
try writer.print("\n stack: {s}", .{self.page.js.stackTrace() catch |err| @errorName(err) orelse "???"});
|
||||
try writer.print("\n stack: {s}", .{self.page.js.local.?.stackTrace() catch |err| @errorName(err) orelse "???"});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
}
|
||||
|
||||
if (self._when_defined.fetchRemove(name)) |entry| {
|
||||
entry.value.local().resolve("whenDefined", constructor);
|
||||
page.js.toLocal(entry.value).resolve("whenDefined", constructor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,22 +120,23 @@ pub fn upgrade(self: *CustomElementRegistry, root: *Node, page: *Page) !void {
|
||||
}
|
||||
|
||||
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) !js.Promise {
|
||||
const local = page.js.local.?;
|
||||
if (self._definitions.get(name)) |definition| {
|
||||
return page.js.resolvePromise(definition.constructor);
|
||||
return local.resolvePromise(definition.constructor);
|
||||
}
|
||||
|
||||
const gop = try self._when_defined.getOrPut(page.arena, name);
|
||||
if (gop.found_existing) {
|
||||
return gop.value_ptr.local().promise();
|
||||
return local.toLocal(gop.value_ptr.*).promise();
|
||||
}
|
||||
errdefer _ = self._when_defined.remove(name);
|
||||
const owned_name = try page.dupeString(name);
|
||||
|
||||
const resolver = try page.js.createPromiseResolver().persist();
|
||||
const resolver = local.createPromiseResolver();
|
||||
gop.key_ptr.* = owned_name;
|
||||
gop.value_ptr.* = resolver;
|
||||
gop.value_ptr.* = try resolver.persist();
|
||||
|
||||
return resolver.local().promise();
|
||||
return resolver.promise();
|
||||
}
|
||||
|
||||
fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void {
|
||||
@@ -174,8 +175,12 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
|
||||
page._upgrading_element = node;
|
||||
defer page._upgrading_element = prev_upgrading;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
_ = definition.constructor.local().newInstance(&caught) catch |err| {
|
||||
_ = ls.toLocal(definition.constructor).newInstance(&caught) catch |err| {
|
||||
log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err, .caught = caught });
|
||||
return error.CustomElementUpgradeFailed;
|
||||
};
|
||||
|
||||
@@ -63,14 +63,14 @@ pub fn getFilter(self: *const DOMNodeIterator) ?FilterOpts {
|
||||
return self._filter._original_filter;
|
||||
}
|
||||
|
||||
pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
||||
pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
var node = self._reference_node;
|
||||
var before_node = self._pointer_before_reference_node;
|
||||
|
||||
while (true) {
|
||||
if (before_node) {
|
||||
before_node = false;
|
||||
const result = try self.filterNode(node);
|
||||
const result = try self.filterNode(node, page);
|
||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._reference_node = node;
|
||||
self._pointer_before_reference_node = false;
|
||||
@@ -84,7 +84,7 @@ pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
||||
}
|
||||
node = next.?;
|
||||
|
||||
const result = try self.filterNode(node);
|
||||
const result = try self.filterNode(node, page);
|
||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._reference_node = node;
|
||||
self._pointer_before_reference_node = false;
|
||||
@@ -94,13 +94,13 @@ pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previousNode(self: *DOMNodeIterator) !?*Node {
|
||||
pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
var node = self._reference_node;
|
||||
var before_node = self._pointer_before_reference_node;
|
||||
|
||||
while (true) {
|
||||
if (!before_node) {
|
||||
const result = try self.filterNode(node);
|
||||
const result = try self.filterNode(node, page);
|
||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._reference_node = node;
|
||||
self._pointer_before_reference_node = true;
|
||||
@@ -119,7 +119,7 @@ pub fn previousNode(self: *DOMNodeIterator) !?*Node {
|
||||
}
|
||||
}
|
||||
|
||||
fn filterNode(self: *const DOMNodeIterator, node: *Node) !i32 {
|
||||
fn filterNode(self: *const DOMNodeIterator, node: *Node, page: *Page) !i32 {
|
||||
// First check whatToShow
|
||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
@@ -128,7 +128,7 @@ fn filterNode(self: *const DOMNodeIterator, node: *Node) !i32 {
|
||||
// Then check the filter callback
|
||||
// For NodeIterator, REJECT and SKIP are equivalent - both skip the node
|
||||
// but continue with its descendants
|
||||
const result = try self._filter.acceptNode(node);
|
||||
const result = try self._filter.acceptNode(node, page.js.local.?);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,13 +62,13 @@ pub fn setCurrentNode(self: *DOMTreeWalker, node: *Node) void {
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn parentNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self._current._parent;
|
||||
while (node) |n| {
|
||||
if (n == self._root._parent) {
|
||||
return null;
|
||||
}
|
||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
@@ -77,11 +77,11 @@ pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn firstChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self._current.firstChild();
|
||||
|
||||
while (node) |n| {
|
||||
const filter_result = try self.acceptNode(n);
|
||||
const filter_result = try self.acceptNode(n, page);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
@@ -117,11 +117,11 @@ pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn lastChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self._current.lastChild();
|
||||
|
||||
while (node) |n| {
|
||||
const filter_result = try self.acceptNode(n);
|
||||
const filter_result = try self.acceptNode(n, page);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
@@ -157,10 +157,10 @@ pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn previousSibling(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn previousSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self.previousSiblingOrNull(self._current);
|
||||
while (node) |n| {
|
||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
@@ -169,10 +169,10 @@ pub fn previousSibling(self: *DOMTreeWalker) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn nextSibling(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn nextSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self.nextSiblingOrNull(self._current);
|
||||
while (node) |n| {
|
||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
@@ -181,7 +181,7 @@ pub fn nextSibling(self: *DOMTreeWalker) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self._current;
|
||||
while (node != self._root) {
|
||||
var sibling = self.previousSiblingOrNull(node);
|
||||
@@ -189,7 +189,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
node = sib;
|
||||
|
||||
// Check if this sibling is rejected before descending into it
|
||||
const sib_result = try self.acceptNode(node);
|
||||
const sib_result = try self.acceptNode(node, page);
|
||||
if (sib_result == NodeFilter.FILTER_REJECT) {
|
||||
// Skip this sibling and its descendants entirely
|
||||
sibling = self.previousSiblingOrNull(node);
|
||||
@@ -204,7 +204,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
while (child) |c| {
|
||||
if (!self.isInSubtree(c)) break;
|
||||
|
||||
const filter_result = try self.acceptNode(c);
|
||||
const filter_result = try self.acceptNode(c, page);
|
||||
if (filter_result == NodeFilter.FILTER_REJECT) {
|
||||
// Skip this child and try its previous sibling
|
||||
child = self.previousSiblingOrNull(c);
|
||||
@@ -220,7 +220,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
node = child.?;
|
||||
}
|
||||
|
||||
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(node, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
}
|
||||
@@ -232,7 +232,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
}
|
||||
|
||||
const parent = node._parent orelse return null;
|
||||
if (try self.acceptNode(parent) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(parent, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = parent;
|
||||
return parent;
|
||||
}
|
||||
@@ -241,14 +241,14 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
||||
pub fn nextNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
var node = self._current;
|
||||
|
||||
while (true) {
|
||||
// Try children first (depth-first)
|
||||
if (node.firstChild()) |child| {
|
||||
node = child;
|
||||
const filter_result = try self.acceptNode(node);
|
||||
const filter_result = try self.acceptNode(node, page);
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
@@ -271,7 +271,7 @@ pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
||||
|
||||
if (node.nextSibling()) |sibling| {
|
||||
node = sibling;
|
||||
const filter_result = try self.acceptNode(node);
|
||||
const filter_result = try self.acceptNode(node, page);
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
@@ -293,7 +293,7 @@ pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
fn acceptNode(self: *const DOMTreeWalker, node: *Node) !i32 {
|
||||
fn acceptNode(self: *const DOMTreeWalker, node: *Node, page: *Page) !i32 {
|
||||
// First check whatToShow
|
||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
@@ -303,7 +303,7 @@ fn acceptNode(self: *const DOMTreeWalker, node: *Node) !i32 {
|
||||
// For TreeWalker, REJECT means reject node and its descendants
|
||||
// SKIP means skip node but check its descendants
|
||||
// ACCEPT means accept the node
|
||||
return try self._filter.acceptNode(node);
|
||||
return try self._filter.acceptNode(node, page.js.local.?);
|
||||
}
|
||||
|
||||
fn isInSubtree(self: *const DOMTreeWalker, node: *Node) bool {
|
||||
|
||||
@@ -770,7 +770,7 @@ pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object.Global {
|
||||
if (self._adopted_style_sheets) |ass| {
|
||||
return ass;
|
||||
}
|
||||
const js_arr = page.js.newArray(0);
|
||||
const js_arr = page.js.local.?.newArray(0);
|
||||
const js_obj = js_arr.toObject();
|
||||
self._adopted_style_sheets = try js_obj.persist();
|
||||
return self._adopted_style_sheets.?;
|
||||
|
||||
@@ -34,7 +34,7 @@ pub fn getLength(_: *const History, page: *Page) u32 {
|
||||
|
||||
pub fn getState(_: *const History, page: *Page) !?js.Value {
|
||||
if (page._session.navigation.getCurrentEntry()._state.value) |state| {
|
||||
const value = try page.js.parseJSON(state);
|
||||
const value = try page.js.local.?.parseJSON(state);
|
||||
return value;
|
||||
} else return null;
|
||||
}
|
||||
@@ -81,11 +81,10 @@ fn goInner(delta: i32, page: *Page) !void {
|
||||
if (try page.isSameOrigin(url)) {
|
||||
const event = try PopStateEvent.initTrusted("popstate", .{ .state = entry._state.value }, page);
|
||||
|
||||
const func = if (page.window._on_popstate) |*g| g.local() else null;
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
page.window.asEventTarget(),
|
||||
event.asEvent(),
|
||||
func,
|
||||
page.js.toLocal(page.window._on_popstate),
|
||||
.{ .context = "Pop State" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -246,7 +246,12 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
|
||||
|
||||
const entries = try self.takeRecords(page);
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
self._callback.local().tryCall(void, .{ entries, self }, &caught) catch |err| {
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
ls.toLocal(self._callback).tryCall(void, .{ entries, self }, &caught) catch |err| {
|
||||
log.err(.page, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
|
||||
@@ -116,6 +116,7 @@ const PostMessageCallback = struct {
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||
defer self.deinit();
|
||||
const page = self.page;
|
||||
|
||||
if (self.port._closed) {
|
||||
return null;
|
||||
@@ -125,16 +126,19 @@ const PostMessageCallback = struct {
|
||||
.data = self.message,
|
||||
.origin = "",
|
||||
.source = null,
|
||||
}, self.page) catch |err| {
|
||||
}, page) catch |err| {
|
||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||
return null;
|
||||
};
|
||||
|
||||
const func = if (self.port._on_message) |*g| g.local() else null;
|
||||
self.page._event_manager.dispatchWithFunction(
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
page._event_manager.dispatchWithFunction(
|
||||
self.port.asEventTarget(),
|
||||
event.asEvent(),
|
||||
func,
|
||||
ls.toLocal(self.port._on_message),
|
||||
.{ .context = "MessagePort message" },
|
||||
) catch |err| {
|
||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||
|
||||
@@ -248,8 +248,12 @@ pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
|
||||
// Take a copy of the records and clear the list before calling callback
|
||||
// This ensures mutations triggered during the callback go into a fresh list
|
||||
const records = try self.takeRecords(page);
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
self._callback.local().tryCall(void, .{ records, self }, &caught) catch |err| {
|
||||
ls.toLocal(self._callback).tryCall(void, .{ records, self }, &caught) catch |err| {
|
||||
log.err(.page, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
|
||||
@@ -65,9 +65,9 @@ pub const SHOW_DOCUMENT_TYPE: u32 = 0x200;
|
||||
pub const SHOW_DOCUMENT_FRAGMENT: u32 = 0x400;
|
||||
pub const SHOW_NOTATION: u32 = 0x800;
|
||||
|
||||
pub fn acceptNode(self: *const NodeFilter, node: *Node) !i32 {
|
||||
pub fn acceptNode(self: *const NodeFilter, node: *Node, local: *const js.Local) !i32 {
|
||||
const func = self._func orelse return FILTER_ACCEPT;
|
||||
return func.local().call(i32, .{node});
|
||||
return local.toLocal(func).call(i32, .{node});
|
||||
}
|
||||
|
||||
pub fn shouldShow(node: *const Node, what_to_show: u32) bool {
|
||||
|
||||
@@ -362,10 +362,10 @@ pub const Mark = struct {
|
||||
|
||||
pub const Measure = struct {
|
||||
_proto: *Entry,
|
||||
_detail: ?js.Object.Global,
|
||||
_detail: ?js.Value.Global,
|
||||
|
||||
const Options = struct {
|
||||
detail: ?js.Object = null,
|
||||
detail: ?js.Value = null,
|
||||
start: ?TimestampOrMark,
|
||||
end: ?TimestampOrMark,
|
||||
duration: ?f64 = null,
|
||||
@@ -378,7 +378,7 @@ pub const Measure = struct {
|
||||
|
||||
pub fn init(
|
||||
name: []const u8,
|
||||
maybe_detail: ?js.Object,
|
||||
maybe_detail: ?js.Value,
|
||||
start_timestamp: f64,
|
||||
end_timestamp: f64,
|
||||
maybe_duration: ?f64,
|
||||
@@ -405,7 +405,7 @@ pub const Measure = struct {
|
||||
return m;
|
||||
}
|
||||
|
||||
pub fn getDetail(self: *const Measure) ?js.Object.Global {
|
||||
pub fn getDetail(self: *const Measure) ?js.Value.Global {
|
||||
return self._detail;
|
||||
}
|
||||
|
||||
|
||||
@@ -153,8 +153,13 @@ pub inline fn hasRecords(self: *const PerformanceObserver) bool {
|
||||
/// Runs the PerformanceObserver's callback with records; emptying it out.
|
||||
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
|
||||
const records = try self.takeRecords(page);
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
self._callback.local().tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
|
||||
ls.toLocal(self._callback).tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
|
||||
log.err(.page, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
|
||||
@@ -96,10 +96,10 @@ pub fn generateKey(
|
||||
page: *Page,
|
||||
) !js.Promise {
|
||||
const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| {
|
||||
return page.js.rejectPromise(@errorName(err));
|
||||
return page.js.local.?.rejectPromise(@errorName(err));
|
||||
};
|
||||
|
||||
return page.js.resolvePromise(key_or_pair);
|
||||
return page.js.local.?.resolvePromise(key_or_pair);
|
||||
}
|
||||
|
||||
/// Exports a key: that is, it takes as input a CryptoKey object and gives you
|
||||
@@ -115,7 +115,7 @@ pub fn exportKey(
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, format, "raw")) {
|
||||
return page.js.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
||||
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
||||
}
|
||||
|
||||
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
|
||||
@@ -125,7 +125,7 @@ pub fn exportKey(
|
||||
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
||||
}
|
||||
|
||||
return page.js.rejectPromise(@errorName(error.NotSupported));
|
||||
return page.js.local.?.rejectPromise(@errorName(error.NotSupported));
|
||||
}
|
||||
|
||||
/// Derive a secret key from a master key.
|
||||
@@ -140,14 +140,14 @@ pub fn deriveBits(
|
||||
.ecdh_or_x25519 => |p| {
|
||||
const name = p.name;
|
||||
if (std.mem.eql(u8, name, "X25519")) {
|
||||
return page.js.resolvePromise(base_key.deriveBitsX25519(p.public, length, page));
|
||||
return page.js.local.?.resolvePromise(base_key.deriveBitsX25519(p.public, length, page));
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "ECDH")) {
|
||||
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
|
||||
}
|
||||
|
||||
return page.js.rejectPromise(@errorName(error.NotSupported));
|
||||
return page.js.local.?.rejectPromise(@errorName(error.NotSupported));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -184,19 +184,19 @@ pub fn sign(
|
||||
.hmac => {
|
||||
// Verify algorithm.
|
||||
if (!algorithm.isHMAC()) {
|
||||
return page.js.rejectPromise(@errorName(error.InvalidAccessError));
|
||||
return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError));
|
||||
}
|
||||
|
||||
// Call sign for HMAC.
|
||||
const result = key.signHMAC(data, page) catch |err| {
|
||||
return page.js.rejectPromise(@errorName(err));
|
||||
return page.js.local.?.rejectPromise(@errorName(err));
|
||||
};
|
||||
|
||||
return page.js.resolvePromise(result);
|
||||
return page.js.local.?.resolvePromise(result);
|
||||
},
|
||||
else => {
|
||||
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
||||
return page.js.rejectPromise(@errorName(error.InvalidAccessError));
|
||||
return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -454,10 +454,10 @@ pub const CryptoKey = struct {
|
||||
if (signed != null) {
|
||||
// CRYPTO_memcmp compare in constant time so prohibits time-based attacks.
|
||||
const res = crypto.CRYPTO_memcmp(signed, @ptrCast(signature.ptr), signature.len);
|
||||
return page.js.resolvePromise(res == 0);
|
||||
return page.js.local.?.resolvePromise(res == 0);
|
||||
}
|
||||
|
||||
return page.js.resolvePromise(false);
|
||||
return page.js.local.?.resolvePromise(false);
|
||||
}
|
||||
|
||||
// X25519.
|
||||
|
||||
@@ -269,10 +269,10 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void {
|
||||
sc.removed = true;
|
||||
}
|
||||
|
||||
pub fn reportError(self: *Window, err: js.Value.Global, page: *Page) !void {
|
||||
pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
|
||||
const error_event = try ErrorEvent.initTrusted("error", .{
|
||||
.@"error" = err,
|
||||
.message = err.local().toString(.{}) catch "Unknown error",
|
||||
.@"error" = try err.persist(),
|
||||
.message = err.toString(.{}) catch "Unknown error",
|
||||
.bubbles = false,
|
||||
.cancelable = true,
|
||||
}, page);
|
||||
@@ -552,20 +552,24 @@ const ScheduleCallback = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
switch (self.mode) {
|
||||
.idle => {
|
||||
const IdleDeadline = @import("IdleDeadline.zig");
|
||||
self.cb.local().call(void, .{IdleDeadline{}}) catch |err| {
|
||||
ls.toLocal(self.cb).call(void, .{IdleDeadline{}}) catch |err| {
|
||||
log.warn(.js, "window.idleCallback", .{ .name = self.name, .err = err });
|
||||
};
|
||||
},
|
||||
.animation_frame => {
|
||||
self.cb.local().call(void, .{page.window._performance.now()}) catch |err| {
|
||||
ls.toLocal(self.cb).call(void, .{page.window._performance.now()}) catch |err| {
|
||||
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
||||
};
|
||||
},
|
||||
.normal => {
|
||||
self.cb.local().call(void, self.params) catch |err| {
|
||||
ls.toLocal(self.cb).call(void, self.params) catch |err| {
|
||||
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
|
||||
};
|
||||
},
|
||||
|
||||
@@ -46,20 +46,22 @@ pub fn getPending(_: *const Animation) bool {
|
||||
|
||||
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
|
||||
if (self._finished_resolver == null) {
|
||||
const resolver = try page.js.createPromiseResolver().persist();
|
||||
resolver.local().resolve("Animation.getFinished", self);
|
||||
self._finished_resolver = resolver;
|
||||
const resolver = page.js.local.?.createPromiseResolver();
|
||||
resolver.resolve("Animation.getFinished", self);
|
||||
self._finished_resolver = try resolver.persist();
|
||||
return resolver.promise();
|
||||
}
|
||||
return self._finished_resolver.?.local().promise();
|
||||
return page.js.toLocal(self._finished_resolver).?.promise();
|
||||
}
|
||||
|
||||
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
|
||||
// never resolved, because we're always "finished"
|
||||
if (self._ready_resolver == null) {
|
||||
const resolver = try page.js.createPromiseResolver().persist();
|
||||
self._ready_resolver = resolver;
|
||||
const resolver = page.js.local.?.createPromiseResolver();
|
||||
self._ready_resolver = try resolver.persist();
|
||||
return resolver.promise();
|
||||
}
|
||||
return self._ready_resolver.?.local().promise();
|
||||
return page.js.toLocal(self._ready_resolver).?.promise();
|
||||
}
|
||||
|
||||
pub fn getEffect(self: *const Animation) ?js.Object.Global {
|
||||
|
||||
@@ -66,7 +66,7 @@ pub fn replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise
|
||||
_ = self;
|
||||
_ = text;
|
||||
// TODO: clear self.css_rules
|
||||
return page.js.resolvePromise({});
|
||||
return page.js.local.?.resolvePromise({});
|
||||
}
|
||||
|
||||
pub fn replaceSync(self: *CSSStyleSheet, text: []const u8) !void {
|
||||
|
||||
@@ -50,8 +50,8 @@ pub const Build = struct {
|
||||
pub fn complete(node: *Node, page: *Page) !void {
|
||||
const el = node.as(Element);
|
||||
const on_load = el.getAttributeSafe("onload") orelse return;
|
||||
if (page.js.stringToFunction(on_load)) |func| {
|
||||
page.window._on_load = try func.persist();
|
||||
if (page.js.stringToPersistedFunction(on_load)) |func| {
|
||||
page.window._on_load = func;
|
||||
} else |err| {
|
||||
log.err(.js, "body.onload", .{ .err = err, .str = on_load });
|
||||
}
|
||||
|
||||
@@ -160,10 +160,12 @@ pub fn invokeAttributeChangedCallbackOnElement(element: *Element, name: []const
|
||||
fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefinition, comptime callback_name: [:0]const u8, args: anytype, page: *Page) void {
|
||||
_ = definition;
|
||||
|
||||
const ctx = page.js;
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
// Get the JS element object
|
||||
const js_val = ctx.zigValueToJs(element, .{}) catch return;
|
||||
const js_val = ls.local.zigValueToJs(element, .{}) catch return;
|
||||
const js_element = js_val.toObject();
|
||||
|
||||
// Call the callback method if it exists
|
||||
@@ -195,8 +197,26 @@ pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
|
||||
page._upgrading_element = node;
|
||||
defer page._upgrading_element = prev_upgrading;
|
||||
|
||||
// PERFORMANCE OPTIMIZATION: This pattern is discouraged in general code.
|
||||
// Used here because: (1) multiple early returns before needing Local,
|
||||
// (2) called from both V8 callbacks (Local exists) and parser (no Local).
|
||||
// Prefer either: requiring *const js.Local parameter, OR always creating
|
||||
// Local.Scope upfront.
|
||||
var ls: ?js.Local.Scope = null;
|
||||
var local = blk: {
|
||||
if (page.js.local) |l| {
|
||||
break :blk l;
|
||||
}
|
||||
ls = undefined;
|
||||
page.js.localScope(&ls.?);
|
||||
break :blk &ls.?.local;
|
||||
};
|
||||
defer if (ls) |*_ls| {
|
||||
_ls.deinit();
|
||||
};
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
_ = definition.constructor.local().newInstance(&caught) catch |err| {
|
||||
_ = local.toLocal(definition.constructor).newInstance(&caught) catch |err| {
|
||||
log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err, .caught = caught });
|
||||
return;
|
||||
};
|
||||
@@ -207,9 +227,11 @@ fn invokeCallback(self: *Custom, comptime callback_name: [:0]const u8, args: any
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = page.js;
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const js_val = ctx.zigValueToJs(self, .{}) catch return;
|
||||
const js_val = ls.local.zigValueToJs(self, .{}) catch return;
|
||||
const js_element = js_val.toObject();
|
||||
|
||||
js_element.callMethod(void, callback_name, args) catch return;
|
||||
|
||||
@@ -130,16 +130,16 @@ pub const Build = struct {
|
||||
self._src = element.getAttributeSafe("src") orelse "";
|
||||
|
||||
if (element.getAttributeSafe("onload")) |on_load| {
|
||||
if (page.js.stringToFunction(on_load)) |func| {
|
||||
self._on_load = try func.persist();
|
||||
if (page.js.stringToPersistedFunction(on_load)) |func| {
|
||||
self._on_load = func;
|
||||
} else |err| {
|
||||
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
|
||||
}
|
||||
}
|
||||
|
||||
if (element.getAttributeSafe("onerror")) |on_error| {
|
||||
if (page.js.stringToFunction(on_error)) |func| {
|
||||
self._on_error = try func.persist();
|
||||
if (page.js.stringToPersistedFunction(on_error)) |func| {
|
||||
self._on_error = func;
|
||||
} else |err| {
|
||||
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ const Allocator = std.mem.Allocator;
|
||||
const CustomEvent = @This();
|
||||
|
||||
_proto: *Event,
|
||||
_arena: Allocator,
|
||||
_detail: ?js.Value.Global = null,
|
||||
_arena: Allocator,
|
||||
|
||||
const CustomEventOptions = struct {
|
||||
detail: ?js.Value.Global = null,
|
||||
|
||||
@@ -61,7 +61,7 @@ pub fn asEvent(self: *PopStateEvent) *Event {
|
||||
|
||||
pub fn getState(self: *PopStateEvent, page: *Page) !?js.Value {
|
||||
const s = self._state orelse return null;
|
||||
return try page.js.parseJSON(s);
|
||||
return try page.js.local.?.parseJSON(s);
|
||||
}
|
||||
|
||||
pub fn hasUAVisualTransition(_: *PopStateEvent) bool {
|
||||
|
||||
@@ -267,8 +267,9 @@ pub fn navigateInner(
|
||||
//
|
||||
// These will only settle on same-origin navigation (mostly intended for SPAs).
|
||||
// It is fine (and expected) for these to not settle on cross-origin requests :)
|
||||
const committed = try page.js.createPromiseResolver().persist();
|
||||
const finished = try page.js.createPromiseResolver().persist();
|
||||
const local = page.js.local.?;
|
||||
const committed = local.createPromiseResolver();
|
||||
const finished = local.createPromiseResolver();
|
||||
|
||||
const new_url = try URL.resolve(arena, page.url, url, .{});
|
||||
const is_same_document = URL.eqlDocument(new_url, page.url);
|
||||
@@ -280,9 +281,9 @@ pub fn navigateInner(
|
||||
if (is_same_document) {
|
||||
page.url = new_url;
|
||||
|
||||
committed.local().resolve("navigation push", {});
|
||||
committed.resolve("navigation push", {});
|
||||
// todo: Fire navigate event
|
||||
finished.local().resolve("navigation push", {});
|
||||
finished.resolve("navigation push", {});
|
||||
|
||||
_ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true);
|
||||
} else {
|
||||
@@ -293,9 +294,9 @@ pub fn navigateInner(
|
||||
if (is_same_document) {
|
||||
page.url = new_url;
|
||||
|
||||
committed.local().resolve("navigation replace", {});
|
||||
committed.resolve("navigation replace", {});
|
||||
// todo: Fire navigate event
|
||||
finished.local().resolve("navigation replace", {});
|
||||
finished.resolve("navigation replace", {});
|
||||
|
||||
_ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true);
|
||||
} else {
|
||||
@@ -308,9 +309,9 @@ pub fn navigateInner(
|
||||
if (is_same_document) {
|
||||
page.url = new_url;
|
||||
|
||||
committed.local().resolve("navigation traverse", {});
|
||||
committed.resolve("navigation traverse", {});
|
||||
// todo: Fire navigate event
|
||||
finished.local().resolve("navigation traverse", {});
|
||||
finished.resolve("navigation traverse", {});
|
||||
} else {
|
||||
try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script);
|
||||
}
|
||||
@@ -328,9 +329,11 @@ pub fn navigateInner(
|
||||
);
|
||||
try self._proto.dispatch(.{ .currententrychange = event }, page);
|
||||
|
||||
_ = try committed.persist();
|
||||
_ = try finished.persist();
|
||||
return .{
|
||||
.committed = try committed.local().promise().persist(),
|
||||
.finished = try finished.local().promise().persist(),
|
||||
.committed = try committed.promise().persist(),
|
||||
.finished = try finished.promise().persist(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,11 +43,10 @@ pub fn dispatch(self: *NavigationEventTarget, event_type: DispatchType, page: *P
|
||||
};
|
||||
};
|
||||
|
||||
const func = if (@field(self, field)) |*g| g.local() else null;
|
||||
return page._event_manager.dispatchWithFunction(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
func,
|
||||
page.js.toLocal(@field(self, field)),
|
||||
.{ .context = "Navigation" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ pub const StateReturn = union(enum) { value: ?js.Value, undefined: void };
|
||||
pub fn getState(self: *const NavigationHistoryEntry, page: *Page) !StateReturn {
|
||||
if (self._state.source == .navigation) {
|
||||
if (self._state.value) |value| {
|
||||
return .{ .value = try page.js.parseJSON(value) };
|
||||
return .{ .value = try page.js.local.?.parseJSON(value) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,12 +44,14 @@ pub const InitOpts = Request.InitOpts;
|
||||
pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
||||
const request = try Request.init(input, options, page);
|
||||
|
||||
const resolver = page.js.local.?.createPromiseResolver();
|
||||
|
||||
const fetch = try page.arena.create(Fetch);
|
||||
fetch.* = .{
|
||||
._page = page,
|
||||
._buf = .empty,
|
||||
._url = try page.arena.dupe(u8, request._url),
|
||||
._resolver = try page.js.createPromiseResolver().persist(),
|
||||
._resolver = try resolver.persist(),
|
||||
._response = try Response.init(null, .{ .status = 0 }, page),
|
||||
};
|
||||
|
||||
@@ -77,7 +79,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
||||
.done_callback = httpDoneCallback,
|
||||
.error_callback = httpErrorCallback,
|
||||
});
|
||||
return fetch._resolver.local().promise();
|
||||
return resolver.promise();
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Fetch) void {
|
||||
@@ -149,13 +151,22 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
.len = self._buf.items.len,
|
||||
});
|
||||
|
||||
return self._resolver.local().resolve("fetch done", self._response);
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self._page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
return ls.toLocal(self._resolver).resolve("fetch done", self._response);
|
||||
}
|
||||
|
||||
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
||||
self._response._type = .@"error"; // Set type to error for network failures
|
||||
self._resolver.local().reject("fetch error", @errorName(err));
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self._page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
ls.toLocal(self._resolver).reject("fetch error", @errorName(err));
|
||||
}
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
|
||||
@@ -115,15 +115,16 @@ pub fn isOK(self: *const Response) bool {
|
||||
|
||||
pub fn getText(self: *const Response, page: *Page) !js.Promise {
|
||||
const body = self._body orelse "";
|
||||
return page.js.resolvePromise(body);
|
||||
return page.js.local.?.resolvePromise(body);
|
||||
}
|
||||
|
||||
pub fn getJson(self: *Response, page: *Page) !js.Promise {
|
||||
const body = self._body orelse "";
|
||||
const value = page.js.parseJSON(body) catch |err| {
|
||||
return page.js.rejectPromise(.{@errorName(err)});
|
||||
const local = page.js.local.?;
|
||||
const value = local.parseJSON(body) catch |err| {
|
||||
return local.rejectPromise(.{@errorName(err)});
|
||||
};
|
||||
return page.js.resolvePromise(try value.persist());
|
||||
return local.resolvePromise(try value.persist());
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -131,7 +131,7 @@ pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void
|
||||
|
||||
self._method = try parseMethod(method_);
|
||||
self._url = try URL.resolve(self._arena, self._page.base(), url, .{ .always_dupe = true });
|
||||
try self.stateChanged(.opened, self._page);
|
||||
try self.stateChanged(.opened, self._page.js.local.?, self._page);
|
||||
}
|
||||
|
||||
pub fn setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const u8, page: *Page) !void {
|
||||
@@ -254,7 +254,7 @@ pub fn getResponse(self: *XMLHttpRequest, page: *Page) !?Response {
|
||||
const res: Response = switch (self._response_type) {
|
||||
.text => .{ .text = data },
|
||||
.json => blk: {
|
||||
const value = try page.js.parseJSON(data);
|
||||
const value = try page.js.local.?.parseJSON(data);
|
||||
break :blk .{ .json = try value.persist() };
|
||||
},
|
||||
.document => blk: {
|
||||
@@ -322,19 +322,32 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void {
|
||||
}
|
||||
self._response_url = try self._arena.dupeZ(u8, std.mem.span(header.url));
|
||||
|
||||
try self.stateChanged(.headers_received, self._page);
|
||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, self._page);
|
||||
try self.stateChanged(.loading, self._page);
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.stateChanged(.headers_received, local, page);
|
||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, local, page);
|
||||
try self.stateChanged(.loading, local, page);
|
||||
}
|
||||
|
||||
fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||
const self: *XMLHttpRequest = @ptrCast(@alignCast(transfer.ctx));
|
||||
try self._response_data.appendSlice(self._arena, data);
|
||||
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
try self._proto.dispatch(.progress, .{
|
||||
.total = self._response_len orelse 0,
|
||||
.loaded = self._response_data.items.len,
|
||||
}, self._page);
|
||||
}, &ls.local, page);
|
||||
}
|
||||
|
||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
@@ -350,17 +363,25 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||
// Not that the request is done, the http/client will free the transfer
|
||||
// object. It isn't safe to keep it around.
|
||||
self._transfer = null;
|
||||
try self.stateChanged(.done, self._page);
|
||||
|
||||
const page = self._page;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.stateChanged(.done, local, page);
|
||||
|
||||
const loaded = self._response_data.items.len;
|
||||
try self._proto.dispatch(.load, .{
|
||||
.total = loaded,
|
||||
.loaded = loaded,
|
||||
}, self._page);
|
||||
}, local, page);
|
||||
try self._proto.dispatch(.load_end, .{
|
||||
.total = loaded,
|
||||
.loaded = loaded,
|
||||
}, self._page);
|
||||
}, local, page);
|
||||
}
|
||||
|
||||
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
@@ -392,12 +413,18 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
||||
const new_state: ReadyState = if (is_abort) .unsent else .done;
|
||||
if (new_state != self._ready_state) {
|
||||
const page = self._page;
|
||||
try self.stateChanged(new_state, page);
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const local = &ls.local;
|
||||
|
||||
try self.stateChanged(new_state, local, page);
|
||||
if (is_abort) {
|
||||
try self._proto.dispatch(.abort, null, page);
|
||||
try self._proto.dispatch(.abort, null, local, page);
|
||||
}
|
||||
try self._proto.dispatch(.err, null, page);
|
||||
try self._proto.dispatch(.load_end, null, page);
|
||||
try self._proto.dispatch(.err, null, local, page);
|
||||
try self._proto.dispatch(.load_end, null, local, page);
|
||||
}
|
||||
|
||||
const level: log.Level = if (err == error.Abort) .debug else .err;
|
||||
@@ -408,7 +435,7 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
||||
});
|
||||
}
|
||||
|
||||
fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
|
||||
fn stateChanged(self: *XMLHttpRequest, state: ReadyState, local: *const js.Local, page: *Page) !void {
|
||||
if (state == self._ready_state) {
|
||||
return;
|
||||
}
|
||||
@@ -416,11 +443,10 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
|
||||
self._ready_state = state;
|
||||
|
||||
const event = try Event.initTrusted("readystatechange", .{}, page);
|
||||
const func = if (self._on_ready_state_change) |*g| g.local() else null;
|
||||
try page._event_manager.dispatchWithFunction(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
func,
|
||||
local.toLocal(self._on_ready_state_change),
|
||||
.{ .context = "XHR state change" },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub fn asEventTarget(self: *XMLHttpRequestEventTarget) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, page: *Page) !void {
|
||||
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, local: *const js.Local, page: *Page) !void {
|
||||
const field, const typ = comptime blk: {
|
||||
break :blk switch (event_type) {
|
||||
.abort => .{ "_on_abort", "abort" },
|
||||
@@ -63,11 +63,10 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
|
||||
page,
|
||||
);
|
||||
|
||||
const func = if (@field(self, field)) |*g| g.local() else null;
|
||||
return page._event_manager.dispatchWithFunction(
|
||||
self.asEventTarget(),
|
||||
event.asEvent(),
|
||||
func,
|
||||
local.toLocal(@field(self, field)),
|
||||
.{ .context = "XHR " ++ typ },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,12 +137,12 @@ pub fn callPullIfNeeded(self: *ReadableStream) !void {
|
||||
|
||||
self._pulling = true;
|
||||
|
||||
const pull_fn = &(self._pull_fn orelse return);
|
||||
const pull_fn = self._page.js.toLocal(self._pull_fn) orelse return;
|
||||
|
||||
// Call the pull function
|
||||
// Note: In a complete implementation, we'd handle the promise returned by pull
|
||||
// and set _pulling = false when it resolves
|
||||
try pull_fn.local().call(void, .{self._controller});
|
||||
try pull_fn.call(void, .{self._controller});
|
||||
|
||||
self._pulling = false;
|
||||
|
||||
@@ -167,13 +167,15 @@ fn shouldCallPull(self: *const ReadableStream) bool {
|
||||
}
|
||||
|
||||
pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promise {
|
||||
const local = page.js.local.?;
|
||||
|
||||
if (self._state != .readable) {
|
||||
if (self._cancel) |c| {
|
||||
if (c.resolver) |r| {
|
||||
return r.local().promise();
|
||||
return local.toLocal(r).promise();
|
||||
}
|
||||
}
|
||||
return page.js.resolvePromise(.{});
|
||||
return local.resolvePromise(.{});
|
||||
}
|
||||
|
||||
if (self._cancel == null) {
|
||||
@@ -181,16 +183,21 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi
|
||||
}
|
||||
|
||||
var c = &self._cancel.?;
|
||||
if (c.resolver == null) {
|
||||
c.resolver = try page.js.createPromiseResolver().persist();
|
||||
}
|
||||
var resolver = blk: {
|
||||
if (c.resolver) |r| {
|
||||
break :blk local.toLocal(r);
|
||||
}
|
||||
var temp = local.createPromiseResolver();
|
||||
c.resolver = try temp.persist();
|
||||
break :blk temp;
|
||||
};
|
||||
|
||||
// Execute the cancel callback if provided
|
||||
if (c.callback) |*cb| {
|
||||
if (c.callback) |cb| {
|
||||
if (reason) |r| {
|
||||
try cb.local().call(void, .{r});
|
||||
try local.toLocal(cb).call(void, .{r});
|
||||
} else {
|
||||
try cb.local().call(void, .{});
|
||||
try local.toLocal(cb).call(void, .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,13 +208,12 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi
|
||||
.done = true,
|
||||
.value = .empty,
|
||||
};
|
||||
for (self._controller._pending_reads.items) |*resolver| {
|
||||
resolver.local().resolve("stream cancelled", result);
|
||||
for (self._controller._pending_reads.items) |r| {
|
||||
local.toLocal(r).resolve("stream cancelled", result);
|
||||
}
|
||||
self._controller._pending_reads.clearRetainingCapacity();
|
||||
|
||||
c.resolver.?.local().resolve("ReadableStream.cancel", {});
|
||||
return c.resolver.?.local().promise();
|
||||
resolver.resolve("ReadableStream.cancel", {});
|
||||
return resolver.promise();
|
||||
}
|
||||
|
||||
const Cancel = struct {
|
||||
@@ -250,7 +256,7 @@ pub const AsyncIterator = struct {
|
||||
|
||||
pub fn @"return"(self: *AsyncIterator, page: *Page) !js.Promise {
|
||||
self._reader.releaseLock();
|
||||
return page.js.resolvePromise(.{ .done = true, .value = null });
|
||||
return page.js.local.?.resolvePromise(.{ .done = true, .value = null });
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -57,9 +57,9 @@ pub fn init(stream: *ReadableStream, high_water_mark: u32, page: *Page) !*Readab
|
||||
}
|
||||
|
||||
pub fn addPendingRead(self: *ReadableStreamDefaultController, page: *Page) !js.Promise {
|
||||
const resolver = try page.js.createPromiseResolver().persist();
|
||||
try self._pending_reads.append(self._arena, resolver);
|
||||
return resolver.local().promise();
|
||||
const resolver = page.js.local.?.createPromiseResolver();
|
||||
try self._pending_reads.append(self._arena, try resolver.persist());
|
||||
return resolver.promise();
|
||||
}
|
||||
|
||||
pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
|
||||
@@ -79,7 +79,7 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
|
||||
.done = false,
|
||||
.value = .fromChunk(chunk),
|
||||
};
|
||||
resolver.local().resolve("stream enqueue", result);
|
||||
self._page.js.toLocal(resolver).resolve("stream enqueue", result);
|
||||
}
|
||||
|
||||
pub fn close(self: *ReadableStreamDefaultController) !void {
|
||||
@@ -94,8 +94,8 @@ pub fn close(self: *ReadableStreamDefaultController) !void {
|
||||
.done = true,
|
||||
.value = .empty,
|
||||
};
|
||||
for (self._pending_reads.items) |*resolver| {
|
||||
resolver.local().resolve("stream close", result);
|
||||
for (self._pending_reads.items) |resolver| {
|
||||
self._page.js.toLocal(resolver).resolve("stream close", result);
|
||||
}
|
||||
self._pending_reads.clearRetainingCapacity();
|
||||
}
|
||||
@@ -109,8 +109,8 @@ pub fn doError(self: *ReadableStreamDefaultController, err: []const u8) !void {
|
||||
self._stream._stored_error = try self._page.arena.dupe(u8, err);
|
||||
|
||||
// Reject all pending reads
|
||||
for (self._pending_reads.items) |*resolver| {
|
||||
resolver.local().reject("stream errror", err);
|
||||
for (self._pending_reads.items) |resolver| {
|
||||
self._page.js.toLocal(resolver).reject("stream errror", err);
|
||||
}
|
||||
self._pending_reads.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
@@ -56,12 +56,12 @@ pub const ReadResult = struct {
|
||||
|
||||
pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
||||
const stream = self._stream orelse {
|
||||
return page.js.rejectPromise("Reader has been released");
|
||||
return page.js.local.?.rejectPromise("Reader has been released");
|
||||
};
|
||||
|
||||
if (stream._state == .errored) {
|
||||
const err = stream._stored_error orelse "Stream errored";
|
||||
return page.js.rejectPromise(err);
|
||||
return page.js.local.?.rejectPromise(err);
|
||||
}
|
||||
|
||||
if (stream._controller.dequeue()) |chunk| {
|
||||
@@ -69,7 +69,7 @@ pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
||||
.done = false,
|
||||
.value = .fromChunk(chunk),
|
||||
};
|
||||
return page.js.resolvePromise(result);
|
||||
return page.js.local.?.resolvePromise(result);
|
||||
}
|
||||
|
||||
if (stream._state == .closed) {
|
||||
@@ -77,7 +77,7 @@ pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
||||
.done = true,
|
||||
.value = .empty,
|
||||
};
|
||||
return page.js.resolvePromise(result);
|
||||
return page.js.local.?.resolvePromise(result);
|
||||
}
|
||||
|
||||
// No data, but not closed. We need to queue the read for any future data
|
||||
@@ -93,7 +93,7 @@ pub fn releaseLock(self: *ReadableStreamDefaultReader) void {
|
||||
|
||||
pub fn cancel(self: *ReadableStreamDefaultReader, reason_: ?[]const u8, page: *Page) !js.Promise {
|
||||
const stream = self._stream orelse {
|
||||
return page.js.rejectPromise("Reader has been released");
|
||||
return page.js.local.?.rejectPromise("Reader has been released");
|
||||
};
|
||||
|
||||
self.releaseLock();
|
||||
|
||||
@@ -24,7 +24,6 @@ const Selector = @import("../../browser/webapi/selector/Selector.zig");
|
||||
|
||||
const dump = @import("../../browser/dump.zig");
|
||||
const js = @import("../../browser/js/js.zig");
|
||||
const v8 = js.v8;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -273,16 +272,32 @@ fn resolveNode(cmd: anytype) !void {
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||
|
||||
var js_context = page.js;
|
||||
if (params.executionContextId) |context_id| {
|
||||
if (js_context.debugContextId() != context_id) {
|
||||
for (bc.isolated_worlds.items) |*isolated_world| {
|
||||
js_context = &(isolated_world.executor.context orelse return error.ContextNotFound);
|
||||
if (js_context.debugContextId() == context_id) {
|
||||
break;
|
||||
}
|
||||
} else return error.ContextNotFound;
|
||||
var ls: ?js.Local.Scope = null;
|
||||
defer if (ls) |*_ls| {
|
||||
_ls.deinit();
|
||||
};
|
||||
|
||||
if (params.executionContextId) |context_id| blk: {
|
||||
ls = undefined;
|
||||
page.js.localScope(&ls.?);
|
||||
if (ls.?.local.debugContextId() == context_id) {
|
||||
break :blk;
|
||||
}
|
||||
// not the default scope, check the other ones
|
||||
for (bc.isolated_worlds.items) |*isolated_world| {
|
||||
ls.?.deinit();
|
||||
ls = null;
|
||||
|
||||
const ctx = &(isolated_world.executor.context orelse return error.ContextNotFound);
|
||||
ls = undefined;
|
||||
ctx.localScope(&ls.?);
|
||||
if (ls.?.local.debugContextId() == context_id) {
|
||||
break :blk;
|
||||
}
|
||||
} else return error.ContextNotFound;
|
||||
} else {
|
||||
ls = undefined;
|
||||
page.js.localScope(&ls.?);
|
||||
}
|
||||
|
||||
const input_node_id = params.nodeId orelse params.backendNodeId orelse return error.InvalidParam;
|
||||
@@ -291,7 +306,7 @@ fn resolveNode(cmd: anytype) !void {
|
||||
// node._node is a *DOMNode we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement
|
||||
// So we use the Node.Union when retrieve the value from the environment
|
||||
const remote_object = try bc.inspector.getRemoteObject(
|
||||
js_context,
|
||||
&ls.?.local,
|
||||
params.objectGroup orelse "",
|
||||
node.dom,
|
||||
);
|
||||
@@ -377,15 +392,20 @@ fn scrollIntoViewIfNeeded(cmd: anytype) !void {
|
||||
return cmd.sendResult(null, .{});
|
||||
}
|
||||
|
||||
fn getNode(arena: Allocator, browser_context: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
|
||||
fn getNode(arena: Allocator, bc: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
|
||||
const input_node_id = node_id orelse backend_node_id;
|
||||
if (input_node_id) |input_node_id_| {
|
||||
return browser_context.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
|
||||
return bc.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
|
||||
}
|
||||
if (object_id) |object_id_| {
|
||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
// Retrieve the object from which ever context it is in.
|
||||
const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
|
||||
return try browser_context.node_registry.register(@ptrCast(@alignCast(parser_node)));
|
||||
const parser_node = try bc.inspector.getNodePtr(arena, object_id_, &ls.local);
|
||||
return try bc.node_registry.register(@ptrCast(@alignCast(parser_node)));
|
||||
}
|
||||
return error.MissingParams;
|
||||
}
|
||||
|
||||
@@ -196,9 +196,12 @@ fn createIsolatedWorld(cmd: anytype) !void {
|
||||
// Create the auxdata json for the contextCreated event
|
||||
// Calling contextCreated will assign a Id to the context and send the contextCreated event
|
||||
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{params.frameId});
|
||||
bc.inspector.contextCreated(js_context, world.name, "", aux_data, false);
|
||||
var ls: js.Local.Scope = undefined;
|
||||
js_context.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
return cmd.sendResult(.{ .executionContextId = js_context.debugContextId() }, .{});
|
||||
bc.inspector.contextCreated(&ls.local, world.name, "", aux_data, false);
|
||||
return cmd.sendResult(.{ .executionContextId = ls.local.debugContextId() }, .{});
|
||||
}
|
||||
|
||||
fn navigate(cmd: anytype) !void {
|
||||
@@ -353,8 +356,13 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
||||
{
|
||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
bc.inspector.contextCreated(
|
||||
page.js,
|
||||
&ls.local,
|
||||
"",
|
||||
try page.getOrigin(arena) orelse "",
|
||||
aux_data,
|
||||
@@ -363,9 +371,15 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
||||
}
|
||||
for (bc.isolated_worlds.items) |*isolated_world| {
|
||||
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||
|
||||
// Calling contextCreated will assign a new Id to the context and send the contextCreated event
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
(isolated_world.executor.context orelse continue).localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
bc.inspector.contextCreated(
|
||||
&isolated_world.executor.context.?,
|
||||
&ls.local,
|
||||
isolated_world.name,
|
||||
"://",
|
||||
aux_json,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const log = @import("../../log.zig");
|
||||
const js = @import("../../browser/js/js.zig");
|
||||
|
||||
// TODO: hard coded IDs
|
||||
const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";
|
||||
@@ -176,9 +177,13 @@ fn createTarget(cmd: anytype) !void {
|
||||
|
||||
const page = try bc.session.createPage();
|
||||
{
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||
bc.inspector.contextCreated(
|
||||
page.js,
|
||||
&ls.local,
|
||||
"",
|
||||
"", // @ZIGDOM
|
||||
// try page.origin(arena),
|
||||
|
||||
@@ -85,15 +85,18 @@ pub fn run(allocator: Allocator, file: []const u8, session: *lp.Session) !void {
|
||||
const page = try session.createPage();
|
||||
defer session.removePage();
|
||||
|
||||
const js_context = page.js;
|
||||
var ls: lp.js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var try_catch: lp.js.TryCatch = undefined;
|
||||
try_catch.init(js_context);
|
||||
try_catch.init(&ls.local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
try page.navigate(url, .{});
|
||||
_ = session.wait(2000);
|
||||
|
||||
js_context.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
||||
ls.local.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
||||
const caught = try_catch.caughtOrError(allocator, err);
|
||||
std.debug.print("{s}: test failure\nError: {f}\n", .{ file, caught });
|
||||
return err;
|
||||
|
||||
@@ -118,20 +118,23 @@ fn run(
|
||||
|
||||
_ = page.wait(2000);
|
||||
|
||||
const js_context = page.js;
|
||||
var ls: lp.js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var try_catch: lp.js.TryCatch = undefined;
|
||||
try_catch.init(js_context);
|
||||
try_catch.init(&ls.local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
// Check the final test status.
|
||||
js_context.eval("report.status", "teststatus") catch |err| {
|
||||
ls.local.eval("report.status", "teststatus") catch |err| {
|
||||
const caught = try_catch.caughtOrError(arena, err);
|
||||
err_out.* = caught.exception;
|
||||
return err;
|
||||
};
|
||||
|
||||
// return the detailed result.
|
||||
const value = js_context.exec("report.log", "report") catch |err| {
|
||||
const value = ls.local.exec("report.log", "report") catch |err| {
|
||||
const caught = try_catch.caughtOrError(arena, err);
|
||||
err_out.* = caught.exception;
|
||||
return err;
|
||||
|
||||
@@ -396,9 +396,12 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
||||
0,
|
||||
);
|
||||
|
||||
const js_context = page.js;
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
try_catch.init(js_context);
|
||||
try_catch.init(&ls.local);
|
||||
defer try_catch.deinit();
|
||||
|
||||
try page.navigate(url, .{});
|
||||
@@ -406,7 +409,7 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
||||
|
||||
test_browser.runMicrotasks();
|
||||
|
||||
js_context.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
||||
ls.local.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
||||
const caught = try_catch.caughtOrError(arena_allocator, err);
|
||||
std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught });
|
||||
return err;
|
||||
|
||||
Reference in New Issue
Block a user