mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 07:33:16 +00:00
Merge pull request #1374 from lightpanda-io/fix_context_lifetime
Fix context lifetime
This commit is contained in:
@@ -367,14 +367,18 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
event._target = getAdjustedTarget(original_target, current_target);
|
event._target = getAdjustedTarget(original_target, current_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
switch (listener.function) {
|
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| {
|
.string => |string| {
|
||||||
const str = try page.call_arena.dupeZ(u8, string.str());
|
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| {
|
.object => |obj_global| {
|
||||||
const obj = obj_global.local();
|
const obj = ls.toLocal(obj_global);
|
||||||
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
||||||
try handleEvent.callWithThis(void, obj, .{event});
|
try handleEvent.callWithThis(void, obj, .{event});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -562,21 +562,24 @@ fn _documentIsComplete(self: *Page) !void {
|
|||||||
const event = try Event.initTrusted("load", .{}, self);
|
const event = try Event.initTrusted("load", .{}, self);
|
||||||
// this event is weird, it's dispatched directly on the window, but
|
// this event is weird, it's dispatched directly on the window, but
|
||||||
// with the document as the target
|
// with the document as the target
|
||||||
|
|
||||||
|
var ls: JS.Local.Scope = undefined;
|
||||||
|
self.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
event._target = self.document.asEventTarget();
|
event._target = self.document.asEventTarget();
|
||||||
const on_load = if (self.window._on_load) |*g| g.local() else null;
|
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchWithFunction(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
on_load,
|
ls.toLocal(self.window._on_load),
|
||||||
.{ .inject_target = false, .context = "page load" },
|
.{ .inject_target = false, .context = "page load" },
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageshow_event = try PageTransitionEvent.initTrusted("pageshow", .{}, self);
|
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(
|
try self._event_manager.dispatchWithFunction(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
pageshow_event.asEvent(),
|
pageshow_event.asEvent(),
|
||||||
on_pageshow,
|
ls.toLocal(self.window._on_pageshow),
|
||||||
.{ .context = "page show" },
|
.{ .context = "page show" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -748,10 +751,6 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
|||||||
var timer = try std.time.Timer.start();
|
var timer = try std.time.Timer.start();
|
||||||
var ms_remaining = wait_ms;
|
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 scheduler = &self.scheduler;
|
||||||
var http_client = self._session.browser.http_client;
|
var http_client = self._session.browser.http_client;
|
||||||
|
|
||||||
@@ -808,10 +807,6 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
|
|||||||
// it AFTER.
|
// it AFTER.
|
||||||
const ms_to_next_task = try scheduler.run();
|
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 http_active = http_client.active;
|
||||||
const total_network_activity = http_active + http_client.intercepted;
|
const total_network_activity = http_active + http_client.intercepted;
|
||||||
if (self._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
if (self._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||||
@@ -2005,8 +2000,12 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
|
|||||||
self._upgrading_element = node;
|
self._upgrading_element = node;
|
||||||
defer self._upgrading_element = prev_upgrading;
|
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;
|
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 });
|
log.warn(.js, "custom element constructor", .{ .name = name, .err = err, .caught = caught });
|
||||||
return node;
|
return node;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const js = @import("js/js.zig");
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
|
||||||
|
|
||||||
|
|||||||
@@ -271,11 +271,15 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
.ctx = ctx,
|
.ctx = ctx,
|
||||||
.url = remote_url.?,
|
.url = remote_url.?,
|
||||||
.element = element,
|
.element = element,
|
||||||
.stack = page.js.stackTrace() catch "???",
|
.stack = ls.local.stackTrace() catch "???",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,11 +361,15 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
|||||||
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
.url = url,
|
.url = url,
|
||||||
.ctx = "module",
|
.ctx = "module",
|
||||||
.referrer = referrer,
|
.referrer = referrer,
|
||||||
.stack = self.page.js.stackTrace() catch "???",
|
.stack = ls.local.stackTrace() catch "???",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,11 +456,15 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
|||||||
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
self.page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
log.debug(.http, "script queue", .{
|
log.debug(.http, "script queue", .{
|
||||||
.url = url,
|
.url = url,
|
||||||
.ctx = "dynamic module",
|
.ctx = "dynamic module",
|
||||||
.referrer = referrer,
|
.referrer = referrer,
|
||||||
.stack = self.page.js.stackTrace() catch "???",
|
.stack = ls.local.stackTrace() catch "???",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -785,6 +797,12 @@ pub const Script = struct {
|
|||||||
.cacheable = cacheable,
|
.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
|
// Handle importmap special case here: the content is a JSON containing
|
||||||
// imports.
|
// imports.
|
||||||
if (self.kind == .importmap) {
|
if (self.kind == .importmap) {
|
||||||
@@ -795,25 +813,24 @@ pub const Script = struct {
|
|||||||
.kind = self.kind,
|
.kind = self.kind,
|
||||||
.cacheable = cacheable,
|
.cacheable = cacheable,
|
||||||
});
|
});
|
||||||
self.executeCallback("error", script_element._on_error, page);
|
self.executeCallback("error", local.toLocal(script_element._on_error), page);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.executeCallback("load", script_element._on_load, page);
|
self.executeCallback("load", local.toLocal(script_element._on_load), page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const js_context = page.js;
|
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
const success = blk: {
|
const success = blk: {
|
||||||
const content = self.source.content();
|
const content = self.source.content();
|
||||||
switch (self.kind) {
|
switch (self.kind) {
|
||||||
.javascript => _ = js_context.eval(content, url) catch break :blk false,
|
.javascript => _ = local.eval(content, url) catch break :blk false,
|
||||||
.module => {
|
.module => {
|
||||||
// We don't care about waiting for the evaluation here.
|
// 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.
|
.importmap => unreachable, // handled before the try/catch.
|
||||||
}
|
}
|
||||||
@@ -833,7 +850,7 @@ pub const Script = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
self.executeCallback("load", script_element._on_load, page);
|
self.executeCallback("load", local.toLocal(script_element._on_load), page);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,12 +861,11 @@ pub const Script = struct {
|
|||||||
.cacheable = cacheable,
|
.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 {
|
fn executeCallback(self: *const Script, comptime typ: []const u8, cb_: ?js.Function, page: *Page) void {
|
||||||
const cb_global = cb_ orelse return;
|
const cb = cb_ orelse return;
|
||||||
const cb = cb_global.local();
|
|
||||||
|
|
||||||
const Event = @import("webapi/Event.zig");
|
const Event = @import("webapi/Event.zig");
|
||||||
const event = Event.initTrusted(typ, .{}, page) catch |err| {
|
const event = Event.initTrusted(typ, .{}, page) catch |err| {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const Array = @This();
|
const Array = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Array,
|
handle: *const v8.Array,
|
||||||
|
|
||||||
pub fn len(self: Array) usize {
|
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 {
|
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 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 error.JsException;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
|
pub fn set(self: Array, index: u32, value: anytype, comptime opts: js.Caller.CallOpts) !bool {
|
||||||
const ctx = self.ctx;
|
const js_value = try self.local.zigValueToJs(value, opts);
|
||||||
|
|
||||||
const js_value = try ctx.zigValueToJs(value, opts);
|
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
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;
|
return out.has_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toObject(self: Array) js.Object {
|
pub fn toObject(self: Array) js.Object {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toValue(self: Array) js.Value {
|
pub fn toValue(self: Array) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
569
src/browser/js/Caller.zig
Normal file
569
src/browser/js/Caller.zig
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
const Page = @import("../Page.zig");
|
||||||
|
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const bridge = @import("bridge.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||||
|
|
||||||
|
const v8 = js.v8;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const CALL_ARENA_RETAIN = 1024 * 16;
|
||||||
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||||
|
|
||||||
|
const Caller = @This();
|
||||||
|
local: js.Local,
|
||||||
|
prev_local: ?*const js.Local,
|
||||||
|
|
||||||
|
// Takes the raw v8 isolate and extracts the context from it.
|
||||||
|
pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void {
|
||||||
|
const v8_context_handle = v8.v8__Isolate__GetCurrentContext(v8_isolate);
|
||||||
|
|
||||||
|
const embedder_data = v8.v8__Context__GetEmbedderData(v8_context_handle, 1);
|
||||||
|
var lossless: bool = undefined;
|
||||||
|
const ctx: *Context = @ptrFromInt(v8.v8__BigInt__Uint64Value(embedder_data, &lossless));
|
||||||
|
|
||||||
|
ctx.call_depth += 1;
|
||||||
|
self.* = Caller{
|
||||||
|
.local = .{
|
||||||
|
.ctx = ctx,
|
||||||
|
.handle = v8_context_handle.?,
|
||||||
|
.call_arena = ctx.call_arena,
|
||||||
|
.isolate = .{ .handle = v8_isolate },
|
||||||
|
},
|
||||||
|
.prev_local = ctx.local,
|
||||||
|
};
|
||||||
|
ctx.local = &self.local;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Caller) void {
|
||||||
|
const ctx = self.local.ctx;
|
||||||
|
const call_depth = ctx.call_depth - 1;
|
||||||
|
|
||||||
|
// Because of callbacks, calls can be nested. Because of this, we
|
||||||
|
// can't clear the call_arena after _every_ call. Imagine we have
|
||||||
|
// arr.forEach((i) => { console.log(i); }
|
||||||
|
//
|
||||||
|
// First we call forEach. Inside of our forEach call,
|
||||||
|
// we call console.log. If we reset the call_arena after this call,
|
||||||
|
// it'll reset it for the `forEach` call after, which might still
|
||||||
|
// need the data.
|
||||||
|
//
|
||||||
|
// Therefore, we keep a call_depth, and only reset the call_arena
|
||||||
|
// when a top-level (call_depth == 0) function ends.
|
||||||
|
if (call_depth == 0) {
|
||||||
|
const arena: *ArenaAllocator = @ptrCast(@alignCast(ctx.call_arena.ptr));
|
||||||
|
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.call_depth = call_depth;
|
||||||
|
ctx.local = self.prev_local;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const CallOpts = struct {
|
||||||
|
dom_exception: bool = false,
|
||||||
|
null_as_undefined: bool = false,
|
||||||
|
as_typed_array: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn constructor(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = FunctionCallbackInfo{ .handle = handle };
|
||||||
|
|
||||||
|
if (!info.isConstructCall()) {
|
||||||
|
self.handleError(T, @TypeOf(func), error.InvalidArgument, info, opts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._constructor(func, info) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _constructor(self: *Caller, func: anytype, info: FunctionCallbackInfo) !void {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
const args = try self.getArgs(F, 0, info);
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
|
||||||
|
const ReturnType = @typeInfo(F).@"fn".return_type orelse {
|
||||||
|
@compileError(@typeName(F) ++ " has a constructor without a return type");
|
||||||
|
};
|
||||||
|
|
||||||
|
const new_this_handle = info.getThis();
|
||||||
|
var this = js.Object{ .local = &self.local, .handle = new_this_handle };
|
||||||
|
if (@typeInfo(ReturnType) == .error_union) {
|
||||||
|
const non_error_res = res catch |err| return err;
|
||||||
|
this = try self.local.mapZigInstanceToJs(new_this_handle, non_error_res);
|
||||||
|
} else {
|
||||||
|
this = try self.local.mapZigInstanceToJs(new_this_handle, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got back a different object (existing wrapper), copy the prototype
|
||||||
|
// from new object. (this happens when we're upgrading an CustomElement)
|
||||||
|
if (this.handle != new_this_handle) {
|
||||||
|
const prototype_handle = v8.v8__Object__GetPrototype(new_this_handle).?;
|
||||||
|
var out: v8.MaybeBool = undefined;
|
||||||
|
v8.v8__Object__SetPrototype(this.handle, self.local.handle, prototype_handle, &out);
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
std.debug.assert(out.has_value and out.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info.getReturnValue().set(this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = FunctionCallbackInfo{ .handle = handle };
|
||||||
|
self._method(T, func, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args = try self.getArgs(F, 1, info);
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
info.getReturnValue().set(try self.local.zigValueToJs(res, opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn function(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = FunctionCallbackInfo{ .handle = handle };
|
||||||
|
self._function(func, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _function(self: *Caller, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
const args = try self.getArgs(F, 0, info);
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
info.getReturnValue().set(try self.local.zigValueToJs(res, opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._getIndex(T, func, idx, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _getIndex(self: *Caller, comptime T: type, func: anytype, idx: u32, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args = try self.getArgs(F, 2, info);
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = idx;
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._getNamedIndex(T, func, name, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args = try self.getArgs(F, 2, info);
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = try self.nameToString(name);
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, true, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, js_value: *const v8.Value, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._setNamedIndex(T, func, name, .{ .local = &self.local, .handle = js_value }, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, js_value: js.Value, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args: ParameterTypes(F) = undefined;
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = try self.nameToString(name);
|
||||||
|
@field(args, "2") = try self.local.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
|
||||||
|
if (@typeInfo(F).@"fn".params.len == 4) {
|
||||||
|
@field(args, "3") = self.local.ctx.page;
|
||||||
|
}
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, handle: *const v8.PropertyCallbackInfo, comptime opts: CallOpts) u8 {
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(self.local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const info = PropertyCallbackInfo{ .handle = handle };
|
||||||
|
return self._deleteNamedIndex(T, func, name, info, opts) catch |err| {
|
||||||
|
self.handleError(T, @TypeOf(func), err, info, opts);
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v8.Name, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
const F = @TypeOf(func);
|
||||||
|
var args: ParameterTypes(F) = undefined;
|
||||||
|
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
|
||||||
|
@field(args, "1") = try self.nameToString(name);
|
||||||
|
if (@typeInfo(F).@"fn".params.len == 3) {
|
||||||
|
@field(args, "2") = self.local.ctx.page;
|
||||||
|
}
|
||||||
|
const ret = @call(.auto, func, args);
|
||||||
|
return self.handleIndexedReturn(T, F, false, ret, info, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleIndexedReturn(self: *Caller, comptime T: type, comptime F: type, comptime getter: bool, ret: anytype, info: PropertyCallbackInfo, comptime opts: CallOpts) !u8 {
|
||||||
|
// need to unwrap this error immediately for when opts.null_as_undefined == true
|
||||||
|
// and we need to compare it to null;
|
||||||
|
const non_error_ret = switch (@typeInfo(@TypeOf(ret))) {
|
||||||
|
.error_union => |eu| blk: {
|
||||||
|
break :blk ret catch |err| {
|
||||||
|
// We can't compare err == error.NotHandled if error.NotHandled
|
||||||
|
// isn't part of the possible error set. So we first need to check
|
||||||
|
// if error.NotHandled is part of the error set.
|
||||||
|
if (isInErrorSet(error.NotHandled, eu.error_set)) {
|
||||||
|
if (err == error.NotHandled) {
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.handleError(T, F, err, info, opts);
|
||||||
|
// not intercepted
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
else => ret,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (comptime getter) {
|
||||||
|
info.getReturnValue().set(try self.local.zigValueToJs(non_error_ret, opts));
|
||||||
|
}
|
||||||
|
// intercepted
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isInErrorSet(err: anyerror, comptime T: type) bool {
|
||||||
|
inline for (@typeInfo(T).error_set.?) |e| {
|
||||||
|
if (err == @field(anyerror, e.name)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nameToString(self: *const Caller, name: *const v8.Name) ![]const u8 {
|
||||||
|
return self.local.valueHandleToString(@ptrCast(name), .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
||||||
|
const isolate = self.local.isolate;
|
||||||
|
|
||||||
|
if (comptime @import("builtin").mode == .Debug and @TypeOf(info) == FunctionCallbackInfo) {
|
||||||
|
if (log.enabled(.js, .warn)) {
|
||||||
|
self.logFunctionCallError(@typeName(T), @typeName(F), err, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const js_err: *const v8.Value = switch (err) {
|
||||||
|
error.InvalidArgument => isolate.createTypeError("invalid argument"),
|
||||||
|
error.OutOfMemory => isolate.createError("out of memory"),
|
||||||
|
error.IllegalConstructor => isolate.createError("Illegal Contructor"),
|
||||||
|
else => blk: {
|
||||||
|
if (comptime opts.dom_exception) {
|
||||||
|
const DOMException = @import("../webapi/DOMException.zig");
|
||||||
|
if (DOMException.fromError(err)) |ex| {
|
||||||
|
const value = self.local.zigValueToJs(ex, .{}) catch break :blk isolate.createError("internal error");
|
||||||
|
break :blk value.handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break :blk isolate.createError(@errorName(err));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const js_exception = isolate.throwException(js_err);
|
||||||
|
info.getReturnValue().setValueHandle(js_exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we call a method in javascript: cat.lives('nine');
|
||||||
|
//
|
||||||
|
// Then we'd expect a Zig function with 2 parameters: a self and the string.
|
||||||
|
// In this case, offset == 1. Offset is always 1 for setters or methods.
|
||||||
|
//
|
||||||
|
// Offset is always 0 for constructors.
|
||||||
|
//
|
||||||
|
// For constructors, setters and methods, we can further increase offset + 1
|
||||||
|
// if the first parameter is an instance of Page.
|
||||||
|
//
|
||||||
|
// Finally, if the JS function is called with _more_ parameters and
|
||||||
|
// the last parameter in Zig is an array, we'll try to slurp the additional
|
||||||
|
// parameters into the array.
|
||||||
|
fn getArgs(self: *const Caller, comptime F: type, comptime offset: usize, info: anytype) !ParameterTypes(F) {
|
||||||
|
const local = &self.local;
|
||||||
|
var args: ParameterTypes(F) = undefined;
|
||||||
|
|
||||||
|
const params = @typeInfo(F).@"fn".params[offset..];
|
||||||
|
// Except for the constructor, the first parameter is always `self`
|
||||||
|
// This isn't something we'll bind from JS, so skip it.
|
||||||
|
const params_to_map = blk: {
|
||||||
|
if (params.len == 0) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last parameter is the Page, set it, and exclude it
|
||||||
|
// from our params slice, because we don't want to bind it to
|
||||||
|
// a JS argument
|
||||||
|
if (comptime isPage(params[params.len - 1].type.?)) {
|
||||||
|
@field(args, tupleFieldName(params.len - 1 + offset)) = local.ctx.page;
|
||||||
|
break :blk params[0 .. params.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have neither a Page nor a JsObject. All params must be
|
||||||
|
// bound to a JavaScript value.
|
||||||
|
break :blk params;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params_to_map.len == 0) {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
const js_parameter_count = info.length();
|
||||||
|
const last_js_parameter = params_to_map.len - 1;
|
||||||
|
var is_variadic = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
// This is going to get complicated. If the last Zig parameter
|
||||||
|
// is a slice AND the corresponding javascript parameter is
|
||||||
|
// NOT an an array, then we'll treat it as a variadic.
|
||||||
|
|
||||||
|
const last_parameter_type = params_to_map[params_to_map.len - 1].type.?;
|
||||||
|
const last_parameter_type_info = @typeInfo(last_parameter_type);
|
||||||
|
if (last_parameter_type_info == .pointer and last_parameter_type_info.pointer.size == .slice) {
|
||||||
|
const slice_type = last_parameter_type_info.pointer.child;
|
||||||
|
const corresponding_js_value = info.getArg(@intCast(last_js_parameter), local);
|
||||||
|
if (corresponding_js_value.isArray() == false and corresponding_js_value.isTypedArray() == false and slice_type != u8) {
|
||||||
|
is_variadic = true;
|
||||||
|
if (js_parameter_count == 0) {
|
||||||
|
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
||||||
|
} else if (js_parameter_count >= params_to_map.len) {
|
||||||
|
const arr = try local.call_arena.alloc(last_parameter_type_info.pointer.child, js_parameter_count - params_to_map.len + 1);
|
||||||
|
for (arr, last_js_parameter..) |*a, i| {
|
||||||
|
a.* = try local.jsValueToZig(slice_type, info.getArg(@intCast(i), local));
|
||||||
|
}
|
||||||
|
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = arr;
|
||||||
|
} else {
|
||||||
|
@field(args, tupleFieldName(params_to_map.len + offset - 1)) = &.{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline for (params_to_map, 0..) |param, i| {
|
||||||
|
const field_index = comptime i + offset;
|
||||||
|
if (comptime i == params_to_map.len - 1) {
|
||||||
|
if (is_variadic) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime isPage(param.type.?)) {
|
||||||
|
@compileError("Page must be the last parameter (or 2nd last if there's a JsThis): " ++ @typeName(F));
|
||||||
|
} else if (i >= js_parameter_count) {
|
||||||
|
if (@typeInfo(param.type.?) != .optional) {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
@field(args, tupleFieldName(field_index)) = null;
|
||||||
|
} else {
|
||||||
|
const js_val = info.getArg(@intCast(i), local);
|
||||||
|
@field(args, tupleFieldName(field_index)) = local.jsValueToZig(param.type.?, js_val) catch {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is extracted to speed up compilation. When left inlined in handleError,
|
||||||
|
// this can add as much as 10 seconds of compilation time.
|
||||||
|
fn logFunctionCallError(self: *Caller, type_name: []const u8, func: []const u8, err: anyerror, info: FunctionCallbackInfo) void {
|
||||||
|
const args_dump = self.serializeFunctionArgs(info) catch "failed to serialize args";
|
||||||
|
log.info(.js, "function call error", .{
|
||||||
|
.type = type_name,
|
||||||
|
.func = func,
|
||||||
|
.err = err,
|
||||||
|
.args = args_dump,
|
||||||
|
.stack = self.local.stackTrace() catch |err1| @errorName(err1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serializeFunctionArgs(self: *Caller, info: FunctionCallbackInfo) ![]const u8 {
|
||||||
|
const local = &self.local;
|
||||||
|
var buf = std.Io.Writer.Allocating.init(local.call_arena);
|
||||||
|
|
||||||
|
const separator = log.separator();
|
||||||
|
for (0..info.length()) |i| {
|
||||||
|
try buf.writer.print("{s}{d} - ", .{ separator, i + 1 });
|
||||||
|
const js_value = info.getArg(@intCast(i), local);
|
||||||
|
try local.debugValue(js_value, &buf.writer);
|
||||||
|
}
|
||||||
|
return buf.written();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a function, and returns a tuple for its argument. Used when we
|
||||||
|
// @call a function
|
||||||
|
fn ParameterTypes(comptime F: type) type {
|
||||||
|
const params = @typeInfo(F).@"fn".params;
|
||||||
|
var fields: [params.len]std.builtin.Type.StructField = undefined;
|
||||||
|
|
||||||
|
inline for (params, 0..) |param, i| {
|
||||||
|
fields[i] = .{
|
||||||
|
.name = tupleFieldName(i),
|
||||||
|
.type = param.type.?,
|
||||||
|
.default_value_ptr = null,
|
||||||
|
.is_comptime = false,
|
||||||
|
.alignment = @alignOf(param.type.?),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @Type(.{ .@"struct" = .{
|
||||||
|
.layout = .auto,
|
||||||
|
.decls = &.{},
|
||||||
|
.fields = &fields,
|
||||||
|
.is_tuple = true,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tupleFieldName(comptime i: usize) [:0]const u8 {
|
||||||
|
return switch (i) {
|
||||||
|
0 => "0",
|
||||||
|
1 => "1",
|
||||||
|
2 => "2",
|
||||||
|
3 => "3",
|
||||||
|
4 => "4",
|
||||||
|
5 => "5",
|
||||||
|
6 => "6",
|
||||||
|
7 => "7",
|
||||||
|
8 => "8",
|
||||||
|
9 => "9",
|
||||||
|
else => std.fmt.comptimePrint("{d}", .{i}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isPage(comptime T: type) bool {
|
||||||
|
return T == *Page or T == *const Page;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These wrap the raw v8 C API to provide a cleaner interface.
|
||||||
|
pub const FunctionCallbackInfo = struct {
|
||||||
|
handle: *const v8.FunctionCallbackInfo,
|
||||||
|
|
||||||
|
pub fn length(self: FunctionCallbackInfo) u32 {
|
||||||
|
return @intCast(v8.v8__FunctionCallbackInfo__Length(self.handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getArg(self: FunctionCallbackInfo, index: u32, local: *const js.Local) js.Value {
|
||||||
|
return .{ .local = local, .handle = v8.v8__FunctionCallbackInfo__INDEX(self.handle, @intCast(index)).? };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getThis(self: FunctionCallbackInfo) *const v8.Object {
|
||||||
|
return v8.v8__FunctionCallbackInfo__This(self.handle).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getReturnValue(self: FunctionCallbackInfo) ReturnValue {
|
||||||
|
var rv: v8.ReturnValue = undefined;
|
||||||
|
v8.v8__FunctionCallbackInfo__GetReturnValue(self.handle, &rv);
|
||||||
|
return .{ .handle = rv };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isConstructCall(self: FunctionCallbackInfo) bool {
|
||||||
|
return v8.v8__FunctionCallbackInfo__IsConstructCall(self.handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const PropertyCallbackInfo = struct {
|
||||||
|
handle: *const v8.PropertyCallbackInfo,
|
||||||
|
|
||||||
|
pub fn getThis(self: PropertyCallbackInfo) *const v8.Object {
|
||||||
|
return v8.v8__PropertyCallbackInfo__This(self.handle).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getReturnValue(self: PropertyCallbackInfo) ReturnValue {
|
||||||
|
var rv: v8.ReturnValue = undefined;
|
||||||
|
v8.v8__PropertyCallbackInfo__GetReturnValue(self.handle, &rv);
|
||||||
|
return .{ .handle = rv };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReturnValue = struct {
|
||||||
|
handle: v8.ReturnValue,
|
||||||
|
|
||||||
|
pub fn set(self: ReturnValue, value: anytype) void {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
if (T == *const v8.Object) {
|
||||||
|
self.setValueHandle(@ptrCast(value));
|
||||||
|
} else if (T == *const v8.Value) {
|
||||||
|
self.setValueHandle(value);
|
||||||
|
} else if (T == js.Value) {
|
||||||
|
self.setValueHandle(value.handle);
|
||||||
|
} else {
|
||||||
|
@compileError("Unsupported type for ReturnValue.set: " ++ @typeName(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setValueHandle(self: ReturnValue, handle: *const v8.Value) void {
|
||||||
|
v8.v8__ReturnValue__Set(self.handle, handle);
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -189,19 +189,27 @@ pub fn dumpMemoryStats(self: *Env) void {
|
|||||||
|
|
||||||
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
|
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
|
||||||
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
|
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
|
||||||
const isolate_handle = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
|
const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
|
||||||
const js_isolate = js.Isolate{ .handle = isolate_handle };
|
const js_isolate = js.Isolate{ .handle = v8_isolate };
|
||||||
const context = Context.fromIsolate(js_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 =
|
const value =
|
||||||
if (v8.v8__PromiseRejectMessage__GetValue(&message_handle)) |v8_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
|
else
|
||||||
"no value";
|
"no value";
|
||||||
|
|
||||||
log.debug(.js, "unhandled rejection", .{
|
log.debug(.js, "unhandled rejection", .{
|
||||||
.value = value,
|
.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",
|
.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
|
// does all the work, but having all page-specific data structures
|
||||||
// grouped together helps keep things clean.
|
// grouped together helps keep things clean.
|
||||||
context: ?Context = null,
|
context: ?Context = null,
|
||||||
persisted_context: ?js.Global(Context) = null,
|
|
||||||
|
|
||||||
// no init, must be initialized via env.newExecutionWorld()
|
// no init, must be initialized via env.newExecutionWorld()
|
||||||
|
|
||||||
@@ -77,43 +76,41 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
|||||||
const isolate = env.isolate;
|
const isolate = env.isolate;
|
||||||
const arena = self.context_arena.allocator();
|
const arena = self.context_arena.allocator();
|
||||||
|
|
||||||
const persisted_context: js.Global(Context) = blk: {
|
var hs: js.HandleScope = undefined;
|
||||||
var temp_scope: js.HandleScope = undefined;
|
hs.init(isolate);
|
||||||
temp_scope.init(isolate);
|
defer hs.deinit();
|
||||||
defer temp_scope.deinit();
|
|
||||||
|
|
||||||
// Getting this into the snapshot is tricky (anything involving the
|
// Getting this into the snapshot is tricky (anything involving the
|
||||||
// global is tricky). Easier to do here
|
// global is tricky). Easier to do here
|
||||||
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate.handle, env.templates);
|
const global_template = @import("Snapshot.zig").createGlobalTemplate(isolate.handle, env.templates);
|
||||||
v8.v8__ObjectTemplate__SetNamedHandler(global_template, &.{
|
v8.v8__ObjectTemplate__SetNamedHandler(global_template, &.{
|
||||||
.getter = bridge.unknownPropertyCallback,
|
.getter = bridge.unknownPropertyCallback,
|
||||||
.setter = null,
|
.setter = null,
|
||||||
.query = null,
|
.query = null,
|
||||||
.deleter = null,
|
.deleter = null,
|
||||||
.enumerator = null,
|
.enumerator = null,
|
||||||
.definer = null,
|
.definer = null,
|
||||||
.descriptor = null,
|
.descriptor = null,
|
||||||
.data = null,
|
.data = null,
|
||||||
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
||||||
});
|
});
|
||||||
|
|
||||||
const context_handle = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
const v8_context = v8.v8__Context__New(isolate.handle, global_template, null).?;
|
||||||
break :blk js.Global(Context).init(isolate.handle, context_handle);
|
|
||||||
};
|
// Create the v8::Context and wrap it in a v8::Global
|
||||||
|
var context_global: v8.Global = undefined;
|
||||||
|
v8.v8__Global__New(isolate.handle, v8_context, &context_global);
|
||||||
|
|
||||||
|
// our window wrapped in a v8::Global
|
||||||
|
const global_obj = v8.v8__Context__Global(v8_context).?;
|
||||||
|
var global_global: v8.Global = undefined;
|
||||||
|
v8.v8__Global__New(isolate.handle, global_obj, &global_global);
|
||||||
|
|
||||||
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
|
|
||||||
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
|
|
||||||
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
|
|
||||||
const v8_context = persisted_context.local();
|
|
||||||
var handle_scope: ?js.HandleScope = null;
|
|
||||||
if (enter) {
|
if (enter) {
|
||||||
handle_scope = @as(js.HandleScope, undefined);
|
|
||||||
handle_scope.?.init(isolate);
|
|
||||||
v8.v8__Context__Enter(v8_context);
|
v8.v8__Context__Enter(v8_context);
|
||||||
}
|
}
|
||||||
errdefer if (enter) {
|
errdefer if (enter) {
|
||||||
v8.v8__Context__Exit(v8_context);
|
v8.v8__Context__Exit(v8_context);
|
||||||
handle_scope.?.deinit();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const context_id = env.context_id;
|
const context_id = env.context_id;
|
||||||
@@ -122,24 +119,24 @@ pub fn createContext(self: *ExecutionWorld, page: *Page, enter: bool) !*Context
|
|||||||
self.context = Context{
|
self.context = Context{
|
||||||
.page = page,
|
.page = page,
|
||||||
.id = context_id,
|
.id = context_id,
|
||||||
|
.entered = enter,
|
||||||
.isolate = isolate,
|
.isolate = isolate,
|
||||||
.handle = v8_context,
|
.handle = context_global,
|
||||||
.templates = env.templates,
|
.templates = env.templates,
|
||||||
.handle_scope = handle_scope,
|
|
||||||
.script_manager = &page._script_manager,
|
.script_manager = &page._script_manager,
|
||||||
.call_arena = page.call_arena,
|
.call_arena = page.call_arena,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
};
|
};
|
||||||
self.persisted_context = persisted_context;
|
|
||||||
|
|
||||||
var context = &self.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
|
// Store a pointer to our context inside the v8 context so that, given
|
||||||
// a v8 context, we can get our context out
|
// a v8 context, we can get our context out
|
||||||
const data = isolate.initBigInt(@intFromPtr(context));
|
const data = isolate.initBigInt(@intFromPtr(&self.context.?));
|
||||||
v8.v8__Context__SetEmbedderData(context.handle, 1, @ptrCast(data.handle));
|
v8.v8__Context__SetEmbedderData(v8_context, 1, @ptrCast(data.handle));
|
||||||
|
|
||||||
try context.setupGlobal();
|
return &self.context.?;
|
||||||
return context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeContext(self: *ExecutionWorld) void {
|
pub fn removeContext(self: *ExecutionWorld) void {
|
||||||
@@ -147,9 +144,6 @@ pub fn removeContext(self: *ExecutionWorld) void {
|
|||||||
context.deinit();
|
context.deinit();
|
||||||
self.context = null;
|
self.context = null;
|
||||||
|
|
||||||
self.persisted_context.?.deinit();
|
|
||||||
self.persisted_context = null;
|
|
||||||
|
|
||||||
self.env.isolate.notifyContextDisposed();
|
self.env.isolate.notifyContextDisposed();
|
||||||
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
|
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const Function = @This();
|
const Function = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
this: ?*const v8.Object = null,
|
this: ?*const v8.Object = null,
|
||||||
handle: *const v8.Function,
|
handle: *const v8.Function,
|
||||||
|
|
||||||
@@ -34,34 +34,35 @@ pub const Result = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn withThis(self: *const Function, value: anytype) !Function {
|
pub fn withThis(self: *const Function, value: anytype) !Function {
|
||||||
|
const local = self.local;
|
||||||
const this_obj = if (@TypeOf(value) == js.Object)
|
const this_obj = if (@TypeOf(value) == js.Object)
|
||||||
value.handle
|
value.handle
|
||||||
else
|
else
|
||||||
(try self.ctx.zigValueToJs(value, .{})).handle;
|
(try local.zigValueToJs(value, .{})).handle;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = local,
|
||||||
.this = this_obj,
|
.this = this_obj,
|
||||||
.handle = self.handle,
|
.handle = self.handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newInstance(self: *const Function, caught: *js.TryCatch.Caught) !js.Object {
|
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;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(ctx);
|
try_catch.init(local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
// This creates a new instance using this Function as a constructor.
|
// This creates a new instance using this Function as a constructor.
|
||||||
// const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{}));
|
// const c_args = @as(?[*]const ?*c.Value, @ptrCast(&.{}));
|
||||||
const handle = v8.v8__Function__NewInstance(self.handle, ctx.handle, 0, null) orelse {
|
const handle = v8.v8__Function__NewInstance(self.handle, local.handle, 0, null) orelse {
|
||||||
caught.* = try_catch.caughtOrError(ctx.call_arena, error.Unknown);
|
caught.* = try_catch.caughtOrError(local.call_arena, error.Unknown);
|
||||||
return error.JsConstructorFailed;
|
return error.JsConstructorFailed;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = handle,
|
.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 {
|
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, caught: *js.TryCatch.Caught) !T {
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
|
|
||||||
try_catch.init(self.ctx);
|
try_catch.init(self.local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
return self.callWithThis(T, this, args) catch |err| {
|
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;
|
return err;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
|
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
|
// When we're calling a function from within JavaScript itself, this isn't
|
||||||
// necessary. We're within a Caller instantiation, which will already have
|
// 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
|
// 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
|
// 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.
|
// will be reset after each statement of the function which executes Zig code.
|
||||||
|
const ctx = local.ctx;
|
||||||
const call_depth = ctx.call_depth;
|
const call_depth = ctx.call_depth;
|
||||||
ctx.call_depth = call_depth + 1;
|
ctx.call_depth = call_depth + 1;
|
||||||
defer ctx.call_depth = call_depth;
|
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) {
|
if (@TypeOf(this) == js.Object) {
|
||||||
break :blk this;
|
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;
|
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;
|
const fields = s.fields;
|
||||||
var js_args: [fields.len]*const v8.Value = undefined;
|
var js_args: [fields.len]*const v8.Value = undefined;
|
||||||
inline for (fields, 0..) |f, i| {
|
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;
|
const cargs: [fields.len]*const v8.Value = js_args;
|
||||||
break :blk &cargs;
|
break :blk &cargs;
|
||||||
},
|
},
|
||||||
.pointer => blk: {
|
.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| {
|
for (args, 0..) |a, i| {
|
||||||
values[i] = (try ctx.zigValueToJs(a, .{})).handle;
|
values[i] = (try local.zigValueToJs(a, .{})).handle;
|
||||||
}
|
}
|
||||||
break :blk values;
|
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 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 "???"});
|
// std.debug.print("CB ERR: {s}\n", .{self.src() catch "???"});
|
||||||
return error.JSExecCallback;
|
return error.JSExecCallback;
|
||||||
};
|
};
|
||||||
@@ -140,13 +142,13 @@ pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args
|
|||||||
if (@typeInfo(T) == .void) {
|
if (@typeInfo(T) == .void) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return ctx.jsValueToZig(T, .{ .ctx = ctx, .handle = handle });
|
return local.jsValueToZig(T, .{ .local = local, .handle = handle });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getThis(self: *const Function) js.Object {
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.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 {
|
pub fn getPropertyValue(self: *const Function, name: []const u8) !?js.Value {
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
const key = ctx.isolate.initStringHandle(name);
|
const key = local.isolate.initStringHandle(name);
|
||||||
const handle = v8.v8__Object__Get(self.handle, ctx.handle, key) orelse {
|
const handle = v8.v8__Object__Get(self.handle, self.local.handle, key) orelse {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: *const Function) !Global {
|
pub fn persist(self: *const Function) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
|
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
|
|
||||||
try ctx.global_functions.append(ctx.arena, global);
|
try ctx.global_functions.append(ctx.arena, global);
|
||||||
|
return .{ .handle = global };
|
||||||
return .{
|
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persistWithThis(self: *const Function, value: anytype) !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 {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.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 js = @import("js.zig");
|
||||||
const v8 = js.v8;
|
const v8 = js.v8;
|
||||||
|
|
||||||
const Context = @import("Context.zig");
|
const TaggedOpaque = @import("TaggedOpaque.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const RndGen = std.Random.DefaultPrng;
|
const RndGen = std.Random.DefaultPrng;
|
||||||
@@ -36,7 +36,7 @@ client: Client,
|
|||||||
channel: Channel,
|
channel: Channel,
|
||||||
session: Session,
|
session: Session,
|
||||||
rnd: RndGen = RndGen.init(0),
|
rnd: RndGen = RndGen.init(0),
|
||||||
default_context: ?*const v8.Context = null,
|
default_context: ?v8.Global,
|
||||||
|
|
||||||
// We expect allocator to be an arena
|
// We expect allocator to be an arena
|
||||||
// Note: This initializes the pre-allocated inspector in-place
|
// 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 {
|
pub fn deinit(self: *const Inspector) void {
|
||||||
var temp_scope: v8.HandleScope = undefined;
|
var hs: v8.HandleScope = undefined;
|
||||||
v8.v8__HandleScope__CONSTRUCT(&temp_scope, self.isolate);
|
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate);
|
||||||
defer v8.v8__HandleScope__DESTRUCT(&temp_scope);
|
defer v8.v8__HandleScope__DESTRUCT(&hs);
|
||||||
|
|
||||||
self.session.deinit();
|
self.session.deinit();
|
||||||
self.client.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
|
// - is_default_context: Whether the execution context is default, should match the auxData
|
||||||
pub fn contextCreated(
|
pub fn contextCreated(
|
||||||
self: *Inspector,
|
self: *Inspector,
|
||||||
context: *const Context,
|
local: *const js.Local,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
origin: []const u8,
|
origin: []const u8,
|
||||||
aux_data: []const u8,
|
aux_data: []const u8,
|
||||||
@@ -143,11 +143,11 @@ pub fn contextCreated(
|
|||||||
aux_data.ptr,
|
aux_data.ptr,
|
||||||
aux_data.len,
|
aux_data.len,
|
||||||
CONTEXT_GROUP_ID,
|
CONTEXT_GROUP_ID,
|
||||||
context.handle,
|
local.handle,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (is_default_context) {
|
if (is_default_context) {
|
||||||
self.default_context = context.handle;
|
self.default_context = local.ctx.handle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,41 +158,42 @@ pub fn contextCreated(
|
|||||||
// we'll create it and track it for cleanup when the context ends.
|
// we'll create it and track it for cleanup when the context ends.
|
||||||
pub fn getRemoteObject(
|
pub fn getRemoteObject(
|
||||||
self: *const Inspector,
|
self: *const Inspector,
|
||||||
context: *Context,
|
local: *const js.Local,
|
||||||
group: []const u8,
|
group: []const u8,
|
||||||
value: anytype,
|
value: anytype,
|
||||||
) !RemoteObject {
|
) !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
|
// We do not want to expose this as a parameter for now
|
||||||
const generate_preview = false;
|
const generate_preview = false;
|
||||||
return self.session.wrapObject(
|
return self.session.wrapObject(
|
||||||
context.isolate.handle,
|
local.isolate.handle,
|
||||||
context.handle,
|
local.handle,
|
||||||
js_value.handle,
|
js_val.handle,
|
||||||
group,
|
group,
|
||||||
generate_preview,
|
generate_preview,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets a value by object ID regardless of which context it is in.
|
// Gets a value by object ID regardless of which context it is in.
|
||||||
// Our TaggedAnyOpaque stores the "resolved" ptr value (the most specific _type,
|
// Our TaggedOpaque stores the "resolved" ptr value (the most specific _type,
|
||||||
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
|
// e.g. we store the ptr to the Div not the EventTarget). But, this is asking for
|
||||||
// the pointer to the Node, so we need to use the same resolution mechanism which
|
// the pointer to the Node, so we need to use the same resolution mechanism which
|
||||||
// is used when we're calling a function to turn the Div into a Node, which is
|
// is used when we're calling a function to turn the Div into a Node, which is
|
||||||
// what Context.typeTaggedAnyOpaque does.
|
// what TaggedOpaque.fromJS does.
|
||||||
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8) !*anyopaque {
|
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8, local: *js.Local) !*anyopaque {
|
||||||
|
// just to indicate that the caller is responsible for ensure there's a local environment
|
||||||
|
_ = local;
|
||||||
const unwrapped = try self.session.unwrapObject(allocator, object_id);
|
const unwrapped = try self.session.unwrapObject(allocator, object_id);
|
||||||
// The values context and groupId are not used here
|
// The values context and groupId are not used here
|
||||||
const js_val = unwrapped.value;
|
const js_val = unwrapped.value;
|
||||||
if (!v8.v8__Value__IsObject(js_val)) {
|
if (!v8.v8__Value__IsObject(js_val)) {
|
||||||
return error.ObjectIdIsNotANode;
|
return error.ObjectIdIsNotANode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Node = @import("../webapi/Node.zig");
|
const Node = @import("../webapi/Node.zig");
|
||||||
// Cast to *const v8.Object for typeTaggedAnyOpaque
|
// Cast to *const v8.Object for typeTaggedAnyOpaque
|
||||||
return Context.typeTaggedAnyOpaque(*Node, @ptrCast(js_val)) catch {
|
return TaggedOpaque.fromJS(*Node, @ptrCast(js_val)) catch return error.ObjectIdIsNotANode;
|
||||||
return error.ObjectIdIsNotANode;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const RemoteObject = struct {
|
pub const RemoteObject = struct {
|
||||||
@@ -399,7 +400,7 @@ fn fromData(data: *anyopaque) *Inspector {
|
|||||||
return @ptrCast(@alignCast(data));
|
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)) {
|
if (!v8.v8__Value__IsObject(value)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -469,7 +470,8 @@ pub export fn v8_inspector__Client__IMPL__ensureDefaultContextInGroup(
|
|||||||
data: *anyopaque,
|
data: *anyopaque,
|
||||||
) callconv(.c) ?*const v8.Context {
|
) callconv(.c) ?*const v8.Context {
|
||||||
const inspector: *Inspector = @ptrCast(@alignCast(data));
|
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(
|
pub export fn v8_inspector__Channel__IMPL__sendResponse(
|
||||||
|
|||||||
1326
src/browser/js/Local.zig
Normal file
1326
src/browser/js/Local.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const Module = @This();
|
const Module = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Module,
|
handle: *const v8.Module,
|
||||||
|
|
||||||
pub const Status = enum(u32) {
|
pub const Status = enum(u32) {
|
||||||
@@ -39,21 +39,21 @@ pub fn getStatus(self: Module) Status {
|
|||||||
|
|
||||||
pub fn getException(self: Module) js.Value {
|
pub fn getException(self: Module) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = v8.v8__Module__GetException(self.handle).?,
|
.handle = v8.v8__Module__GetException(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getModuleRequests(self: Module) Requests {
|
pub fn getModuleRequests(self: Module) Requests {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx.handle,
|
.context_handle = self.local.handle,
|
||||||
.handle = v8.v8__Module__GetModuleRequests(self.handle).?,
|
.handle = v8.v8__Module__GetModuleRequests(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
||||||
var out: v8.MaybeBool = undefined;
|
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) {
|
if (out.has_value) {
|
||||||
return out.value;
|
return out.value;
|
||||||
}
|
}
|
||||||
@@ -61,15 +61,14 @@ pub fn instantiate(self: Module, cb: v8.ResolveModuleCallback) !bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(self: Module) !js.Value {
|
pub fn evaluate(self: Module) !js.Value {
|
||||||
const ctx = self.ctx;
|
const res = v8.v8__Module__Evaluate(self.handle, self.local.handle) orelse return error.JsException;
|
||||||
const res = v8.v8__Module__Evaluate(self.handle, ctx.handle) orelse return error.JsException;
|
|
||||||
|
|
||||||
if (self.getStatus() == .kErrored) {
|
if (self.getStatus() == .kErrored) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = self.local,
|
||||||
.handle = res,
|
.handle = res,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -80,7 +79,7 @@ pub fn getIdentityHash(self: Module) u32 {
|
|||||||
|
|
||||||
pub fn getModuleNamespace(self: Module) js.Value {
|
pub fn getModuleNamespace(self: Module) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = v8.v8__Module__GetModuleNamespace(self.handle).?,
|
.handle = v8.v8__Module__GetModuleNamespace(self.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -90,28 +89,24 @@ pub fn getScriptId(self: Module) u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: Module) !Global {
|
pub fn persist(self: Module) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
try ctx.global_modules.append(ctx.arena, global);
|
try ctx.global_modules.append(ctx.arena, global);
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,15 +116,15 @@ pub const Global = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Requests = struct {
|
const Requests = struct {
|
||||||
ctx: *const v8.Context,
|
|
||||||
handle: *const v8.FixedArray,
|
handle: *const v8.FixedArray,
|
||||||
|
context_handle: *const v8.Context,
|
||||||
|
|
||||||
pub fn len(self: Requests) usize {
|
pub fn len(self: Requests) usize {
|
||||||
return @intCast(v8.v8__FixedArray__Length(self.handle));
|
return @intCast(v8.v8__FixedArray__Length(self.handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: Requests, idx: usize) Request {
|
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();
|
const Object = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Object,
|
handle: *const v8.Object,
|
||||||
|
|
||||||
pub fn getId(self: Object) u32 {
|
pub fn getId(self: Object) u32 {
|
||||||
@@ -36,11 +36,11 @@ pub fn getId(self: Object) u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn has(self: Object, key: anytype) bool {
|
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);
|
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
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) {
|
if (out.has_value) {
|
||||||
return out.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 {
|
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 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 .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = self.local,
|
||||||
.handle = js_val_handle,
|
.handle = js_val_handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.bridge.Caller.CallOpts) !bool {
|
pub fn set(self: Object, key: anytype, value: anytype, comptime opts: js.Caller.CallOpts) !bool {
|
||||||
const ctx = self.ctx;
|
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);
|
const key_handle = if (@TypeOf(key) == *const v8.String) key else ctx.isolate.initStringHandle(key);
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
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;
|
return out.has_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn defineOwnProperty(self: Object, name: []const u8, value: js.Value, attr: v8.PropertyAttribute) ?bool {
|
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);
|
const name_handle = ctx.isolate.initStringHandle(name);
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
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) {
|
if (out.has_value) {
|
||||||
return out.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 {
|
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 {
|
pub fn toValue(self: Object) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: Object, writer: *std.Io.Writer) !void {
|
pub fn format(self: Object, writer: *std.Io.Writer) !void {
|
||||||
if (comptime IS_DEBUG) {
|
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;
|
const str = self.toString() catch return error.WriteFailed;
|
||||||
return writer.writeAll(str);
|
return writer.writeAll(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: Object) !Global {
|
pub fn persist(self: Object) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
|
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
|
|
||||||
try ctx.global_objects.append(ctx.arena, global);
|
try ctx.global_objects.append(ctx.arena, global);
|
||||||
|
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
|
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
|
||||||
if (self.isNullOrUndefined()) {
|
if (self.isNullOrUndefined()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
|
|
||||||
const js_name = ctx.isolate.initStringHandle(name);
|
const js_name = local.isolate.initStringHandle(name);
|
||||||
const js_val_handle = v8.v8__Object__Get(self.handle, ctx.handle, js_name) orelse return error.JsException;
|
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) {
|
if (v8.v8__Value__IsFunction(js_val_handle) == false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = @ptrCast(js_val_handle),
|
.handle = @ptrCast(js_val_handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -145,51 +142,48 @@ pub fn isNullOrUndefined(self: Object) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getOwnPropertyNames(self: Object) js.Array {
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPropertyNames(self: Object) js.Array {
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nameIterator(self: Object) NameIterator {
|
pub fn nameIterator(self: Object) NameIterator {
|
||||||
const ctx = self.ctx;
|
const handle = v8.v8__Object__GetPropertyNames(self.handle, self.local.handle).?;
|
||||||
|
|
||||||
const handle = v8.v8__Object__GetPropertyNames(self.handle, ctx.handle).?;
|
|
||||||
const count = v8.v8__Array__Length(handle);
|
const count = v8.v8__Array__Length(handle);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
.count = count,
|
.count = count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toZig(self: Object, comptime T: type) !T {
|
pub fn toZig(self: Object, comptime T: type) !T {
|
||||||
const js_value = js.Value{ .ctx = self.ctx, .handle = @ptrCast(self.handle) };
|
const js_value = js.Value{ .local = self.local, .handle = @ptrCast(self.handle) };
|
||||||
return self.ctx.jsValueToZig(T, js_value);
|
return self.local.jsValueToZig(T, js_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +195,7 @@ pub const Global = struct {
|
|||||||
pub const NameIterator = struct {
|
pub const NameIterator = struct {
|
||||||
count: u32,
|
count: u32,
|
||||||
idx: u32 = 0,
|
idx: u32 = 0,
|
||||||
ctx: *Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Array,
|
handle: *const v8.Array,
|
||||||
|
|
||||||
pub fn next(self: *NameIterator) !?[]const u8 {
|
pub fn next(self: *NameIterator) !?[]const u8 {
|
||||||
@@ -211,8 +205,8 @@ pub const NameIterator = struct {
|
|||||||
}
|
}
|
||||||
self.idx += 1;
|
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_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.local.handle, idx) orelse return error.JsException;
|
||||||
const js_val = js.Value{ .ctx = self.ctx, .handle = js_val_handle };
|
const js_val = js.Value{ .local = self.local, .handle = js_val_handle };
|
||||||
return try self.ctx.valueToString(js_val, .{});
|
return try self.local.valueToString(js_val, .{});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,63 +21,51 @@ const v8 = js.v8;
|
|||||||
|
|
||||||
const Promise = @This();
|
const Promise = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Promise,
|
handle: *const v8.Promise,
|
||||||
|
|
||||||
pub fn toObject(self: Promise) js.Object {
|
pub fn toObject(self: Promise) js.Object {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toValue(self: Promise) js.Value {
|
pub fn toValue(self: Promise) js.Value {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thenAndCatch(self: Promise, on_fulfilled: js.Function, on_rejected: js.Function) !Promise {
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = handle,
|
.handle = handle,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return error.PromiseChainFailed;
|
return error.PromiseChainFailed;
|
||||||
}
|
}
|
||||||
pub fn persist(self: Promise) !Global {
|
pub fn persist(self: Promise) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
try ctx.global_promises.append(ctx.arena, global);
|
try ctx.global_promises.append(ctx.arena, global);
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.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();
|
const PromiseResolver = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.PromiseResolver,
|
handle: *const v8.PromiseResolver,
|
||||||
|
|
||||||
pub fn init(ctx: *js.Context) PromiseResolver {
|
pub fn init(local: *const js.Local) PromiseResolver {
|
||||||
return .{
|
return .{
|
||||||
.ctx = ctx,
|
.local = local,
|
||||||
.handle = v8.v8__Promise__Resolver__New(ctx.handle).?,
|
.handle = v8.v8__Promise__Resolver__New(local.handle).?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn promise(self: PromiseResolver) js.Promise {
|
pub fn promise(self: PromiseResolver) js.Promise {
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = v8.v8__Promise__Resolver__GetPromise(self.handle).?,
|
.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 {
|
fn _resolve(self: PromiseResolver, value: anytype) !void {
|
||||||
const ctx: *js.Context = @constCast(self.ctx);
|
const local = self.local;
|
||||||
const js_value = try ctx.zigValueToJs(value, .{});
|
const js_val = try local.zigValueToJs(value, .{});
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
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) {
|
if (!out.has_value or !out.value) {
|
||||||
return error.FailedToResolvePromise;
|
return error.FailedToResolvePromise;
|
||||||
}
|
}
|
||||||
ctx.runMicrotasks();
|
local.ctx.runMicrotasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype) void {
|
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 {
|
fn _reject(self: PromiseResolver, value: anytype) !void {
|
||||||
const ctx = self.ctx;
|
const local = self.local;
|
||||||
const js_value = try ctx.zigValueToJs(value, .{});
|
const js_val = try local.zigValueToJs(value, .{});
|
||||||
|
|
||||||
var out: v8.MaybeBool = undefined;
|
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) {
|
if (!out.has_value or !out.value) {
|
||||||
return error.FailedToRejectPromise;
|
return error.FailedToRejectPromise;
|
||||||
}
|
}
|
||||||
ctx.runMicrotasks();
|
local.ctx.runMicrotasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(self: PromiseResolver) !Global {
|
pub fn persist(self: PromiseResolver) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
try ctx.global_promise_resolvers.append(ctx.arena, global);
|
try ctx.global_promise_resolvers.append(ctx.arena, global);
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.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();
|
const String = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.String,
|
handle: *const v8.String,
|
||||||
|
|
||||||
pub const ToZigOpts = struct {
|
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) {
|
fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) {
|
||||||
const isolate = self.ctx.isolate.handle;
|
const isolate = self.local.isolate.handle;
|
||||||
const allocator = opts.allocator orelse self.ctx.call_arena;
|
const allocator = opts.allocator orelse self.local.ctx.call_arena;
|
||||||
const len: u32 = @intCast(v8.v8__String__Utf8Length(self.handle, isolate));
|
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);
|
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();
|
const TryCatch = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
|
||||||
handle: v8.TryCatch,
|
handle: v8.TryCatch,
|
||||||
|
local: *const js.Local,
|
||||||
|
|
||||||
pub fn init(self: *TryCatch, ctx: *js.Context) void {
|
pub fn init(self: *TryCatch, l: *const js.Local) void {
|
||||||
self.ctx = ctx;
|
self.local = l;
|
||||||
v8.v8__TryCatch__CONSTRUCT(&self.handle, ctx.isolate.handle);
|
v8.v8__TryCatch__CONSTRUCT(&self.handle, l.isolate.handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasCaught(self: TryCatch) bool {
|
pub fn hasCaught(self: TryCatch) bool {
|
||||||
@@ -41,26 +41,21 @@ pub fn caught(self: TryCatch, allocator: Allocator) ?Caught {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = self.ctx;
|
const l = self.local;
|
||||||
|
|
||||||
var hs: js.HandleScope = undefined;
|
|
||||||
hs.init(ctx.isolate);
|
|
||||||
defer hs.deinit();
|
|
||||||
|
|
||||||
const line: ?u32 = blk: {
|
const line: ?u32 = blk: {
|
||||||
const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null;
|
const handle = v8.v8__TryCatch__Message(&self.handle) orelse return null;
|
||||||
const l = v8.v8__Message__GetLineNumber(handle, ctx.handle);
|
const line = v8.v8__Message__GetLineNumber(handle, l.handle);
|
||||||
break :blk if (l < 0) null else @intCast(l);
|
break :blk if (line < 0) null else @intCast(line);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exception: ?[]const u8 = blk: {
|
const exception: ?[]const u8 = blk: {
|
||||||
const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null;
|
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 stack: ?[]const u8 = blk: {
|
||||||
const handle = v8.v8__TryCatch__StackTrace(&self.handle, ctx.handle) orelse break :blk null;
|
const handle = v8.v8__TryCatch__StackTrace(&self.handle, l.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);
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const Value = @This();
|
const Value = @This();
|
||||||
|
|
||||||
ctx: *js.Context,
|
local: *const js.Local,
|
||||||
handle: *const v8.Value,
|
handle: *const v8.Value,
|
||||||
|
|
||||||
pub fn isObject(self: Value) bool {
|
pub fn isObject(self: Value) bool {
|
||||||
@@ -155,12 +155,12 @@ pub fn isPromise(self: Value) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn toBool(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 {
|
pub fn typeOf(self: Value) js.String {
|
||||||
const str_handle = v8.v8__Value__TypeOf(self.handle, self.ctx.isolate.handle).?;
|
const str_handle = v8.v8__Value__TypeOf(self.handle, self.local.isolate.handle).?;
|
||||||
return js.String{ .ctx = self.ctx, .handle = str_handle };
|
return js.String{ .local = self.local, .handle = str_handle };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toF32(self: Value) !f32 {
|
pub fn toF32(self: Value) !f32 {
|
||||||
@@ -169,7 +169,7 @@ pub fn toF32(self: Value) !f32 {
|
|||||||
|
|
||||||
pub fn toF64(self: Value) !f64 {
|
pub fn toF64(self: Value) !f64 {
|
||||||
var maybe: v8.MaybeF64 = undefined;
|
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) {
|
if (!maybe.has_value) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ pub fn toF64(self: Value) !f64 {
|
|||||||
|
|
||||||
pub fn toI32(self: Value) !i32 {
|
pub fn toI32(self: Value) !i32 {
|
||||||
var maybe: v8.MaybeI32 = undefined;
|
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) {
|
if (!maybe.has_value) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ pub fn toI32(self: Value) !i32 {
|
|||||||
|
|
||||||
pub fn toU32(self: Value) !u32 {
|
pub fn toU32(self: Value) !u32 {
|
||||||
var maybe: v8.MaybeU32 = undefined;
|
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) {
|
if (!maybe.has_value) {
|
||||||
return error.JsException;
|
return error.JsException;
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ pub fn toPromise(self: Value) js.Promise {
|
|||||||
std.debug.assert(self.isPromise());
|
std.debug.assert(self.isPromise());
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.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 {
|
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;
|
const json_str_handle = v8.v8__JSON__Stringify(self.local.handle, self.handle, null) orelse return error.JsException;
|
||||||
return self.ctx.jsStringToZig(json_str_handle, .{ .allocator = allocator });
|
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) {
|
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()) {
|
if (self.isSymbol()) {
|
||||||
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), ctx.isolate.handle).?;
|
const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), l.isolate.handle).?;
|
||||||
return _toString(.{ .handle = @ptrCast(sym_handle), .ctx = ctx }, null_terminate, opts);
|
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;
|
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) {
|
if (comptime null_terminate) {
|
||||||
return js.String.toZigZ(str, opts);
|
return js.String.toZigZ(str, opts);
|
||||||
}
|
}
|
||||||
return js.String.toZig(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 {
|
pub fn persist(self: Value) !Global {
|
||||||
var ctx = self.ctx;
|
var ctx = self.local.ctx;
|
||||||
|
|
||||||
var global: v8.Global = undefined;
|
var global: v8.Global = undefined;
|
||||||
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
v8.v8__Global__New(ctx.isolate.handle, self.handle, &global);
|
||||||
|
|
||||||
try ctx.global_values.append(ctx.arena, global);
|
try ctx.global_values.append(ctx.arena, global);
|
||||||
|
|
||||||
return .{
|
return .{ .handle = global };
|
||||||
.handle = global,
|
|
||||||
.ctx = ctx,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toZig(self: Value, comptime T: type) !T {
|
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 {
|
pub fn toObject(self: Value) js.Object {
|
||||||
@@ -267,7 +256,7 @@ pub fn toObject(self: Value) js.Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.handle = @ptrCast(self.handle),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -278,7 +267,7 @@ pub fn toArray(self: Value) js.Array {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = self.local,
|
||||||
.handle = @ptrCast(self.handle),
|
.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 {
|
pub fn format(self: Value, writer: *std.Io.Writer) !void {
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
return self.ctx.debugValue(self, writer);
|
return self.local.debugValue(self, writer);
|
||||||
}
|
}
|
||||||
const str = self.toString(.{}) catch return error.WriteFailed;
|
const str = self.toString(.{}) catch return error.WriteFailed;
|
||||||
return writer.writeAll(str);
|
return writer.writeAll(str);
|
||||||
@@ -303,16 +292,15 @@ pub fn format(self: Value, writer: *std.Io.Writer) !void {
|
|||||||
|
|
||||||
pub const Global = struct {
|
pub const Global = struct {
|
||||||
handle: v8.Global,
|
handle: v8.Global,
|
||||||
ctx: *js.Context,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Global) void {
|
pub fn deinit(self: *Global) void {
|
||||||
v8.v8__Global__Reset(&self.handle);
|
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 .{
|
return .{
|
||||||
.ctx = self.ctx,
|
.local = l,
|
||||||
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, self.ctx.isolate.handle)),
|
.handle = @ptrCast(v8.v8__Global__Get(&self.handle, l.isolate.handle)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,555 +20,15 @@ const std = @import("std");
|
|||||||
const js = @import("js.zig");
|
const js = @import("js.zig");
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const Page = @import("../Page.zig");
|
||||||
|
|
||||||
const v8 = js.v8;
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Caller = @import("Caller.zig");
|
||||||
const Context = @import("Context.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;
|
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 {
|
pub fn Builder(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub const @"type" = T;
|
pub const @"type" = T;
|
||||||
@@ -610,8 +70,9 @@ pub fn Builder(comptime T: type) type {
|
|||||||
@compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet");
|
@compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prototypeChain() [prototypeChainLength(T)]js.PrototypeChainEntry {
|
const PrototypeChainEntry = @import("TaggedOpaque.zig").PrototypeChainEntry;
|
||||||
var entries: [prototypeChainLength(T)]js.PrototypeChainEntry = undefined;
|
pub fn prototypeChain() [prototypeChainLength(T)]PrototypeChainEntry {
|
||||||
|
var entries: [prototypeChainLength(T)]PrototypeChainEntry = undefined;
|
||||||
|
|
||||||
entries[0] = .{ .offset = 0, .index = JsApiLookup.getId(T.JsApi) };
|
entries[0] = .{ .offset = 0, .index = JsApiLookup.getId(T.JsApi) };
|
||||||
|
|
||||||
@@ -644,11 +105,11 @@ pub const Constructor = struct {
|
|||||||
return .{ .func = struct {
|
return .{ .func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
caller.constructor(T, func, handle.?, .{
|
||||||
caller.constructor(T, func, info, .{
|
|
||||||
.dom_exception = opts.dom_exception,
|
.dom_exception = opts.dom_exception,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -673,18 +134,18 @@ pub const Function = struct {
|
|||||||
.func = struct {
|
.func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
|
||||||
if (comptime opts.static) {
|
if (comptime opts.static) {
|
||||||
caller.function(T, func, info, .{
|
caller.function(T, func, handle.?, .{
|
||||||
.dom_exception = opts.dom_exception,
|
.dom_exception = opts.dom_exception,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
caller.method(T, func, info, .{
|
caller.method(T, func, handle.?, .{
|
||||||
.dom_exception = opts.dom_exception,
|
.dom_exception = opts.dom_exception,
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
@@ -717,17 +178,17 @@ pub const Accessor = struct {
|
|||||||
accessor.getter = struct {
|
accessor.getter = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
|
||||||
if (comptime opts.static) {
|
if (comptime opts.static) {
|
||||||
caller.function(T, getter, info, .{
|
caller.function(T, getter, handle.?, .{
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
caller.method(T, getter, info, .{
|
caller.method(T, getter, handle.?, .{
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -740,15 +201,11 @@ pub const Accessor = struct {
|
|||||||
accessor.setter = struct {
|
accessor.setter = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
caller.method(T, setter, handle.?, .{
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
lp.assert(info.length() == 1, "bridge.setter", .{ .len = info.length() });
|
|
||||||
}
|
|
||||||
|
|
||||||
caller.method(T, setter, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -772,11 +229,11 @@ pub const Indexed = struct {
|
|||||||
return .{ .getter = struct {
|
return .{ .getter = struct {
|
||||||
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
fn wrap(idx: u32, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.getIndex(T, getter, idx, handle.?, .{
|
||||||
return caller.getIndex(T, getter, idx, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -799,11 +256,11 @@ pub const NamedIndexed = struct {
|
|||||||
const getter_fn = struct {
|
const getter_fn = struct {
|
||||||
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.getNamedIndex(T, getter, c_name.?, handle.?, .{
|
||||||
return caller.getNamedIndex(T, getter, .{ .handle = c_name.? }, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -813,11 +270,11 @@ pub const NamedIndexed = struct {
|
|||||||
const setter_fn = if (@typeInfo(@TypeOf(setter)) == .null) null else struct {
|
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 {
|
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).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.setNamedIndex(T, setter, c_name.?, c_value.?, handle.?, .{
|
||||||
return caller.setNamedIndex(T, setter, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -827,11 +284,11 @@ pub const NamedIndexed = struct {
|
|||||||
const deleter_fn = if (@typeInfo(@TypeOf(deleter)) == .null) null else struct {
|
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 {
|
fn wrap(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = PropertyCallbackInfo{ .handle = handle.? };
|
return caller.deleteNamedIndex(T, deleter, c_name.?, handle.?, .{
|
||||||
return caller.deleteNamedIndex(T, deleter, .{ .handle = c_name.? }, info, .{
|
|
||||||
.as_typed_array = opts.as_typed_array,
|
.as_typed_array = opts.as_typed_array,
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
@@ -861,7 +318,7 @@ pub const Iterator = struct {
|
|||||||
.async = opts.async,
|
.async = opts.async,
|
||||||
.func = struct {
|
.func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
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());
|
info.getReturnValue().set(info.getThis());
|
||||||
}
|
}
|
||||||
}.wrap,
|
}.wrap,
|
||||||
@@ -873,11 +330,10 @@ pub const Iterator = struct {
|
|||||||
.func = struct {
|
.func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
caller.method(T, struct_or_func, handle.?, .{});
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
|
||||||
caller.method(T, struct_or_func, info, .{});
|
|
||||||
}
|
}
|
||||||
}.wrap,
|
}.wrap,
|
||||||
};
|
};
|
||||||
@@ -895,11 +351,11 @@ pub const Callable = struct {
|
|||||||
return .{ .func = struct {
|
return .{ .func = struct {
|
||||||
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
fn wrap(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
||||||
const v8_isolate = v8.v8__FunctionCallbackInfo__GetIsolate(handle).?;
|
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();
|
defer caller.deinit();
|
||||||
|
|
||||||
const info = FunctionCallbackInfo{ .handle = handle.? };
|
caller.method(T, func, handle.?, .{
|
||||||
caller.method(T, func, info, .{
|
|
||||||
.null_as_undefined = opts.null_as_undefined,
|
.null_as_undefined = opts.null_as_undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -912,52 +368,57 @@ pub const Property = union(enum) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn unknownPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
pub fn unknownPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
const isolate_handle = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
|
||||||
const context = Context.fromIsolate(.{ .handle = isolate_handle });
|
var caller: Caller = undefined;
|
||||||
|
caller.init(v8_isolate);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
const property: []const u8 = context.valueToString(.{ .ctx = context, .handle = c_name.? }, .{}) catch {
|
const local = &caller.local;
|
||||||
|
|
||||||
|
var hs: js.HandleScope = undefined;
|
||||||
|
hs.init(local.isolate);
|
||||||
|
defer hs.deinit();
|
||||||
|
|
||||||
|
const property: []const u8 = local.valueHandleToString(@ptrCast(c_name.?), .{}) catch {
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ignored = std.StaticStringMap(void).initComptime(.{
|
const page = local.ctx.page;
|
||||||
.{ "process", {} },
|
const document = page.document;
|
||||||
.{ "ShadyDOM", {} },
|
|
||||||
.{ "ShadyCSS", {} },
|
|
||||||
|
|
||||||
.{ "litNonce", {} },
|
if (document.getElementById(property, page)) |el| {
|
||||||
.{ "litHtmlVersions", {} },
|
const js_val = local.zigValueToJs(el, .{}) catch return 0;
|
||||||
.{ "litElementVersions", {} },
|
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
|
||||||
.{ "litHtmlPolyfillSupport", {} },
|
pc.getReturnValue().set(js_val);
|
||||||
.{ "litElementHydrateSupport", {} },
|
return 1;
|
||||||
.{ "litElementPolyfillSupport", {} },
|
}
|
||||||
.{ "reactiveElementVersions", {} },
|
|
||||||
|
|
||||||
.{ "recaptcha", {} },
|
if (comptime IS_DEBUG) {
|
||||||
.{ "grecaptcha", {} },
|
const ignored = std.StaticStringMap(void).initComptime(.{
|
||||||
.{ "___grecaptcha_cfg", {} },
|
.{ "process", {} },
|
||||||
.{ "__recaptcha_api", {} },
|
.{ "ShadyDOM", {} },
|
||||||
.{ "__google_recaptcha_client", {} },
|
.{ "ShadyCSS", {} },
|
||||||
|
|
||||||
.{ "CLOSURE_FLAGS", {} },
|
.{ "litNonce", {} },
|
||||||
});
|
.{ "litHtmlVersions", {} },
|
||||||
|
.{ "litElementVersions", {} },
|
||||||
|
.{ "litHtmlPolyfillSupport", {} },
|
||||||
|
.{ "litElementHydrateSupport", {} },
|
||||||
|
.{ "litElementPolyfillSupport", {} },
|
||||||
|
.{ "reactiveElementVersions", {} },
|
||||||
|
|
||||||
if (!ignored.has(property)) {
|
.{ "recaptcha", {} },
|
||||||
const page = context.page;
|
.{ "grecaptcha", {} },
|
||||||
const document = page.document;
|
.{ "___grecaptcha_cfg", {} },
|
||||||
|
.{ "__recaptcha_api", {} },
|
||||||
|
.{ "__google_recaptcha_client", {} },
|
||||||
|
|
||||||
if (document.getElementById(property, page)) |el| {
|
.{ "CLOSURE_FLAGS", {} },
|
||||||
const js_value = context.zigValueToJs(el, .{}) catch {
|
});
|
||||||
return 0;
|
if (!ignored.has(property)) {
|
||||||
};
|
|
||||||
var pc = PropertyCallbackInfo{ .handle = handle.? };
|
|
||||||
pc.getReturnValue().set(js_value);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
log.debug(.unknown_prop, "unknown global property", .{
|
log.debug(.unknown_prop, "unknown global property", .{
|
||||||
.info = "but the property can exist in pure JS",
|
.info = "but the property can exist in pure JS",
|
||||||
.stack = context.stackTrace() catch "???",
|
.stack = local.stackTrace() catch "???",
|
||||||
.property = property,
|
.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 Env = @import("Env.zig");
|
||||||
pub const bridge = @import("bridge.zig");
|
pub const bridge = @import("bridge.zig");
|
||||||
pub const ExecutionWorld = @import("ExecutionWorld.zig");
|
pub const ExecutionWorld = @import("ExecutionWorld.zig");
|
||||||
|
pub const Caller = @import("Caller.zig");
|
||||||
pub const Context = @import("Context.zig");
|
pub const Context = @import("Context.zig");
|
||||||
|
pub const Local = @import("Local.zig");
|
||||||
pub const Inspector = @import("Inspector.zig");
|
pub const Inspector = @import("Inspector.zig");
|
||||||
pub const Snapshot = @import("Snapshot.zig");
|
pub const Snapshot = @import("Snapshot.zig");
|
||||||
pub const Platform = @import("Platform.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 BigInt = @import("BigInt.zig");
|
||||||
pub const Number = @import("Number.zig");
|
pub const Number = @import("Number.zig");
|
||||||
pub const Integer = @import("Integer.zig");
|
pub const Integer = @import("Integer.zig");
|
||||||
pub const Global = @import("global.zig").Global;
|
|
||||||
pub const PromiseResolver = @import("PromiseResolver.zig");
|
pub const PromiseResolver = @import("PromiseResolver.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
@@ -78,11 +79,11 @@ pub const ArrayBuffer = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Exception = struct {
|
pub const Exception = struct {
|
||||||
ctx: *const Context,
|
local: *const Local,
|
||||||
handle: *const v8.Value,
|
handle: *const v8.Value,
|
||||||
|
|
||||||
pub fn exception(self: Exception, allocator: Allocator) ![]const u8 {
|
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;
|
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
|
// These are here, and not in Inspector.zig, because Inspector.zig isn't always
|
||||||
// included (e.g. in the wpt build).
|
// included (e.g. in the wpt build).
|
||||||
@@ -281,7 +227,7 @@ pub export fn v8_inspector__Client__IMPL__valueSubtype(
|
|||||||
_: *v8.InspectorClientImpl,
|
_: *v8.InspectorClientImpl,
|
||||||
c_value: *const v8.Value,
|
c_value: *const v8.Value,
|
||||||
) callconv(.c) [*c]const u8 {
|
) 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;
|
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
|
// 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
|
// 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 "";
|
return if (external_entry.subtype == null) null else "";
|
||||||
}
|
}
|
||||||
|
|
||||||
test "TaggedAnyOpaque" {
|
test "TaggedAnyOpaque" {
|
||||||
// If we grow this, fine, but it should be a conscious decision
|
// 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.eventually(() => {
|
||||||
testing.expectEqual(true, popstateEventFired);
|
testing.expectEqual(true, popstateEventFired);
|
||||||
testing.expectEqual(state, popstateEventState);
|
testing.expectEqual({testInProgress: true }, popstateEventState);
|
||||||
})
|
})
|
||||||
|
|
||||||
history.back();
|
history.back();
|
||||||
|
|||||||
@@ -1,6 +1 @@
|
|||||||
<!DOCTYPE html>
|
<!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 {
|
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 {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
|||||||
return self._proto;
|
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) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,11 +77,10 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
|||||||
|
|
||||||
// Dispatch abort event
|
// Dispatch abort event
|
||||||
const event = try Event.initTrusted("abort", .{}, page);
|
const event = try Event.initTrusted("abort", .{}, page);
|
||||||
const func = if (self._on_abort) |*g| g.local() else null;
|
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchWithFunction(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
func,
|
local.toLocal(self._on_abort),
|
||||||
.{ .context = "abort signal" },
|
.{ .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
|
// Static method to create an already-aborted signal
|
||||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||||
const signal = try init(page);
|
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;
|
return signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +111,13 @@ const ThrowIfAborted = union(enum) {
|
|||||||
undefined: void,
|
undefined: void,
|
||||||
};
|
};
|
||||||
pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted {
|
pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted {
|
||||||
|
const local = page.js.local.?;
|
||||||
|
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
const exception = switch (self._reason) {
|
const exception = switch (self._reason) {
|
||||||
.string => |str| page.js.throw(str),
|
.string => |str| local.throw(str),
|
||||||
.js_val => |js_val| page.js.throw(try js_val.local().toString(.{ .allocator = page.call_arena })),
|
.js_val => |js_val| local.throw(try local.toLocal(js_val).toString(.{ .allocator = page.call_arena })),
|
||||||
.undefined => page.js.throw("AbortError"),
|
.undefined => local.throw("AbortError"),
|
||||||
};
|
};
|
||||||
return .{ .exception = exception };
|
return .{ .exception = exception };
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,11 @@ const TimeoutCallback = struct {
|
|||||||
|
|
||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
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 });
|
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||||
};
|
};
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ fn writeBlobParts(
|
|||||||
/// Returns a Promise that resolves with the contents of the blob
|
/// Returns a Promise that resolves with the contents of the blob
|
||||||
/// as binary data contained in an ArrayBuffer.
|
/// as binary data contained in an ArrayBuffer.
|
||||||
pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
|
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");
|
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
|
/// Returns a Promise that resolves with a string containing
|
||||||
/// the contents of the blob, interpreted as UTF-8.
|
/// the contents of the blob, interpreted as UTF-8.
|
||||||
pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
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.
|
/// 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
|
/// Returns a Promise that resolves with a Uint8Array containing
|
||||||
/// the contents of the blob as an array of bytes.
|
/// the contents of the blob as an array of bytes.
|
||||||
pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
|
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
|
/// 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 {
|
pub fn trace(_: *const Console, values: []js.Value, page: *Page) !void {
|
||||||
logger.debug(.js, "console.trace", .{
|
logger.debug(.js, "console.trace", .{
|
||||||
.stack = page.js.stackTrace() catch "???",
|
.stack = page.js.local.?.stackTrace() catch "???",
|
||||||
.args = ValueWriter{ .page = page, .values = values },
|
.args = ValueWriter{ .page = page, .values = values },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ const ValueWriter = struct {
|
|||||||
try writer.print("\n arg({d}): {f}", .{ i, value });
|
try writer.print("\n arg({d}): {f}", .{ i, value });
|
||||||
}
|
}
|
||||||
if (self.include_stack) {
|
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| {
|
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 {
|
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) !js.Promise {
|
||||||
|
const local = page.js.local.?;
|
||||||
if (self._definitions.get(name)) |definition| {
|
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);
|
const gop = try self._when_defined.getOrPut(page.arena, name);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
return gop.value_ptr.local().promise();
|
return local.toLocal(gop.value_ptr.*).promise();
|
||||||
}
|
}
|
||||||
errdefer _ = self._when_defined.remove(name);
|
errdefer _ = self._when_defined.remove(name);
|
||||||
const owned_name = try page.dupeString(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.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 {
|
fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void {
|
||||||
@@ -174,8 +175,12 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
|
|||||||
page._upgrading_element = node;
|
page._upgrading_element = node;
|
||||||
defer page._upgrading_element = prev_upgrading;
|
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;
|
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 });
|
log.warn(.js, "custom element upgrade", .{ .name = definition.name, .err = err, .caught = caught });
|
||||||
return error.CustomElementUpgradeFailed;
|
return error.CustomElementUpgradeFailed;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,14 +63,14 @@ pub fn getFilter(self: *const DOMNodeIterator) ?FilterOpts {
|
|||||||
return self._filter._original_filter;
|
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 node = self._reference_node;
|
||||||
var before_node = self._pointer_before_reference_node;
|
var before_node = self._pointer_before_reference_node;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (before_node) {
|
if (before_node) {
|
||||||
before_node = false;
|
before_node = false;
|
||||||
const result = try self.filterNode(node);
|
const result = try self.filterNode(node, page);
|
||||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._reference_node = node;
|
self._reference_node = node;
|
||||||
self._pointer_before_reference_node = false;
|
self._pointer_before_reference_node = false;
|
||||||
@@ -84,7 +84,7 @@ pub fn nextNode(self: *DOMNodeIterator) !?*Node {
|
|||||||
}
|
}
|
||||||
node = next.?;
|
node = next.?;
|
||||||
|
|
||||||
const result = try self.filterNode(node);
|
const result = try self.filterNode(node, page);
|
||||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._reference_node = node;
|
self._reference_node = node;
|
||||||
self._pointer_before_reference_node = false;
|
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 node = self._reference_node;
|
||||||
var before_node = self._pointer_before_reference_node;
|
var before_node = self._pointer_before_reference_node;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!before_node) {
|
if (!before_node) {
|
||||||
const result = try self.filterNode(node);
|
const result = try self.filterNode(node, page);
|
||||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._reference_node = node;
|
self._reference_node = node;
|
||||||
self._pointer_before_reference_node = true;
|
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
|
// First check whatToShow
|
||||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||||
return NodeFilter.FILTER_SKIP;
|
return NodeFilter.FILTER_SKIP;
|
||||||
@@ -128,7 +128,7 @@ fn filterNode(self: *const DOMNodeIterator, node: *Node) !i32 {
|
|||||||
// Then check the filter callback
|
// Then check the filter callback
|
||||||
// For NodeIterator, REJECT and SKIP are equivalent - both skip the node
|
// For NodeIterator, REJECT and SKIP are equivalent - both skip the node
|
||||||
// but continue with its descendants
|
// but continue with its descendants
|
||||||
const result = try self._filter.acceptNode(node);
|
const result = try self._filter.acceptNode(node, page.js.local.?);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ pub fn setCurrentNode(self: *DOMTreeWalker, node: *Node) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Navigation methods
|
// Navigation methods
|
||||||
pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
pub fn parentNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current._parent;
|
var node = self._current._parent;
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (n == self._root._parent) {
|
if (n == self._root._parent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -77,11 +77,11 @@ pub fn parentNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
pub fn firstChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current.firstChild();
|
var node = self._current.firstChild();
|
||||||
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
const filter_result = try self.acceptNode(n);
|
const filter_result = try self.acceptNode(n, page);
|
||||||
|
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
@@ -117,11 +117,11 @@ pub fn firstChild(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
pub fn lastChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current.lastChild();
|
var node = self._current.lastChild();
|
||||||
|
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
const filter_result = try self.acceptNode(n);
|
const filter_result = try self.acceptNode(n, page);
|
||||||
|
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
@@ -157,10 +157,10 @@ pub fn lastChild(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previousSibling(self: *DOMTreeWalker) !?*Node {
|
pub fn previousSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self.previousSiblingOrNull(self._current);
|
var node = self.previousSiblingOrNull(self._current);
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -169,10 +169,10 @@ pub fn previousSibling(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextSibling(self: *DOMTreeWalker) !?*Node {
|
pub fn nextSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self.nextSiblingOrNull(self._current);
|
var node = self.nextSiblingOrNull(self._current);
|
||||||
while (node) |n| {
|
while (node) |n| {
|
||||||
if (try self.acceptNode(n) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = n;
|
self._current = n;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,7 @@ pub fn nextSibling(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current;
|
var node = self._current;
|
||||||
while (node != self._root) {
|
while (node != self._root) {
|
||||||
var sibling = self.previousSiblingOrNull(node);
|
var sibling = self.previousSiblingOrNull(node);
|
||||||
@@ -189,7 +189,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
node = sib;
|
node = sib;
|
||||||
|
|
||||||
// Check if this sibling is rejected before descending into it
|
// 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) {
|
if (sib_result == NodeFilter.FILTER_REJECT) {
|
||||||
// Skip this sibling and its descendants entirely
|
// Skip this sibling and its descendants entirely
|
||||||
sibling = self.previousSiblingOrNull(node);
|
sibling = self.previousSiblingOrNull(node);
|
||||||
@@ -204,7 +204,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
while (child) |c| {
|
while (child) |c| {
|
||||||
if (!self.isInSubtree(c)) break;
|
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) {
|
if (filter_result == NodeFilter.FILTER_REJECT) {
|
||||||
// Skip this child and try its previous sibling
|
// Skip this child and try its previous sibling
|
||||||
child = self.previousSiblingOrNull(c);
|
child = self.previousSiblingOrNull(c);
|
||||||
@@ -220,7 +220,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
node = child.?;
|
node = child.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (try self.acceptNode(node) == NodeFilter.FILTER_ACCEPT) {
|
if (try self.acceptNode(node, page) == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
@@ -232,7 +232,7 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parent = node._parent orelse return null;
|
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;
|
self._current = parent;
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
@@ -241,14 +241,14 @@ pub fn previousNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
pub fn nextNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||||
var node = self._current;
|
var node = self._current;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// Try children first (depth-first)
|
// Try children first (depth-first)
|
||||||
if (node.firstChild()) |child| {
|
if (node.firstChild()) |child| {
|
||||||
node = child;
|
node = child;
|
||||||
const filter_result = try self.acceptNode(node);
|
const filter_result = try self.acceptNode(node, page);
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
@@ -271,7 +271,7 @@ pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
|
|
||||||
if (node.nextSibling()) |sibling| {
|
if (node.nextSibling()) |sibling| {
|
||||||
node = sibling;
|
node = sibling;
|
||||||
const filter_result = try self.acceptNode(node);
|
const filter_result = try self.acceptNode(node, page);
|
||||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||||
self._current = node;
|
self._current = node;
|
||||||
return node;
|
return node;
|
||||||
@@ -293,7 +293,7 @@ pub fn nextNode(self: *DOMTreeWalker) !?*Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
fn acceptNode(self: *const DOMTreeWalker, node: *Node) !i32 {
|
fn acceptNode(self: *const DOMTreeWalker, node: *Node, page: *Page) !i32 {
|
||||||
// First check whatToShow
|
// First check whatToShow
|
||||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||||
return NodeFilter.FILTER_SKIP;
|
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
|
// For TreeWalker, REJECT means reject node and its descendants
|
||||||
// SKIP means skip node but check its descendants
|
// SKIP means skip node but check its descendants
|
||||||
// ACCEPT means accept the node
|
// 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 {
|
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| {
|
if (self._adopted_style_sheets) |ass| {
|
||||||
return ass;
|
return ass;
|
||||||
}
|
}
|
||||||
const js_arr = page.js.newArray(0);
|
const js_arr = page.js.local.?.newArray(0);
|
||||||
const js_obj = js_arr.toObject();
|
const js_obj = js_arr.toObject();
|
||||||
self._adopted_style_sheets = try js_obj.persist();
|
self._adopted_style_sheets = try js_obj.persist();
|
||||||
return self._adopted_style_sheets.?;
|
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 {
|
pub fn getState(_: *const History, page: *Page) !?js.Value {
|
||||||
if (page._session.navigation.getCurrentEntry()._state.value) |state| {
|
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;
|
return value;
|
||||||
} else return null;
|
} else return null;
|
||||||
}
|
}
|
||||||
@@ -81,11 +81,10 @@ fn goInner(delta: i32, page: *Page) !void {
|
|||||||
if (try page.isSameOrigin(url)) {
|
if (try page.isSameOrigin(url)) {
|
||||||
const event = try PopStateEvent.initTrusted("popstate", .{ .state = entry._state.value }, page);
|
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(
|
try page._event_manager.dispatchWithFunction(
|
||||||
page.window.asEventTarget(),
|
page.window.asEventTarget(),
|
||||||
event.asEvent(),
|
event.asEvent(),
|
||||||
func,
|
page.js.toLocal(page.window._on_popstate),
|
||||||
.{ .context = "Pop State" },
|
.{ .context = "Pop State" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,7 +246,12 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
|
|||||||
|
|
||||||
const entries = try self.takeRecords(page);
|
const entries = try self.takeRecords(page);
|
||||||
var caught: js.TryCatch.Caught = undefined;
|
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 });
|
log.err(.page, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ const PostMessageCallback = struct {
|
|||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||||
defer self.deinit();
|
defer self.deinit();
|
||||||
|
const page = self.page;
|
||||||
|
|
||||||
if (self.port._closed) {
|
if (self.port._closed) {
|
||||||
return null;
|
return null;
|
||||||
@@ -125,16 +126,19 @@ const PostMessageCallback = struct {
|
|||||||
.data = self.message,
|
.data = self.message,
|
||||||
.origin = "",
|
.origin = "",
|
||||||
.source = null,
|
.source = null,
|
||||||
}, self.page) catch |err| {
|
}, page) catch |err| {
|
||||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const func = if (self.port._on_message) |*g| g.local() else null;
|
var ls: js.Local.Scope = undefined;
|
||||||
self.page._event_manager.dispatchWithFunction(
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
page._event_manager.dispatchWithFunction(
|
||||||
self.port.asEventTarget(),
|
self.port.asEventTarget(),
|
||||||
event.asEvent(),
|
event.asEvent(),
|
||||||
func,
|
ls.toLocal(self.port._on_message),
|
||||||
.{ .context = "MessagePort message" },
|
.{ .context = "MessagePort message" },
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err(.dom, "MessagePort.postMessage", .{ .err = 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
|
// 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
|
// This ensures mutations triggered during the callback go into a fresh list
|
||||||
const records = try self.takeRecords(page);
|
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;
|
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 });
|
log.err(.page, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ pub const SHOW_DOCUMENT_TYPE: u32 = 0x200;
|
|||||||
pub const SHOW_DOCUMENT_FRAGMENT: u32 = 0x400;
|
pub const SHOW_DOCUMENT_FRAGMENT: u32 = 0x400;
|
||||||
pub const SHOW_NOTATION: u32 = 0x800;
|
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;
|
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 {
|
pub fn shouldShow(node: *const Node, what_to_show: u32) bool {
|
||||||
|
|||||||
@@ -362,10 +362,10 @@ pub const Mark = struct {
|
|||||||
|
|
||||||
pub const Measure = struct {
|
pub const Measure = struct {
|
||||||
_proto: *Entry,
|
_proto: *Entry,
|
||||||
_detail: ?js.Object.Global,
|
_detail: ?js.Value.Global,
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
detail: ?js.Object = null,
|
detail: ?js.Value = null,
|
||||||
start: ?TimestampOrMark,
|
start: ?TimestampOrMark,
|
||||||
end: ?TimestampOrMark,
|
end: ?TimestampOrMark,
|
||||||
duration: ?f64 = null,
|
duration: ?f64 = null,
|
||||||
@@ -378,7 +378,7 @@ pub const Measure = struct {
|
|||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
maybe_detail: ?js.Object,
|
maybe_detail: ?js.Value,
|
||||||
start_timestamp: f64,
|
start_timestamp: f64,
|
||||||
end_timestamp: f64,
|
end_timestamp: f64,
|
||||||
maybe_duration: ?f64,
|
maybe_duration: ?f64,
|
||||||
@@ -405,7 +405,7 @@ pub const Measure = struct {
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDetail(self: *const Measure) ?js.Object.Global {
|
pub fn getDetail(self: *const Measure) ?js.Value.Global {
|
||||||
return self._detail;
|
return self._detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,13 @@ pub inline fn hasRecords(self: *const PerformanceObserver) bool {
|
|||||||
/// Runs the PerformanceObserver's callback with records; emptying it out.
|
/// Runs the PerformanceObserver's callback with records; emptying it out.
|
||||||
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
|
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
|
||||||
const records = try self.takeRecords(page);
|
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;
|
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 });
|
log.err(.page, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -96,10 +96,10 @@ pub fn generateKey(
|
|||||||
page: *Page,
|
page: *Page,
|
||||||
) !js.Promise {
|
) !js.Promise {
|
||||||
const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| {
|
const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| {
|
||||||
return page.js.rejectPromise(@errorName(err));
|
return page.js.local.?.rejectPromise(@errorName(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
return page.js.resolvePromise(key_or_pair);
|
return page.js.local.?.resolvePromise(key_or_pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exports a key: that is, it takes as input a CryptoKey object and gives you
|
/// Exports a key: that is, it takes as input a CryptoKey object and gives you
|
||||||
@@ -115,7 +115,7 @@ pub fn exportKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, format, "raw")) {
|
if (std.mem.eql(u8, format, "raw")) {
|
||||||
return page.js.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
|
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
|
||||||
@@ -125,7 +125,7 @@ pub fn exportKey(
|
|||||||
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
||||||
}
|
}
|
||||||
|
|
||||||
return page.js.rejectPromise(@errorName(error.NotSupported));
|
return page.js.local.?.rejectPromise(@errorName(error.NotSupported));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive a secret key from a master key.
|
/// Derive a secret key from a master key.
|
||||||
@@ -140,14 +140,14 @@ pub fn deriveBits(
|
|||||||
.ecdh_or_x25519 => |p| {
|
.ecdh_or_x25519 => |p| {
|
||||||
const name = p.name;
|
const name = p.name;
|
||||||
if (std.mem.eql(u8, name, "X25519")) {
|
if (std.mem.eql(u8, name, "X25519")) {
|
||||||
return page.js.resolvePromise(base_key.deriveBitsX25519(p.public, length, page));
|
return page.js.local.?.resolvePromise(base_key.deriveBitsX25519(p.public, length, page));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, name, "ECDH")) {
|
if (std.mem.eql(u8, name, "ECDH")) {
|
||||||
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
|
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
|
||||||
}
|
}
|
||||||
|
|
||||||
return page.js.rejectPromise(@errorName(error.NotSupported));
|
return page.js.local.?.rejectPromise(@errorName(error.NotSupported));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -184,19 +184,19 @@ pub fn sign(
|
|||||||
.hmac => {
|
.hmac => {
|
||||||
// Verify algorithm.
|
// Verify algorithm.
|
||||||
if (!algorithm.isHMAC()) {
|
if (!algorithm.isHMAC()) {
|
||||||
return page.js.rejectPromise(@errorName(error.InvalidAccessError));
|
return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call sign for HMAC.
|
// Call sign for HMAC.
|
||||||
const result = key.signHMAC(data, page) catch |err| {
|
const result = key.signHMAC(data, page) catch |err| {
|
||||||
return page.js.rejectPromise(@errorName(err));
|
return page.js.local.?.rejectPromise(@errorName(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
return page.js.resolvePromise(result);
|
return page.js.local.?.resolvePromise(result);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
||||||
return page.js.rejectPromise(@errorName(error.InvalidAccessError));
|
return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -454,10 +454,10 @@ pub const CryptoKey = struct {
|
|||||||
if (signed != null) {
|
if (signed != null) {
|
||||||
// CRYPTO_memcmp compare in constant time so prohibits time-based attacks.
|
// CRYPTO_memcmp compare in constant time so prohibits time-based attacks.
|
||||||
const res = crypto.CRYPTO_memcmp(signed, @ptrCast(signature.ptr), signature.len);
|
const res = crypto.CRYPTO_memcmp(signed, @ptrCast(signature.ptr), signature.len);
|
||||||
return page.js.resolvePromise(res == 0);
|
return page.js.local.?.resolvePromise(res == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return page.js.resolvePromise(false);
|
return page.js.local.?.resolvePromise(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// X25519.
|
// X25519.
|
||||||
|
|||||||
@@ -269,10 +269,10 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void {
|
|||||||
sc.removed = true;
|
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", .{
|
const error_event = try ErrorEvent.initTrusted("error", .{
|
||||||
.@"error" = err,
|
.@"error" = try err.persist(),
|
||||||
.message = err.local().toString(.{}) catch "Unknown error",
|
.message = err.toString(.{}) catch "Unknown error",
|
||||||
.bubbles = false,
|
.bubbles = false,
|
||||||
.cancelable = true,
|
.cancelable = true,
|
||||||
}, page);
|
}, page);
|
||||||
@@ -552,20 +552,24 @@ const ScheduleCallback = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
.idle => {
|
.idle => {
|
||||||
const IdleDeadline = @import("IdleDeadline.zig");
|
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 });
|
log.warn(.js, "window.idleCallback", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.animation_frame => {
|
.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 });
|
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.normal => {
|
.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 });
|
log.warn(.js, "window.timer", .{ .name = self.name, .err = err });
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,20 +46,22 @@ pub fn getPending(_: *const Animation) bool {
|
|||||||
|
|
||||||
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
|
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
|
||||||
if (self._finished_resolver == null) {
|
if (self._finished_resolver == null) {
|
||||||
const resolver = try page.js.createPromiseResolver().persist();
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
resolver.local().resolve("Animation.getFinished", self);
|
resolver.resolve("Animation.getFinished", self);
|
||||||
self._finished_resolver = resolver;
|
self._finished_resolver = try resolver.persist();
|
||||||
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
return self._finished_resolver.?.local().promise();
|
return page.js.toLocal(self._finished_resolver).?.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
|
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
|
||||||
// never resolved, because we're always "finished"
|
// never resolved, because we're always "finished"
|
||||||
if (self._ready_resolver == null) {
|
if (self._ready_resolver == null) {
|
||||||
const resolver = try page.js.createPromiseResolver().persist();
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
self._ready_resolver = resolver;
|
self._ready_resolver = try resolver.persist();
|
||||||
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
return self._ready_resolver.?.local().promise();
|
return page.js.toLocal(self._ready_resolver).?.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getEffect(self: *const Animation) ?js.Object.Global {
|
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;
|
_ = self;
|
||||||
_ = text;
|
_ = text;
|
||||||
// TODO: clear self.css_rules
|
// TODO: clear self.css_rules
|
||||||
return page.js.resolvePromise({});
|
return page.js.local.?.resolvePromise({});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replaceSync(self: *CSSStyleSheet, text: []const u8) !void {
|
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 {
|
pub fn complete(node: *Node, page: *Page) !void {
|
||||||
const el = node.as(Element);
|
const el = node.as(Element);
|
||||||
const on_load = el.getAttributeSafe("onload") orelse return;
|
const on_load = el.getAttributeSafe("onload") orelse return;
|
||||||
if (page.js.stringToFunction(on_load)) |func| {
|
if (page.js.stringToPersistedFunction(on_load)) |func| {
|
||||||
page.window._on_load = try func.persist();
|
page.window._on_load = func;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.err(.js, "body.onload", .{ .err = err, .str = on_load });
|
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 {
|
fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefinition, comptime callback_name: [:0]const u8, args: anytype, page: *Page) void {
|
||||||
_ = definition;
|
_ = definition;
|
||||||
|
|
||||||
const ctx = page.js;
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
// Get the JS element object
|
// 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();
|
const js_element = js_val.toObject();
|
||||||
|
|
||||||
// Call the callback method if it exists
|
// Call the callback method if it exists
|
||||||
@@ -195,8 +197,26 @@ pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
|
|||||||
page._upgrading_element = node;
|
page._upgrading_element = node;
|
||||||
defer page._upgrading_element = prev_upgrading;
|
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;
|
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 });
|
log.warn(.js, "custom builtin ctor", .{ .name = is_value, .err = err, .caught = caught });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -207,9 +227,11 @@ fn invokeCallback(self: *Custom, comptime callback_name: [:0]const u8, args: any
|
|||||||
return;
|
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();
|
const js_element = js_val.toObject();
|
||||||
|
|
||||||
js_element.callMethod(void, callback_name, args) catch return;
|
js_element.callMethod(void, callback_name, args) catch return;
|
||||||
|
|||||||
@@ -130,16 +130,16 @@ pub const Build = struct {
|
|||||||
self._src = element.getAttributeSafe("src") orelse "";
|
self._src = element.getAttributeSafe("src") orelse "";
|
||||||
|
|
||||||
if (element.getAttributeSafe("onload")) |on_load| {
|
if (element.getAttributeSafe("onload")) |on_load| {
|
||||||
if (page.js.stringToFunction(on_load)) |func| {
|
if (page.js.stringToPersistedFunction(on_load)) |func| {
|
||||||
self._on_load = try func.persist();
|
self._on_load = func;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
|
log.err(.js, "script.onload", .{ .err = err, .str = on_load });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.getAttributeSafe("onerror")) |on_error| {
|
if (element.getAttributeSafe("onerror")) |on_error| {
|
||||||
if (page.js.stringToFunction(on_error)) |func| {
|
if (page.js.stringToPersistedFunction(on_error)) |func| {
|
||||||
self._on_error = try func.persist();
|
self._on_error = func;
|
||||||
} else |err| {
|
} else |err| {
|
||||||
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
|
log.err(.js, "script.onerror", .{ .err = err, .str = on_error });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ const Allocator = std.mem.Allocator;
|
|||||||
const CustomEvent = @This();
|
const CustomEvent = @This();
|
||||||
|
|
||||||
_proto: *Event,
|
_proto: *Event,
|
||||||
_arena: Allocator,
|
|
||||||
_detail: ?js.Value.Global = null,
|
_detail: ?js.Value.Global = null,
|
||||||
|
_arena: Allocator,
|
||||||
|
|
||||||
const CustomEventOptions = struct {
|
const CustomEventOptions = struct {
|
||||||
detail: ?js.Value.Global = null,
|
detail: ?js.Value.Global = null,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ pub fn asEvent(self: *PopStateEvent) *Event {
|
|||||||
|
|
||||||
pub fn getState(self: *PopStateEvent, page: *Page) !?js.Value {
|
pub fn getState(self: *PopStateEvent, page: *Page) !?js.Value {
|
||||||
const s = self._state orelse return null;
|
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 {
|
pub fn hasUAVisualTransition(_: *PopStateEvent) bool {
|
||||||
|
|||||||
@@ -267,8 +267,9 @@ pub fn navigateInner(
|
|||||||
//
|
//
|
||||||
// These will only settle on same-origin navigation (mostly intended for SPAs).
|
// 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 :)
|
// It is fine (and expected) for these to not settle on cross-origin requests :)
|
||||||
const committed = try page.js.createPromiseResolver().persist();
|
const local = page.js.local.?;
|
||||||
const finished = try page.js.createPromiseResolver().persist();
|
const committed = local.createPromiseResolver();
|
||||||
|
const finished = local.createPromiseResolver();
|
||||||
|
|
||||||
const new_url = try URL.resolve(arena, page.url, url, .{});
|
const new_url = try URL.resolve(arena, page.url, url, .{});
|
||||||
const is_same_document = URL.eqlDocument(new_url, page.url);
|
const is_same_document = URL.eqlDocument(new_url, page.url);
|
||||||
@@ -280,9 +281,9 @@ pub fn navigateInner(
|
|||||||
if (is_same_document) {
|
if (is_same_document) {
|
||||||
page.url = new_url;
|
page.url = new_url;
|
||||||
|
|
||||||
committed.local().resolve("navigation push", {});
|
committed.resolve("navigation push", {});
|
||||||
// todo: Fire navigate event
|
// todo: Fire navigate event
|
||||||
finished.local().resolve("navigation push", {});
|
finished.resolve("navigation push", {});
|
||||||
|
|
||||||
_ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true);
|
_ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true);
|
||||||
} else {
|
} else {
|
||||||
@@ -293,9 +294,9 @@ pub fn navigateInner(
|
|||||||
if (is_same_document) {
|
if (is_same_document) {
|
||||||
page.url = new_url;
|
page.url = new_url;
|
||||||
|
|
||||||
committed.local().resolve("navigation replace", {});
|
committed.resolve("navigation replace", {});
|
||||||
// todo: Fire navigate event
|
// todo: Fire navigate event
|
||||||
finished.local().resolve("navigation replace", {});
|
finished.resolve("navigation replace", {});
|
||||||
|
|
||||||
_ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true);
|
_ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true);
|
||||||
} else {
|
} else {
|
||||||
@@ -308,9 +309,9 @@ pub fn navigateInner(
|
|||||||
if (is_same_document) {
|
if (is_same_document) {
|
||||||
page.url = new_url;
|
page.url = new_url;
|
||||||
|
|
||||||
committed.local().resolve("navigation traverse", {});
|
committed.resolve("navigation traverse", {});
|
||||||
// todo: Fire navigate event
|
// todo: Fire navigate event
|
||||||
finished.local().resolve("navigation traverse", {});
|
finished.resolve("navigation traverse", {});
|
||||||
} else {
|
} else {
|
||||||
try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script);
|
try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script);
|
||||||
}
|
}
|
||||||
@@ -328,9 +329,11 @@ pub fn navigateInner(
|
|||||||
);
|
);
|
||||||
try self._proto.dispatch(.{ .currententrychange = event }, page);
|
try self._proto.dispatch(.{ .currententrychange = event }, page);
|
||||||
|
|
||||||
|
_ = try committed.persist();
|
||||||
|
_ = try finished.persist();
|
||||||
return .{
|
return .{
|
||||||
.committed = try committed.local().promise().persist(),
|
.committed = try committed.promise().persist(),
|
||||||
.finished = try finished.local().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(
|
return page._event_manager.dispatchWithFunction(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
func,
|
page.js.toLocal(@field(self, field)),
|
||||||
.{ .context = "Navigation" },
|
.{ .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 {
|
pub fn getState(self: *const NavigationHistoryEntry, page: *Page) !StateReturn {
|
||||||
if (self._state.source == .navigation) {
|
if (self._state.source == .navigation) {
|
||||||
if (self._state.value) |value| {
|
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 {
|
pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
||||||
const request = try Request.init(input, options, page);
|
const request = try Request.init(input, options, page);
|
||||||
|
|
||||||
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
|
|
||||||
const fetch = try page.arena.create(Fetch);
|
const fetch = try page.arena.create(Fetch);
|
||||||
fetch.* = .{
|
fetch.* = .{
|
||||||
._page = page,
|
._page = page,
|
||||||
._buf = .empty,
|
._buf = .empty,
|
||||||
._url = try page.arena.dupe(u8, request._url),
|
._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),
|
._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,
|
.done_callback = httpDoneCallback,
|
||||||
.error_callback = httpErrorCallback,
|
.error_callback = httpErrorCallback,
|
||||||
});
|
});
|
||||||
return fetch._resolver.local().promise();
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Fetch) void {
|
pub fn deinit(self: *Fetch) void {
|
||||||
@@ -149,13 +151,22 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
|||||||
.len = self._buf.items.len,
|
.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 {
|
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||||
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
const self: *Fetch = @ptrCast(@alignCast(ctx));
|
||||||
self._response._type = .@"error"; // Set type to error for network failures
|
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");
|
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 {
|
pub fn getText(self: *const Response, page: *Page) !js.Promise {
|
||||||
const body = self._body orelse "";
|
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 {
|
pub fn getJson(self: *Response, page: *Page) !js.Promise {
|
||||||
const body = self._body orelse "";
|
const body = self._body orelse "";
|
||||||
const value = page.js.parseJSON(body) catch |err| {
|
const local = page.js.local.?;
|
||||||
return page.js.rejectPromise(.{@errorName(err)});
|
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 {
|
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._method = try parseMethod(method_);
|
||||||
self._url = try URL.resolve(self._arena, self._page.base(), url, .{ .always_dupe = true });
|
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 {
|
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) {
|
const res: Response = switch (self._response_type) {
|
||||||
.text => .{ .text = data },
|
.text => .{ .text = data },
|
||||||
.json => blk: {
|
.json => blk: {
|
||||||
const value = try page.js.parseJSON(data);
|
const value = try page.js.local.?.parseJSON(data);
|
||||||
break :blk .{ .json = try value.persist() };
|
break :blk .{ .json = try value.persist() };
|
||||||
},
|
},
|
||||||
.document => blk: {
|
.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));
|
self._response_url = try self._arena.dupeZ(u8, std.mem.span(header.url));
|
||||||
|
|
||||||
try self.stateChanged(.headers_received, self._page);
|
const page = self._page;
|
||||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, self._page);
|
|
||||||
try self.stateChanged(.loading, 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 {
|
fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
||||||
const self: *XMLHttpRequest = @ptrCast(@alignCast(transfer.ctx));
|
const self: *XMLHttpRequest = @ptrCast(@alignCast(transfer.ctx));
|
||||||
try self._response_data.appendSlice(self._arena, data);
|
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, .{
|
try self._proto.dispatch(.progress, .{
|
||||||
.total = self._response_len orelse 0,
|
.total = self._response_len orelse 0,
|
||||||
.loaded = self._response_data.items.len,
|
.loaded = self._response_data.items.len,
|
||||||
}, self._page);
|
}, &ls.local, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
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
|
// Not that the request is done, the http/client will free the transfer
|
||||||
// object. It isn't safe to keep it around.
|
// object. It isn't safe to keep it around.
|
||||||
self._transfer = null;
|
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;
|
const loaded = self._response_data.items.len;
|
||||||
try self._proto.dispatch(.load, .{
|
try self._proto.dispatch(.load, .{
|
||||||
.total = loaded,
|
.total = loaded,
|
||||||
.loaded = loaded,
|
.loaded = loaded,
|
||||||
}, self._page);
|
}, local, page);
|
||||||
try self._proto.dispatch(.load_end, .{
|
try self._proto.dispatch(.load_end, .{
|
||||||
.total = loaded,
|
.total = loaded,
|
||||||
.loaded = loaded,
|
.loaded = loaded,
|
||||||
}, self._page);
|
}, local, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
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;
|
const new_state: ReadyState = if (is_abort) .unsent else .done;
|
||||||
if (new_state != self._ready_state) {
|
if (new_state != self._ready_state) {
|
||||||
const page = self._page;
|
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) {
|
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(.err, null, local, page);
|
||||||
try self._proto.dispatch(.load_end, null, page);
|
try self._proto.dispatch(.load_end, null, local, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const level: log.Level = if (err == error.Abort) .debug else .err;
|
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) {
|
if (state == self._ready_state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -416,11 +443,10 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
|
|||||||
self._ready_state = state;
|
self._ready_state = state;
|
||||||
|
|
||||||
const event = try Event.initTrusted("readystatechange", .{}, page);
|
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(
|
try page._event_manager.dispatchWithFunction(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
func,
|
local.toLocal(self._on_ready_state_change),
|
||||||
.{ .context = "XHR state change" },
|
.{ .context = "XHR state change" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn asEventTarget(self: *XMLHttpRequestEventTarget) *EventTarget {
|
|||||||
return self._proto;
|
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: {
|
const field, const typ = comptime blk: {
|
||||||
break :blk switch (event_type) {
|
break :blk switch (event_type) {
|
||||||
.abort => .{ "_on_abort", "abort" },
|
.abort => .{ "_on_abort", "abort" },
|
||||||
@@ -63,11 +63,10 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
|
|||||||
page,
|
page,
|
||||||
);
|
);
|
||||||
|
|
||||||
const func = if (@field(self, field)) |*g| g.local() else null;
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
return page._event_manager.dispatchWithFunction(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event.asEvent(),
|
event.asEvent(),
|
||||||
func,
|
local.toLocal(@field(self, field)),
|
||||||
.{ .context = "XHR " ++ typ },
|
.{ .context = "XHR " ++ typ },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,12 +137,12 @@ pub fn callPullIfNeeded(self: *ReadableStream) !void {
|
|||||||
|
|
||||||
self._pulling = true;
|
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
|
// Call the pull function
|
||||||
// Note: In a complete implementation, we'd handle the promise returned by pull
|
// Note: In a complete implementation, we'd handle the promise returned by pull
|
||||||
// and set _pulling = false when it resolves
|
// 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;
|
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 {
|
pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promise {
|
||||||
|
const local = page.js.local.?;
|
||||||
|
|
||||||
if (self._state != .readable) {
|
if (self._state != .readable) {
|
||||||
if (self._cancel) |c| {
|
if (self._cancel) |c| {
|
||||||
if (c.resolver) |r| {
|
if (c.resolver) |r| {
|
||||||
return r.local().promise();
|
return local.toLocal(r).promise();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return page.js.resolvePromise(.{});
|
return local.resolvePromise(.{});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self._cancel == null) {
|
if (self._cancel == null) {
|
||||||
@@ -181,16 +183,21 @@ pub fn cancel(self: *ReadableStream, reason: ?[]const u8, page: *Page) !js.Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
var c = &self._cancel.?;
|
var c = &self._cancel.?;
|
||||||
if (c.resolver == null) {
|
var resolver = blk: {
|
||||||
c.resolver = try page.js.createPromiseResolver().persist();
|
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
|
// Execute the cancel callback if provided
|
||||||
if (c.callback) |*cb| {
|
if (c.callback) |cb| {
|
||||||
if (reason) |r| {
|
if (reason) |r| {
|
||||||
try cb.local().call(void, .{r});
|
try local.toLocal(cb).call(void, .{r});
|
||||||
} else {
|
} 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,
|
.done = true,
|
||||||
.value = .empty,
|
.value = .empty,
|
||||||
};
|
};
|
||||||
for (self._controller._pending_reads.items) |*resolver| {
|
for (self._controller._pending_reads.items) |r| {
|
||||||
resolver.local().resolve("stream cancelled", result);
|
local.toLocal(r).resolve("stream cancelled", result);
|
||||||
}
|
}
|
||||||
self._controller._pending_reads.clearRetainingCapacity();
|
self._controller._pending_reads.clearRetainingCapacity();
|
||||||
|
resolver.resolve("ReadableStream.cancel", {});
|
||||||
c.resolver.?.local().resolve("ReadableStream.cancel", {});
|
return resolver.promise();
|
||||||
return c.resolver.?.local().promise();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Cancel = struct {
|
const Cancel = struct {
|
||||||
@@ -250,7 +256,7 @@ pub const AsyncIterator = struct {
|
|||||||
|
|
||||||
pub fn @"return"(self: *AsyncIterator, page: *Page) !js.Promise {
|
pub fn @"return"(self: *AsyncIterator, page: *Page) !js.Promise {
|
||||||
self._reader.releaseLock();
|
self._reader.releaseLock();
|
||||||
return page.js.resolvePromise(.{ .done = true, .value = null });
|
return page.js.local.?.resolvePromise(.{ .done = true, .value = null });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
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 {
|
pub fn addPendingRead(self: *ReadableStreamDefaultController, page: *Page) !js.Promise {
|
||||||
const resolver = try page.js.createPromiseResolver().persist();
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
try self._pending_reads.append(self._arena, resolver);
|
try self._pending_reads.append(self._arena, try resolver.persist());
|
||||||
return resolver.local().promise();
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
|
pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
|
||||||
@@ -79,7 +79,7 @@ pub fn enqueue(self: *ReadableStreamDefaultController, chunk: Chunk) !void {
|
|||||||
.done = false,
|
.done = false,
|
||||||
.value = .fromChunk(chunk),
|
.value = .fromChunk(chunk),
|
||||||
};
|
};
|
||||||
resolver.local().resolve("stream enqueue", result);
|
self._page.js.toLocal(resolver).resolve("stream enqueue", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(self: *ReadableStreamDefaultController) !void {
|
pub fn close(self: *ReadableStreamDefaultController) !void {
|
||||||
@@ -94,8 +94,8 @@ pub fn close(self: *ReadableStreamDefaultController) !void {
|
|||||||
.done = true,
|
.done = true,
|
||||||
.value = .empty,
|
.value = .empty,
|
||||||
};
|
};
|
||||||
for (self._pending_reads.items) |*resolver| {
|
for (self._pending_reads.items) |resolver| {
|
||||||
resolver.local().resolve("stream close", result);
|
self._page.js.toLocal(resolver).resolve("stream close", result);
|
||||||
}
|
}
|
||||||
self._pending_reads.clearRetainingCapacity();
|
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);
|
self._stream._stored_error = try self._page.arena.dupe(u8, err);
|
||||||
|
|
||||||
// Reject all pending reads
|
// Reject all pending reads
|
||||||
for (self._pending_reads.items) |*resolver| {
|
for (self._pending_reads.items) |resolver| {
|
||||||
resolver.local().reject("stream errror", err);
|
self._page.js.toLocal(resolver).reject("stream errror", err);
|
||||||
}
|
}
|
||||||
self._pending_reads.clearRetainingCapacity();
|
self._pending_reads.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ pub const ReadResult = struct {
|
|||||||
|
|
||||||
pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
||||||
const stream = self._stream orelse {
|
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) {
|
if (stream._state == .errored) {
|
||||||
const err = stream._stored_error orelse "Stream 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| {
|
if (stream._controller.dequeue()) |chunk| {
|
||||||
@@ -69,7 +69,7 @@ pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
|||||||
.done = false,
|
.done = false,
|
||||||
.value = .fromChunk(chunk),
|
.value = .fromChunk(chunk),
|
||||||
};
|
};
|
||||||
return page.js.resolvePromise(result);
|
return page.js.local.?.resolvePromise(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream._state == .closed) {
|
if (stream._state == .closed) {
|
||||||
@@ -77,7 +77,7 @@ pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise {
|
|||||||
.done = true,
|
.done = true,
|
||||||
.value = .empty,
|
.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
|
// 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 {
|
pub fn cancel(self: *ReadableStreamDefaultReader, reason_: ?[]const u8, page: *Page) !js.Promise {
|
||||||
const stream = self._stream orelse {
|
const stream = self._stream orelse {
|
||||||
return page.js.rejectPromise("Reader has been released");
|
return page.js.local.?.rejectPromise("Reader has been released");
|
||||||
};
|
};
|
||||||
|
|
||||||
self.releaseLock();
|
self.releaseLock();
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const Selector = @import("../../browser/webapi/selector/Selector.zig");
|
|||||||
|
|
||||||
const dump = @import("../../browser/dump.zig");
|
const dump = @import("../../browser/dump.zig");
|
||||||
const js = @import("../../browser/js/js.zig");
|
const js = @import("../../browser/js/js.zig");
|
||||||
const v8 = js.v8;
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
@@ -273,16 +272,32 @@ fn resolveNode(cmd: anytype) !void {
|
|||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
|
|
||||||
var js_context = page.js;
|
var ls: ?js.Local.Scope = null;
|
||||||
if (params.executionContextId) |context_id| {
|
defer if (ls) |*_ls| {
|
||||||
if (js_context.debugContextId() != context_id) {
|
_ls.deinit();
|
||||||
for (bc.isolated_worlds.items) |*isolated_world| {
|
};
|
||||||
js_context = &(isolated_world.executor.context orelse return error.ContextNotFound);
|
|
||||||
if (js_context.debugContextId() == context_id) {
|
if (params.executionContextId) |context_id| blk: {
|
||||||
break;
|
ls = undefined;
|
||||||
}
|
page.js.localScope(&ls.?);
|
||||||
} else return error.ContextNotFound;
|
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;
|
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
|
// 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
|
// So we use the Node.Union when retrieve the value from the environment
|
||||||
const remote_object = try bc.inspector.getRemoteObject(
|
const remote_object = try bc.inspector.getRemoteObject(
|
||||||
js_context,
|
&ls.?.local,
|
||||||
params.objectGroup orelse "",
|
params.objectGroup orelse "",
|
||||||
node.dom,
|
node.dom,
|
||||||
);
|
);
|
||||||
@@ -377,15 +392,20 @@ fn scrollIntoViewIfNeeded(cmd: anytype) !void {
|
|||||||
return cmd.sendResult(null, .{});
|
return cmd.sendResult(null, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getNode(arena: Allocator, browser_context: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
|
fn getNode(arena: Allocator, bc: anytype, node_id: ?Node.Id, backend_node_id: ?Node.Id, object_id: ?[]const u8) !*Node {
|
||||||
const input_node_id = node_id orelse backend_node_id;
|
const input_node_id = node_id orelse backend_node_id;
|
||||||
if (input_node_id) |input_node_id_| {
|
if (input_node_id) |input_node_id_| {
|
||||||
return browser_context.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
|
return bc.node_registry.lookup_by_id.get(input_node_id_) orelse return error.NodeNotFound;
|
||||||
}
|
}
|
||||||
if (object_id) |object_id_| {
|
if (object_id) |object_id_| {
|
||||||
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
// Retrieve the object from which ever context it is in.
|
// Retrieve the object from which ever context it is in.
|
||||||
const parser_node = try browser_context.inspector.getNodePtr(arena, object_id_);
|
const parser_node = try bc.inspector.getNodePtr(arena, object_id_, &ls.local);
|
||||||
return try browser_context.node_registry.register(@ptrCast(@alignCast(parser_node)));
|
return try bc.node_registry.register(@ptrCast(@alignCast(parser_node)));
|
||||||
}
|
}
|
||||||
return error.MissingParams;
|
return error.MissingParams;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,9 +196,12 @@ fn createIsolatedWorld(cmd: anytype) !void {
|
|||||||
// Create the auxdata json for the contextCreated event
|
// Create the auxdata json for the contextCreated event
|
||||||
// Calling contextCreated will assign a Id to the context and send 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});
|
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 {
|
fn navigate(cmd: anytype) !void {
|
||||||
@@ -353,8 +356,13 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
{
|
{
|
||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
const aux_data = try std.fmt.allocPrint(arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
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(
|
bc.inspector.contextCreated(
|
||||||
page.js,
|
&ls.local,
|
||||||
"",
|
"",
|
||||||
try page.getOrigin(arena) orelse "",
|
try page.getOrigin(arena) orelse "",
|
||||||
aux_data,
|
aux_data,
|
||||||
@@ -363,9 +371,15 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
}
|
}
|
||||||
for (bc.isolated_worlds.items) |*isolated_world| {
|
for (bc.isolated_worlds.items) |*isolated_world| {
|
||||||
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
|
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
|
// 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(
|
bc.inspector.contextCreated(
|
||||||
&isolated_world.executor.context.?,
|
&ls.local,
|
||||||
isolated_world.name,
|
isolated_world.name,
|
||||||
"://",
|
"://",
|
||||||
aux_json,
|
aux_json,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const js = @import("../../browser/js/js.zig");
|
||||||
|
|
||||||
// TODO: hard coded IDs
|
// TODO: hard coded IDs
|
||||||
const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";
|
const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";
|
||||||
@@ -176,9 +177,13 @@ fn createTarget(cmd: anytype) !void {
|
|||||||
|
|
||||||
const page = try bc.session.createPage();
|
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});
|
const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id});
|
||||||
bc.inspector.contextCreated(
|
bc.inspector.contextCreated(
|
||||||
page.js,
|
&ls.local,
|
||||||
"",
|
"",
|
||||||
"", // @ZIGDOM
|
"", // @ZIGDOM
|
||||||
// try page.origin(arena),
|
// try page.origin(arena),
|
||||||
|
|||||||
@@ -85,15 +85,18 @@ pub fn run(allocator: Allocator, file: []const u8, session: *lp.Session) !void {
|
|||||||
const page = try session.createPage();
|
const page = try session.createPage();
|
||||||
defer session.removePage();
|
defer session.removePage();
|
||||||
|
|
||||||
const js_context = page.js;
|
var ls: lp.js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
var try_catch: lp.js.TryCatch = undefined;
|
var try_catch: lp.js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(&ls.local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
try page.navigate(url, .{});
|
try page.navigate(url, .{});
|
||||||
_ = session.wait(2000);
|
_ = session.wait(2000);
|
||||||
|
|
||||||
js_context.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
ls.local.eval("testing.assertOk()", "testing.assertOk()") catch |err| {
|
||||||
const caught = try_catch.caughtOrError(allocator, err);
|
const caught = try_catch.caughtOrError(allocator, err);
|
||||||
std.debug.print("{s}: test failure\nError: {f}\n", .{ file, caught });
|
std.debug.print("{s}: test failure\nError: {f}\n", .{ file, caught });
|
||||||
return err;
|
return err;
|
||||||
|
|||||||
@@ -118,20 +118,23 @@ fn run(
|
|||||||
|
|
||||||
_ = page.wait(2000);
|
_ = page.wait(2000);
|
||||||
|
|
||||||
const js_context = page.js;
|
var ls: lp.js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
var try_catch: lp.js.TryCatch = undefined;
|
var try_catch: lp.js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(&ls.local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
// Check the final test status.
|
// Check the final test status.
|
||||||
js_context.eval("report.status", "teststatus") catch |err| {
|
ls.local.eval("report.status", "teststatus") catch |err| {
|
||||||
const caught = try_catch.caughtOrError(arena, err);
|
const caught = try_catch.caughtOrError(arena, err);
|
||||||
err_out.* = caught.exception;
|
err_out.* = caught.exception;
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
// return the detailed result.
|
// return the detailed result.
|
||||||
const value = js_context.exec("report.log", "report") catch |err| {
|
const value = ls.local.exec("report.log", "report") catch |err| {
|
||||||
const caught = try_catch.caughtOrError(arena, err);
|
const caught = try_catch.caughtOrError(arena, err);
|
||||||
err_out.* = caught.exception;
|
err_out.* = caught.exception;
|
||||||
return err;
|
return err;
|
||||||
|
|||||||
@@ -396,9 +396,12 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
|||||||
0,
|
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;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(&ls.local);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
try page.navigate(url, .{});
|
try page.navigate(url, .{});
|
||||||
@@ -406,7 +409,7 @@ fn runWebApiTest(test_file: [:0]const u8) !void {
|
|||||||
|
|
||||||
test_browser.runMicrotasks();
|
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);
|
const caught = try_catch.caughtOrError(arena_allocator, err);
|
||||||
std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught });
|
std.debug.print("{s}: test failure\nError: {f}\n", .{ test_file, caught });
|
||||||
return err;
|
return err;
|
||||||
|
|||||||
Reference in New Issue
Block a user