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