mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Merge pull request #1114 from lightpanda-io/extract_js_structs_to_files
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
Start extract JS structs into their own files
This commit is contained in:
@@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
|
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
const Http = @import("http/Http.zig");
|
const Http = @import("http/Http.zig");
|
||||||
const Platform = @import("browser/js/js.zig").Platform;
|
const Platform = @import("browser/js/Platform.zig");
|
||||||
|
|
||||||
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
||||||
const Notification = @import("notification.zig").Notification;
|
const Notification = @import("notification.zig").Notification;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ style_sheet: ?*StyleSheet = null,
|
|||||||
|
|
||||||
// for dom/document
|
// for dom/document
|
||||||
active_element: ?*parser.Element = null,
|
active_element: ?*parser.Element = null,
|
||||||
adopted_style_sheets: ?js.JsObject = null,
|
adopted_style_sheets: ?js.Object = null,
|
||||||
|
|
||||||
// for HTMLSelectElement
|
// for HTMLSelectElement
|
||||||
// By default, if no option is explicitly selected, the first option should
|
// By default, if no option is explicitly selected, the first option should
|
||||||
|
|||||||
@@ -28,39 +28,39 @@ pub const Console = struct {
|
|||||||
timers: std.StringHashMapUnmanaged(u32) = .{},
|
timers: std.StringHashMapUnmanaged(u32) = .{},
|
||||||
counts: std.StringHashMapUnmanaged(u32) = .{},
|
counts: std.StringHashMapUnmanaged(u32) = .{},
|
||||||
|
|
||||||
pub fn _lp(values: []js.JsObject, page: *Page) !void {
|
pub fn _lp(values: []js.Object, page: *Page) !void {
|
||||||
if (values.len == 0) {
|
if (values.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.fatal(.console, "lightpanda", .{ .args = try serializeValues(values, page) });
|
log.fatal(.console, "lightpanda", .{ .args = try serializeValues(values, page) });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _log(values: []js.JsObject, page: *Page) !void {
|
pub fn _log(values: []js.Object, page: *Page) !void {
|
||||||
if (values.len == 0) {
|
if (values.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.info(.console, "info", .{ .args = try serializeValues(values, page) });
|
log.info(.console, "info", .{ .args = try serializeValues(values, page) });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _info(values: []js.JsObject, page: *Page) !void {
|
pub fn _info(values: []js.Object, page: *Page) !void {
|
||||||
return _log(values, page);
|
return _log(values, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _debug(values: []js.JsObject, page: *Page) !void {
|
pub fn _debug(values: []js.Object, page: *Page) !void {
|
||||||
if (values.len == 0) {
|
if (values.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.debug(.console, "debug", .{ .args = try serializeValues(values, page) });
|
log.debug(.console, "debug", .{ .args = try serializeValues(values, page) });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _warn(values: []js.JsObject, page: *Page) !void {
|
pub fn _warn(values: []js.Object, page: *Page) !void {
|
||||||
if (values.len == 0) {
|
if (values.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.warn(.console, "warn", .{ .args = try serializeValues(values, page) });
|
log.warn(.console, "warn", .{ .args = try serializeValues(values, page) });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _error(values: []js.JsObject, page: *Page) !void {
|
pub fn _error(values: []js.Object, page: *Page) !void {
|
||||||
if (values.len == 0) {
|
if (values.len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ pub const Console = struct {
|
|||||||
log.warn(.console, "timer stop", .{ .label = label, .elapsed = elapsed - kv.value });
|
log.warn(.console, "timer stop", .{ .label = label, .elapsed = elapsed - kv.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _assert(assertion: js.JsObject, values: []js.JsObject, page: *Page) !void {
|
pub fn _assert(assertion: js.Object, values: []js.Object, page: *Page) !void {
|
||||||
if (assertion.isTruthy()) {
|
if (assertion.isTruthy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ pub const Console = struct {
|
|||||||
log.info(.console, "assertion failed", .{ .values = serialized_values });
|
log.info(.console, "assertion failed", .{ .values = serialized_values });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serializeValues(values: []js.JsObject, page: *Page) ![]const u8 {
|
fn serializeValues(values: []js.Object, page: *Page) ![]const u8 {
|
||||||
if (values.len == 0) {
|
if (values.len == 0) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const uuidv4 = @import("../../id.zig").uuidv4;
|
|||||||
pub const Crypto = struct {
|
pub const Crypto = struct {
|
||||||
_not_empty: bool = true,
|
_not_empty: bool = true,
|
||||||
|
|
||||||
pub fn _getRandomValues(_: *const Crypto, js_obj: js.JsObject) !js.JsObject {
|
pub fn _getRandomValues(_: *const Crypto, js_obj: js.Object) !js.Object {
|
||||||
var into = try js_obj.toZig(Crypto, "getRandomValues", RandomValues);
|
var into = try js_obj.toZig(Crypto, "getRandomValues", RandomValues);
|
||||||
const buf = into.asBuffer();
|
const buf = into.asBuffer();
|
||||||
if (buf.len > 65_536) {
|
if (buf.len > 65_536) {
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ const Page = @import("../page.zig").Page;
|
|||||||
|
|
||||||
const Animation = @This();
|
const Animation = @This();
|
||||||
|
|
||||||
effect: ?js.JsObject,
|
effect: ?js.Object,
|
||||||
timeline: ?js.JsObject,
|
timeline: ?js.Object,
|
||||||
ready_resolver: ?js.PromiseResolver,
|
ready_resolver: ?js.PromiseResolver,
|
||||||
finished_resolver: ?js.PromiseResolver,
|
finished_resolver: ?js.PromiseResolver,
|
||||||
|
|
||||||
pub fn constructor(effect: ?js.JsObject, timeline: ?js.JsObject) !Animation {
|
pub fn constructor(effect: ?js.Object, timeline: ?js.Object) !Animation {
|
||||||
return .{
|
return .{
|
||||||
.effect = if (effect) |eo| try eo.persist() else null,
|
.effect = if (effect) |eo| try eo.persist() else null,
|
||||||
.timeline = if (timeline) |to| try to.persist() else null,
|
.timeline = if (timeline) |to| try to.persist() else null,
|
||||||
@@ -65,19 +65,19 @@ pub fn get_ready(self: *Animation, page: *Page) !js.Promise {
|
|||||||
return self.ready_resolver.?.promise();
|
return self.ready_resolver.?.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_effect(self: *const Animation) ?js.JsObject {
|
pub fn get_effect(self: *const Animation) ?js.Object {
|
||||||
return self.effect;
|
return self.effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_effect(self: *Animation, effect: js.JsObject) !void {
|
pub fn set_effect(self: *Animation, effect: js.Object) !void {
|
||||||
self.effect = try effect.persist();
|
self.effect = try effect.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_timeline(self: *const Animation) ?js.JsObject {
|
pub fn get_timeline(self: *const Animation) ?js.Object {
|
||||||
return self.timeline;
|
return self.timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_timeline(self: *Animation, timeline: js.JsObject) !void {
|
pub fn set_timeline(self: *Animation, timeline: js.Object) !void {
|
||||||
self.timeline = try timeline.persist();
|
self.timeline = try timeline.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,18 +74,18 @@ pub const MessagePort = struct {
|
|||||||
onmessageerror_cbk: ?js.Function = null,
|
onmessageerror_cbk: ?js.Function = null,
|
||||||
// This is the queue of messages to dispatch to THIS MessagePort when the
|
// This is the queue of messages to dispatch to THIS MessagePort when the
|
||||||
// MessagePort is started.
|
// MessagePort is started.
|
||||||
queue: std.ArrayListUnmanaged(js.JsObject) = .empty,
|
queue: std.ArrayListUnmanaged(js.Object) = .empty,
|
||||||
|
|
||||||
pub const PostMessageOption = union(enum) {
|
pub const PostMessageOption = union(enum) {
|
||||||
transfer: js.JsObject,
|
transfer: js.Object,
|
||||||
options: Opts,
|
options: Opts,
|
||||||
|
|
||||||
pub const Opts = struct {
|
pub const Opts = struct {
|
||||||
transfer: js.JsObject,
|
transfer: js.Object,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn _postMessage(self: *MessagePort, obj: js.JsObject, opts_: ?PostMessageOption, page: *Page) !void {
|
pub fn _postMessage(self: *MessagePort, obj: js.Object, opts_: ?PostMessageOption, page: *Page) !void {
|
||||||
if (self.closed) {
|
if (self.closed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ pub const MessagePort = struct {
|
|||||||
|
|
||||||
// called from our pair. If port1.postMessage("x") is called, then this
|
// called from our pair. If port1.postMessage("x") is called, then this
|
||||||
// will be called on port2.
|
// will be called on port2.
|
||||||
fn dispatchOrQueue(self: *MessagePort, obj: js.JsObject, arena: Allocator) !void {
|
fn dispatchOrQueue(self: *MessagePort, obj: js.Object, arena: Allocator) !void {
|
||||||
// our pair should have checked this already
|
// our pair should have checked this already
|
||||||
std.debug.assert(self.closed == false);
|
std.debug.assert(self.closed == false);
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ pub const MessagePort = struct {
|
|||||||
return self.queue.append(arena, try obj.persist());
|
return self.queue.append(arena, try obj.persist());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch(self: *MessagePort, obj: js.JsObject) !void {
|
fn dispatch(self: *MessagePort, obj: js.Object) !void {
|
||||||
// obj is already persisted, don't use `MessageEvent.constructor`, but
|
// obj is already persisted, don't use `MessageEvent.constructor`, but
|
||||||
// go directly to `init`, which assumes persisted objects.
|
// go directly to `init`, which assumes persisted objects.
|
||||||
var evt = try MessageEvent.init(.{ .data = obj });
|
var evt = try MessageEvent.init(.{ .data = obj });
|
||||||
@@ -205,12 +205,12 @@ pub const MessageEvent = struct {
|
|||||||
pub const union_make_copy = true;
|
pub const union_make_copy = true;
|
||||||
|
|
||||||
proto: parser.Event,
|
proto: parser.Event,
|
||||||
data: ?js.JsObject,
|
data: ?js.Object,
|
||||||
|
|
||||||
// You would think if port1 sends to port2, the source would be port2
|
// You would think if port1 sends to port2, the source would be port2
|
||||||
// (which is how I read the documentation), but it appears to always be
|
// (which is how I read the documentation), but it appears to always be
|
||||||
// null. It can always be set explicitly via the constructor;
|
// null. It can always be set explicitly via the constructor;
|
||||||
source: ?js.JsObject,
|
source: ?js.Object,
|
||||||
|
|
||||||
origin: []const u8,
|
origin: []const u8,
|
||||||
|
|
||||||
@@ -224,8 +224,8 @@ pub const MessageEvent = struct {
|
|||||||
ports: []*MessagePort,
|
ports: []*MessagePort,
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
data: ?js.JsObject = null,
|
data: ?js.Object = null,
|
||||||
source: ?js.JsObject = null,
|
source: ?js.Object = null,
|
||||||
origin: []const u8 = "",
|
origin: []const u8 = "",
|
||||||
lastEventId: []const u8 = "",
|
lastEventId: []const u8 = "",
|
||||||
ports: []*MessagePort = &.{},
|
ports: []*MessagePort = &.{},
|
||||||
@@ -241,7 +241,7 @@ pub const MessageEvent = struct {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is like "constructor", but it assumes js.JsObjects have already been
|
// This is like "constructor", but it assumes js.Objects have already been
|
||||||
// persisted. Necessary because this `new MessageEvent()` can be called
|
// persisted. Necessary because this `new MessageEvent()` can be called
|
||||||
// directly from JS OR from a port.postMessage. In the latter case, data
|
// directly from JS OR from a port.postMessage. In the latter case, data
|
||||||
// may have already been persisted (as it might need to be queued);
|
// may have already been persisted (as it might need to be queued);
|
||||||
@@ -261,7 +261,7 @@ pub const MessageEvent = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_data(self: *const MessageEvent) !?js.JsObject {
|
pub fn get_data(self: *const MessageEvent) !?js.Object {
|
||||||
return self.data;
|
return self.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ pub const MessageEvent = struct {
|
|||||||
return self.origin;
|
return self.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_source(self: *const MessageEvent) ?js.JsObject {
|
pub fn get_source(self: *const MessageEvent) ?js.Object {
|
||||||
return self.source;
|
return self.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ pub const Document = struct {
|
|||||||
return &.{};
|
return &.{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !js.JsObject {
|
pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !js.Object {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
|
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
|
||||||
if (state.adopted_style_sheets) |obj| {
|
if (state.adopted_style_sheets) |obj| {
|
||||||
return obj;
|
return obj;
|
||||||
@@ -309,7 +309,7 @@ pub const Document = struct {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: js.JsObject, page: *Page) !void {
|
pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: js.Object, page: *Page) !void {
|
||||||
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
|
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
|
||||||
state.adopted_style_sheets = try sheets.persist();
|
state.adopted_style_sheets = try sheets.persist();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ pub const Element = struct {
|
|||||||
return sr;
|
return sr;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _animate(self: *parser.Element, effect: js.JsObject, opts: js.JsObject) !Animation {
|
pub fn _animate(self: *parser.Element, effect: js.Object, opts: js.Object) !Animation {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = opts;
|
_ = opts;
|
||||||
return Animation.constructor(effect, null);
|
return Animation.constructor(effect, null);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub const NodeIterator = struct {
|
|||||||
// One of the few cases where null and undefined resolve to different default.
|
// One of the few cases where null and undefined resolve to different default.
|
||||||
// We need the raw JsObject so that we can probe the tri state:
|
// We need the raw JsObject so that we can probe the tri state:
|
||||||
// null, undefined or i32.
|
// null, undefined or i32.
|
||||||
pub const WhatToShow = js.JsObject;
|
pub const WhatToShow = js.Object;
|
||||||
|
|
||||||
pub const NodeIteratorOpts = union(enum) {
|
pub const NodeIteratorOpts = union(enum) {
|
||||||
function: js.Function,
|
function: js.Function,
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const js = @import("../js/js.zig");
|
|||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
|
|
||||||
|
|
||||||
const NodeUnion = @import("node.zig").Union;
|
const NodeUnion = @import("node.zig").Union;
|
||||||
const Node = @import("node.zig").Node;
|
const Node = @import("node.zig").Node;
|
||||||
|
|
||||||
@@ -174,7 +173,7 @@ pub const NodeList = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
|
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
|
||||||
pub fn postAttach(self: *NodeList, js_this: js.JsThis) !void {
|
pub fn postAttach(self: *NodeList, js_this: js.This) !void {
|
||||||
const len = self.get_length();
|
const len = self.get_length();
|
||||||
for (0..len) |i| {
|
for (0..len) |i| {
|
||||||
const node = try self._item(@intCast(i)) orelse unreachable;
|
const node = try self._item(@intCast(i)) orelse unreachable;
|
||||||
|
|||||||
@@ -148,10 +148,10 @@ pub const PerformanceMark = struct {
|
|||||||
pub const prototype = *PerformanceEntry;
|
pub const prototype = *PerformanceEntry;
|
||||||
|
|
||||||
proto: PerformanceEntry,
|
proto: PerformanceEntry,
|
||||||
detail: ?js.JsObject,
|
detail: ?js.Object,
|
||||||
|
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
detail: ?js.JsObject = null,
|
detail: ?js.Object = null,
|
||||||
startTime: ?f64 = null,
|
startTime: ?f64 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ pub const PerformanceMark = struct {
|
|||||||
return .{ .proto = proto, .detail = detail };
|
return .{ .proto = proto, .detail = detail };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_detail(self: *const PerformanceMark) ?js.JsObject {
|
pub fn get_detail(self: *const PerformanceMark) ?js.Object {
|
||||||
return self.detail;
|
return self.detail;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub const ShadowRoot = struct {
|
|||||||
mode: Mode,
|
mode: Mode,
|
||||||
host: *parser.Element,
|
host: *parser.Element,
|
||||||
proto: *parser.DocumentFragment,
|
proto: *parser.DocumentFragment,
|
||||||
adopted_style_sheets: ?js.JsObject = null,
|
adopted_style_sheets: ?js.Object = null,
|
||||||
|
|
||||||
pub const Mode = enum {
|
pub const Mode = enum {
|
||||||
open,
|
open,
|
||||||
@@ -45,7 +45,7 @@ pub const ShadowRoot = struct {
|
|||||||
return Element.toInterface(self.host);
|
return Element.toInterface(self.host);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !js.JsObject {
|
pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !js.Object {
|
||||||
if (self.adopted_style_sheets) |obj| {
|
if (self.adopted_style_sheets) |obj| {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ pub const ShadowRoot = struct {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: js.JsObject) !void {
|
pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: js.Object) !void {
|
||||||
self.adopted_style_sheets = try sheets.persist();
|
self.adopted_style_sheets = try sheets.persist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ pub const DOMTokenList = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle thisArg
|
// TODO handle thisArg
|
||||||
pub fn _forEach(self: *parser.TokenList, cbk: js.Function, this_arg: js.JsObject) !void {
|
pub fn _forEach(self: *parser.TokenList, cbk: js.Function, this_arg: js.Object) !void {
|
||||||
var entries = _entries(self);
|
var entries = _entries(self);
|
||||||
while (try entries._next()) |entry| {
|
while (try entries._next()) |entry| {
|
||||||
var result: js.Function.Result = undefined;
|
var result: js.Function.Result = undefined;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub const TreeWalker = struct {
|
|||||||
// One of the few cases where null and undefined resolve to different default.
|
// One of the few cases where null and undefined resolve to different default.
|
||||||
// We need the raw JsObject so that we can probe the tri state:
|
// We need the raw JsObject so that we can probe the tri state:
|
||||||
// null, undefined or i32.
|
// null, undefined or i32.
|
||||||
pub const WhatToShow = js.JsObject;
|
pub const WhatToShow = js.Object;
|
||||||
|
|
||||||
pub const TreeWalkerOpts = union(enum) {
|
pub const TreeWalkerOpts = union(enum) {
|
||||||
function: js.Function,
|
function: js.Function,
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ pub const CustomEvent = struct {
|
|||||||
pub const union_make_copy = true;
|
pub const union_make_copy = true;
|
||||||
|
|
||||||
proto: parser.Event,
|
proto: parser.Event,
|
||||||
detail: ?js.JsObject,
|
detail: ?js.Object,
|
||||||
|
|
||||||
const CustomEventInit = struct {
|
const CustomEventInit = struct {
|
||||||
bubbles: bool = false,
|
bubbles: bool = false,
|
||||||
cancelable: bool = false,
|
cancelable: bool = false,
|
||||||
composed: bool = false,
|
composed: bool = false,
|
||||||
detail: ?js.JsObject = null,
|
detail: ?js.Object = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn constructor(event_type: []const u8, opts_: ?CustomEventInit) !CustomEvent {
|
pub fn constructor(event_type: []const u8, opts_: ?CustomEventInit) !CustomEvent {
|
||||||
@@ -54,7 +54,7 @@ pub const CustomEvent = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_detail(self: *CustomEvent) ?js.JsObject {
|
pub fn get_detail(self: *CustomEvent) ?js.Object {
|
||||||
return self.detail;
|
return self.detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ pub const CustomEvent = struct {
|
|||||||
event_type: []const u8,
|
event_type: []const u8,
|
||||||
can_bubble: bool,
|
can_bubble: bool,
|
||||||
cancelable: bool,
|
cancelable: bool,
|
||||||
maybe_detail: ?js.JsObject,
|
maybe_detail: ?js.Object,
|
||||||
) !void {
|
) !void {
|
||||||
// This function can only be called after the constructor has called.
|
// This function can only be called after the constructor has called.
|
||||||
// So we assume proto is initialized already by constructor.
|
// So we assume proto is initialized already by constructor.
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ pub const EventHandler = struct {
|
|||||||
|
|
||||||
pub const Listener = union(enum) {
|
pub const Listener = union(enum) {
|
||||||
function: js.Function,
|
function: js.Function,
|
||||||
object: js.JsObject,
|
object: js.Object,
|
||||||
|
|
||||||
pub fn callback(self: Listener, target: *parser.EventTarget) !?js.Function {
|
pub fn callback(self: Listener, target: *parser.EventTarget) !?js.Function {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub const HeadersInit = union(enum) {
|
|||||||
// Headers
|
// Headers
|
||||||
headers: *Headers,
|
headers: *Headers,
|
||||||
// Mappings
|
// Mappings
|
||||||
object: js.JsObject,
|
object: js.Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers {
|
pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers {
|
||||||
@@ -158,7 +158,7 @@ pub fn _entries(self: *const Headers) HeadersEntryIterable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _forEach(self: *Headers, callback_fn: js.Function, this_arg: ?js.JsObject) !void {
|
pub fn _forEach(self: *Headers, callback_fn: js.Function, this_arg: ?js.Object) !void {
|
||||||
var iter = self.headers.iterator();
|
var iter = self.headers.iterator();
|
||||||
|
|
||||||
const cb = if (this_arg) |this| try callback_fn.withThis(this) else callback_fn;
|
const cb = if (this_arg) |this| try callback_fn.withThis(this) else callback_fn;
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ pub fn _bytes(self: *Response, page: *Page) !js.Promise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolver = js.PromiseResolver{
|
const resolver = js.PromiseResolver{
|
||||||
.js_context = page.main_context,
|
.context = page.main_context,
|
||||||
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
|
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ fn _dispatchPopStateEvent(state: ?[]const u8, page: *Page) !void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _pushState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
pub fn _pushState(self: *History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
||||||
const arena = page.session.arena;
|
const arena = page.session.arena;
|
||||||
|
|
||||||
const json = try state.toJson(arena);
|
const json = try state.toJson(arena);
|
||||||
@@ -123,7 +123,7 @@ pub fn _pushState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]c
|
|||||||
self.current = self.stack.items.len - 1;
|
self.current = self.stack.items.len - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _replaceState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
pub fn _replaceState(self: *History, state: js.Object, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
||||||
const arena = page.session.arena;
|
const arena = page.session.arena;
|
||||||
|
|
||||||
if (self.current) |curr| {
|
if (self.current) |curr| {
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ pub const ErrorEvent = struct {
|
|||||||
filename: []const u8,
|
filename: []const u8,
|
||||||
lineno: i32,
|
lineno: i32,
|
||||||
colno: i32,
|
colno: i32,
|
||||||
@"error": ?js.JsObject,
|
@"error": ?js.Object,
|
||||||
|
|
||||||
const ErrorEventInit = struct {
|
const ErrorEventInit = struct {
|
||||||
message: []const u8 = "",
|
message: []const u8 = "",
|
||||||
filename: []const u8 = "",
|
filename: []const u8 = "",
|
||||||
lineno: i32 = 0,
|
lineno: i32 = 0,
|
||||||
colno: i32 = 0,
|
colno: i32 = 0,
|
||||||
@"error": ?js.JsObject = null,
|
@"error": ?js.Object = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn constructor(event_type: []const u8, opts: ?ErrorEventInit) !ErrorEvent {
|
pub fn constructor(event_type: []const u8, opts: ?ErrorEventInit) !ErrorEvent {
|
||||||
@@ -72,7 +72,7 @@ pub const ErrorEvent = struct {
|
|||||||
return self.colno;
|
return self.colno;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_error(self: *const ErrorEvent) js.UndefinedOr(js.JsObject) {
|
pub fn get_error(self: *const ErrorEvent) js.UndefinedOr(js.Object) {
|
||||||
if (self.@"error") |e| {
|
if (self.@"error") |e| {
|
||||||
return .{ .value = e };
|
return .{ .value = e };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,11 +268,11 @@ pub const Window = struct {
|
|||||||
_ = self.timers.remove(id);
|
_ = self.timers.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _setTimeout(self: *Window, cbk: js.Function, delay: ?u32, params: []js.JsObject, page: *Page) !u32 {
|
pub fn _setTimeout(self: *Window, cbk: js.Function, delay: ?u32, params: []js.Object, page: *Page) !u32 {
|
||||||
return self.createTimeout(cbk, delay, page, .{ .args = params, .name = "setTimeout" });
|
return self.createTimeout(cbk, delay, page, .{ .args = params, .name = "setTimeout" });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _setInterval(self: *Window, cbk: js.Function, delay: ?u32, params: []js.JsObject, page: *Page) !u32 {
|
pub fn _setInterval(self: *Window, cbk: js.Function, delay: ?u32, params: []js.Object, page: *Page) !u32 {
|
||||||
return self.createTimeout(cbk, delay, page, .{ .repeat = true, .args = params, .name = "setInterval" });
|
return self.createTimeout(cbk, delay, page, .{ .repeat = true, .args = params, .name = "setInterval" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ pub const Window = struct {
|
|||||||
|
|
||||||
const CreateTimeoutOpts = struct {
|
const CreateTimeoutOpts = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
args: []js.JsObject = &.{},
|
args: []js.Object = &.{},
|
||||||
repeat: bool = false,
|
repeat: bool = false,
|
||||||
animation_frame: bool = false,
|
animation_frame: bool = false,
|
||||||
low_priority: bool = false,
|
low_priority: bool = false,
|
||||||
@@ -345,9 +345,9 @@ pub const Window = struct {
|
|||||||
errdefer _ = self.timers.remove(timer_id);
|
errdefer _ = self.timers.remove(timer_id);
|
||||||
|
|
||||||
const args = opts.args;
|
const args = opts.args;
|
||||||
var persisted_args: []js.JsObject = &.{};
|
var persisted_args: []js.Object = &.{};
|
||||||
if (args.len > 0) {
|
if (args.len > 0) {
|
||||||
persisted_args = try page.arena.alloc(js.JsObject, args.len);
|
persisted_args = try page.arena.alloc(js.Object, args.len);
|
||||||
for (args, persisted_args) |a, *ca| {
|
for (args, persisted_args) |a, *ca| {
|
||||||
ca.* = try a.persist();
|
ca.* = try a.persist();
|
||||||
}
|
}
|
||||||
@@ -480,7 +480,7 @@ const TimerCallback = struct {
|
|||||||
|
|
||||||
window: *Window,
|
window: *Window,
|
||||||
|
|
||||||
args: []js.JsObject = &.{},
|
args: []js.Object = &.{},
|
||||||
|
|
||||||
fn run(ctx: *anyopaque) ?u32 {
|
fn run(ctx: *anyopaque) ?u32 {
|
||||||
const self: *TimerCallback = @ptrCast(@alignCast(ctx));
|
const self: *TimerCallback = @ptrCast(@alignCast(ctx));
|
||||||
|
|||||||
560
src/browser/js/Caller.zig
Normal file
560
src/browser/js/Caller.zig
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
const Page = @import("../page.zig").Page;
|
||||||
|
|
||||||
|
const types = @import("types.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const CALL_ARENA_RETAIN = 1024 * 16;
|
||||||
|
|
||||||
|
// Responsible for calling Zig functions from JS invocations. This could
|
||||||
|
// probably just contained in ExecutionWorld, but having this specific logic, which
|
||||||
|
// is somewhat repetitive between constructors, functions, getters, etc contained
|
||||||
|
// here does feel like it makes it cleaner.
|
||||||
|
const Caller = @This();
|
||||||
|
context: *Context,
|
||||||
|
v8_context: v8.Context,
|
||||||
|
isolate: v8.Isolate,
|
||||||
|
call_arena: Allocator,
|
||||||
|
|
||||||
|
// info is a v8.PropertyCallbackInfo or a v8.FunctionCallback
|
||||||
|
// All we really want from it is the isolate.
|
||||||
|
// executor = Isolate -> getCurrentContext -> getEmbedderData()
|
||||||
|
pub fn init(info: anytype) Caller {
|
||||||
|
const isolate = info.getIsolate();
|
||||||
|
const v8_context = isolate.getCurrentContext();
|
||||||
|
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||||
|
|
||||||
|
context.call_depth += 1;
|
||||||
|
return .{
|
||||||
|
.context = context,
|
||||||
|
.isolate = isolate,
|
||||||
|
.v8_context = v8_context,
|
||||||
|
.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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set this _after_ we've executed the above code, so that if the
|
||||||
|
// above code executes any callbacks, they aren't being executed
|
||||||
|
// at scope 0, which would be wrong.
|
||||||
|
context.call_depth = call_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn constructor(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
|
||||||
|
const args = try self.getArgs(Struct, named_function, 0, info);
|
||||||
|
const res = @call(.auto, Struct.constructor, args);
|
||||||
|
|
||||||
|
const ReturnType = @typeInfo(@TypeOf(Struct.constructor)).@"fn".return_type orelse {
|
||||||
|
@compileError(@typeName(Struct) ++ " has a constructor without a return type");
|
||||||
|
};
|
||||||
|
|
||||||
|
const this = info.getThis();
|
||||||
|
if (@typeInfo(ReturnType) == .error_union) {
|
||||||
|
const non_error_res = res catch |err| return err;
|
||||||
|
_ = try Context.mapZigInstanceToJs(self.v8_context, this, non_error_res);
|
||||||
|
} else {
|
||||||
|
_ = try Context.mapZigInstanceToJs(self.v8_context, this, res);
|
||||||
|
}
|
||||||
|
info.getReturnValue().set(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn method(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
|
||||||
|
if (comptime isSelfReceiver(Struct, named_function) == false) {
|
||||||
|
return self.function(Struct, named_function, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = self.context;
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
var args = try self.getArgs(Struct, named_function, 1, info);
|
||||||
|
const zig_instance = try context.typeTaggedAnyOpaque(named_function, *types.Receiver(Struct), info.getThis());
|
||||||
|
|
||||||
|
// inject 'self' as the first parameter
|
||||||
|
@field(args, "0") = zig_instance;
|
||||||
|
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
info.getReturnValue().set(try context.zigValueToJs(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn function(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, info: v8.FunctionCallbackInfo) !void {
|
||||||
|
const context = self.context;
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
const args = try self.getArgs(Struct, named_function, 0, info);
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
info.getReturnValue().set(try context.zigValueToJs(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getIndex(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, idx: u32, info: v8.PropertyCallbackInfo) !u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
const IndexedGet = @TypeOf(func);
|
||||||
|
if (@typeInfo(IndexedGet).@"fn".return_type == null) {
|
||||||
|
@compileError(named_function.full_name ++ " must have a return type");
|
||||||
|
}
|
||||||
|
|
||||||
|
var has_value = true;
|
||||||
|
|
||||||
|
var args: ParamterTypes(IndexedGet) = undefined;
|
||||||
|
const arg_fields = @typeInfo(@TypeOf(args)).@"struct".fields;
|
||||||
|
switch (arg_fields.len) {
|
||||||
|
0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 and *bool parameter"),
|
||||||
|
3, 4 => {
|
||||||
|
const zig_instance = try context.typeTaggedAnyOpaque(named_function, *types.Receiver(Struct), info.getThis());
|
||||||
|
comptime assertSelfReceiver(Struct, named_function);
|
||||||
|
@field(args, "0") = zig_instance;
|
||||||
|
@field(args, "1") = idx;
|
||||||
|
@field(args, "2") = &has_value;
|
||||||
|
if (comptime arg_fields.len == 4) {
|
||||||
|
comptime assertIsPageArg(Struct, named_function, 3);
|
||||||
|
@field(args, "3") = context.page;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError(named_function.full_name ++ " has too many parmaters"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
if (has_value == false) {
|
||||||
|
return v8.Intercepted.No;
|
||||||
|
}
|
||||||
|
info.getReturnValue().set(try context.zigValueToJs(res));
|
||||||
|
return v8.Intercepted.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNamedIndex(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, name: v8.Name, info: v8.PropertyCallbackInfo) !u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
comptime assertSelfReceiver(Struct, named_function);
|
||||||
|
|
||||||
|
var has_value = true;
|
||||||
|
var args = try self.getArgs(Struct, named_function, 3, info);
|
||||||
|
const zig_instance = try context.typeTaggedAnyOpaque(named_function, *types.Receiver(Struct), info.getThis());
|
||||||
|
@field(args, "0") = zig_instance;
|
||||||
|
@field(args, "1") = try self.nameToString(name);
|
||||||
|
@field(args, "2") = &has_value;
|
||||||
|
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
if (has_value == false) {
|
||||||
|
return v8.Intercepted.No;
|
||||||
|
}
|
||||||
|
info.getReturnValue().set(try self.context.zigValueToJs(res));
|
||||||
|
return v8.Intercepted.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setNamedIndex(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, name: v8.Name, js_value: v8.Value, info: v8.PropertyCallbackInfo) !u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
comptime assertSelfReceiver(Struct, named_function);
|
||||||
|
|
||||||
|
var has_value = true;
|
||||||
|
var args = try self.getArgs(Struct, named_function, 4, info);
|
||||||
|
const zig_instance = try context.typeTaggedAnyOpaque(named_function, *types.Receiver(Struct), info.getThis());
|
||||||
|
@field(args, "0") = zig_instance;
|
||||||
|
@field(args, "1") = try self.nameToString(name);
|
||||||
|
@field(args, "2") = try context.jsValueToZig(named_function, @TypeOf(@field(args, "2")), js_value);
|
||||||
|
@field(args, "3") = &has_value;
|
||||||
|
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
return namedSetOrDeleteCall(res, has_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteNamedIndex(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, name: v8.Name, info: v8.PropertyCallbackInfo) !u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
comptime assertSelfReceiver(Struct, named_function);
|
||||||
|
|
||||||
|
var has_value = true;
|
||||||
|
var args = try self.getArgs(Struct, named_function, 3, info);
|
||||||
|
const zig_instance = try context.typeTaggedAnyOpaque(named_function, *types.Receiver(Struct), info.getThis());
|
||||||
|
@field(args, "0") = zig_instance;
|
||||||
|
@field(args, "1") = try self.nameToString(name);
|
||||||
|
@field(args, "2") = &has_value;
|
||||||
|
|
||||||
|
const res = @call(.auto, func, args);
|
||||||
|
return namedSetOrDeleteCall(res, has_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn namedSetOrDeleteCall(res: anytype, has_value: bool) !u8 {
|
||||||
|
if (@typeInfo(@TypeOf(res)) == .error_union) {
|
||||||
|
_ = try res;
|
||||||
|
}
|
||||||
|
if (has_value == false) {
|
||||||
|
return v8.Intercepted.No;
|
||||||
|
}
|
||||||
|
return v8.Intercepted.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nameToString(self: *Caller, name: v8.Name) ![]const u8 {
|
||||||
|
return js.valueToString(self.call_arena, .{ .handle = name.handle }, self.isolate, self.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isSelfReceiver(comptime Struct: type, comptime named_function: NamedFunction) bool {
|
||||||
|
return checkSelfReceiver(Struct, named_function, false);
|
||||||
|
}
|
||||||
|
fn assertSelfReceiver(comptime Struct: type, comptime named_function: NamedFunction) void {
|
||||||
|
_ = checkSelfReceiver(Struct, named_function, true);
|
||||||
|
}
|
||||||
|
fn checkSelfReceiver(comptime Struct: type, comptime named_function: NamedFunction, comptime fail: bool) bool {
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
const params = @typeInfo(@TypeOf(func)).@"fn".params;
|
||||||
|
if (params.len == 0) {
|
||||||
|
if (fail) {
|
||||||
|
@compileError(named_function.full_name ++ " must have a self parameter");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const R = types.Receiver(Struct);
|
||||||
|
const first_param = params[0].type.?;
|
||||||
|
if (first_param != *R and first_param != *const R) {
|
||||||
|
if (fail) {
|
||||||
|
@compileError(std.fmt.comptimePrint("The first parameter to {s} must be a *{s} or *const {s}. Got: {s}", .{
|
||||||
|
named_function.full_name,
|
||||||
|
@typeName(R),
|
||||||
|
@typeName(R),
|
||||||
|
@typeName(first_param),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assertIsPageArg(comptime Struct: type, comptime named_function: NamedFunction, index: comptime_int) void {
|
||||||
|
const F = @TypeOf(@field(Struct, named_function.name));
|
||||||
|
const param = @typeInfo(F).@"fn".params[index].type.?;
|
||||||
|
if (isPage(param)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@compileError(std.fmt.comptimePrint("The {d} parameter to {s} must be a *Page or *const Page. Got: {s}", .{ index, named_function.full_name, @typeName(param) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handleError(self: *Caller, comptime Struct: type, comptime named_function: NamedFunction, err: anyerror, info: anytype) void {
|
||||||
|
const isolate = self.isolate;
|
||||||
|
|
||||||
|
if (comptime @import("builtin").mode == .Debug and @hasDecl(@TypeOf(info), "length")) {
|
||||||
|
if (log.enabled(.js, .warn)) {
|
||||||
|
logFunctionCallError(self.call_arena, self.isolate, self.v8_context, err, named_function.full_name, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var js_err: ?v8.Value = switch (err) {
|
||||||
|
error.InvalidArgument => createTypeException(isolate, "invalid argument"),
|
||||||
|
error.OutOfMemory => js._createException(isolate, "out of memory"),
|
||||||
|
error.IllegalConstructor => js._createException(isolate, "Illegal Contructor"),
|
||||||
|
else => blk: {
|
||||||
|
const func = @field(Struct, named_function.name);
|
||||||
|
const return_type = @typeInfo(@TypeOf(func)).@"fn".return_type orelse {
|
||||||
|
// void return type;
|
||||||
|
break :blk null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (@typeInfo(return_type) != .error_union) {
|
||||||
|
// type defines a custom exception, but this function should
|
||||||
|
// not fail. We failed somewhere inside of js.zig and
|
||||||
|
// should return the error as-is, since it isn't related
|
||||||
|
// to our Struct
|
||||||
|
break :blk null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const function_error_set = @typeInfo(return_type).error_union.error_set;
|
||||||
|
|
||||||
|
const E = comptime getCustomException(Struct) orelse break :blk null;
|
||||||
|
if (function_error_set == E or isErrorSetException(E, err)) {
|
||||||
|
const custom_exception = E.init(self.call_arena, err, named_function.js_name) catch |init_err| {
|
||||||
|
switch (init_err) {
|
||||||
|
// if a custom exceptions' init wants to return a
|
||||||
|
// different error, we need to think about how to
|
||||||
|
// handle that failure.
|
||||||
|
error.OutOfMemory => break :blk js._createException(isolate, "out of memory"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// ughh..how to handle an error here?
|
||||||
|
break :blk self.context.zigValueToJs(custom_exception) catch js._createException(isolate, "internal error");
|
||||||
|
}
|
||||||
|
// this error isn't part of a custom exception
|
||||||
|
break :blk null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (js_err == null) {
|
||||||
|
js_err = js._createException(isolate, @errorName(err));
|
||||||
|
}
|
||||||
|
const js_exception = isolate.throwException(js_err.?);
|
||||||
|
info.getReturnValue().setValueHandle(js_exception.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk the prototype chain to see if a type declares a custom Exception
|
||||||
|
fn getCustomException(comptime Struct: type) ?type {
|
||||||
|
var S = Struct;
|
||||||
|
while (true) {
|
||||||
|
if (@hasDecl(S, "Exception")) {
|
||||||
|
return S.Exception;
|
||||||
|
}
|
||||||
|
if (@hasDecl(S, "prototype") == false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// long ago, we validated that every prototype declaration
|
||||||
|
// is a pointer.
|
||||||
|
S = @typeInfo(S.prototype).pointer.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does the error we want to return belong to the custom exeception's ErrorSet
|
||||||
|
fn isErrorSetException(comptime E: type, err: anytype) bool {
|
||||||
|
const Entry = std.meta.Tuple(&.{ []const u8, void });
|
||||||
|
|
||||||
|
const error_set = @typeInfo(E.ErrorSet).error_set.?;
|
||||||
|
const entries = comptime blk: {
|
||||||
|
var kv: [error_set.len]Entry = undefined;
|
||||||
|
for (error_set, 0..) |e, i| {
|
||||||
|
kv[i] = .{ e.name, {} };
|
||||||
|
}
|
||||||
|
break :blk kv;
|
||||||
|
};
|
||||||
|
const lookup = std.StaticStringMap(void).initComptime(entries);
|
||||||
|
return lookup.has(@errorName(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Struct: type, comptime named_function: NamedFunction, comptime offset: usize, info: anytype) !ParamterTypes(@TypeOf(@field(Struct, named_function.name))) {
|
||||||
|
const context = self.context;
|
||||||
|
const F = @TypeOf(@field(Struct, named_function.name));
|
||||||
|
var args: ParamterTypes(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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last parameter is a special JsThis, set it, and exclude it
|
||||||
|
// from our params slice, because we don't want to bind it to
|
||||||
|
// a JS argument
|
||||||
|
if (comptime params[params.len - 1].type.? == js.This) {
|
||||||
|
@field(args, tupleFieldName(params.len - 1 + offset)) = .{ .obj = .{
|
||||||
|
.context = context,
|
||||||
|
.js_obj = info.getThis(),
|
||||||
|
} };
|
||||||
|
|
||||||
|
// AND the 2nd last parameter is state
|
||||||
|
if (params.len > 1 and comptime isPage(params[params.len - 2].type.?)) {
|
||||||
|
@field(args, tupleFieldName(params.len - 2 + offset)) = self.context.page;
|
||||||
|
break :blk params[0 .. params.len - 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
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(named_function, slice_type, js_value);
|
||||||
|
}
|
||||||
|
@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): " ++ named_function.full_name);
|
||||||
|
} else if (comptime param.type.? == js.This) {
|
||||||
|
@compileError("JsThis must be the last parameter: " ++ named_function.full_name);
|
||||||
|
} 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(named_function, param.type.?, js_value) catch {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the function name, or more precisely, the "Struct.function" for
|
||||||
|
// displaying helpful @compileError.
|
||||||
|
// However, there's no way to get the name from a std.Builtin.Fn, so we create
|
||||||
|
// a NamedFunction as part of our binding, and pass it around incase we need
|
||||||
|
// to display an error
|
||||||
|
pub const NamedFunction = struct {
|
||||||
|
name: []const u8,
|
||||||
|
js_name: []const u8,
|
||||||
|
full_name: []const u8,
|
||||||
|
|
||||||
|
pub fn init(comptime Struct: type, comptime name: []const u8) NamedFunction {
|
||||||
|
return .{
|
||||||
|
.name = name,
|
||||||
|
.js_name = if (name[0] == '_') name[1..] else name,
|
||||||
|
.full_name = @typeName(Struct) ++ "." ++ name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Takes a function, and returns a tuple for its argument. Used when we
|
||||||
|
// @call a function
|
||||||
|
fn ParamterTypes(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(arena: Allocator, isolate: v8.Isolate, context: v8.Context, err: anyerror, function_name: []const u8, info: v8.FunctionCallbackInfo) void {
|
||||||
|
const args_dump = serializeFunctionArgs(arena, isolate, context, info) catch "failed to serialize args";
|
||||||
|
log.info(.js, "function call error", .{
|
||||||
|
.name = function_name,
|
||||||
|
.err = err,
|
||||||
|
.args = args_dump,
|
||||||
|
.stack = Context.stackForLogs(arena, isolate) catch |err1| @errorName(err1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn serializeFunctionArgs(arena: Allocator, isolate: v8.Isolate, context: v8.Context, info: v8.FunctionCallbackInfo) ![]const u8 {
|
||||||
|
const separator = log.separator();
|
||||||
|
const js_parameter_count = info.length();
|
||||||
|
|
||||||
|
var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
|
for (0..js_parameter_count) |i| {
|
||||||
|
const js_value = info.getArg(@intCast(i));
|
||||||
|
const value_string = try js.valueToDetailString(arena, js_value, isolate, context);
|
||||||
|
const value_type = try js.stringToZig(arena, try js_value.typeOf(isolate), isolate);
|
||||||
|
try std.fmt.format(arr.writer(arena), "{s}{d}: {s} ({s})", .{
|
||||||
|
separator,
|
||||||
|
i + 1,
|
||||||
|
value_string,
|
||||||
|
value_type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return arr.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createTypeException(isolate: v8.Isolate, msg: []const u8) v8.Value {
|
||||||
|
return v8.Exception.initTypeError(v8.String.initUtf8(isolate, msg));
|
||||||
|
}
|
||||||
1731
src/browser/js/Context.zig
Normal file
1731
src/browser/js/Context.zig
Normal file
File diff suppressed because it is too large
Load Diff
549
src/browser/js/Env.zig
Normal file
549
src/browser/js/Env.zig
Normal file
@@ -0,0 +1,549 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
|
||||||
|
const types = @import("types.zig");
|
||||||
|
const Types = types.Types;
|
||||||
|
const Caller = @import("Caller.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const Platform = @import("Platform.zig");
|
||||||
|
const Inspector = @import("Inspector.zig");
|
||||||
|
const ExecutionWorld = @import("ExecutionWorld.zig");
|
||||||
|
const NamedFunction = Caller.NamedFunction;
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
// The Env maps to a V8 isolate, which represents a isolated sandbox for
|
||||||
|
// executing JavaScript. The Env is where we'll define our V8 <-> Zig bindings,
|
||||||
|
// and it's where we'll start ExecutionWorlds, which actually execute JavaScript.
|
||||||
|
// The `S` parameter is arbitrary state. When we start an ExecutionWorld, an instance
|
||||||
|
// of S must be given. This instance is available to any Zig binding.
|
||||||
|
// The `types` parameter is a tuple of Zig structures we want to bind to V8.
|
||||||
|
const Env = @This();
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
platform: *const Platform,
|
||||||
|
|
||||||
|
// the global isolate
|
||||||
|
isolate: v8.Isolate,
|
||||||
|
|
||||||
|
// just kept around because we need to free it on deinit
|
||||||
|
isolate_params: *v8.CreateParams,
|
||||||
|
|
||||||
|
// Given a type, we can lookup its index in TYPE_LOOKUP and then have
|
||||||
|
// access to its TunctionTemplate (the thing we need to create an instance
|
||||||
|
// of it)
|
||||||
|
// I.e.:
|
||||||
|
// const index = @field(TYPE_LOOKUP, @typeName(type_name))
|
||||||
|
// const template = templates[index];
|
||||||
|
templates: [Types.len]v8.FunctionTemplate,
|
||||||
|
|
||||||
|
// Given a type index (retrieved via the TYPE_LOOKUP), we can retrieve
|
||||||
|
// the index of its prototype. Types without a prototype have their own
|
||||||
|
// index.
|
||||||
|
prototype_lookup: [Types.len]u16,
|
||||||
|
|
||||||
|
meta_lookup: [Types.len]types.Meta,
|
||||||
|
|
||||||
|
context_id: usize,
|
||||||
|
|
||||||
|
const Opts = struct {};
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator, platform: *const Platform, _: Opts) !*Env {
|
||||||
|
// var params = v8.initCreateParams();
|
||||||
|
var params = try allocator.create(v8.CreateParams);
|
||||||
|
errdefer allocator.destroy(params);
|
||||||
|
|
||||||
|
v8.c.v8__Isolate__CreateParams__CONSTRUCT(params);
|
||||||
|
|
||||||
|
params.array_buffer_allocator = v8.createDefaultArrayBufferAllocator();
|
||||||
|
errdefer v8.destroyArrayBufferAllocator(params.array_buffer_allocator.?);
|
||||||
|
|
||||||
|
var isolate = v8.Isolate.init(params);
|
||||||
|
errdefer isolate.deinit();
|
||||||
|
|
||||||
|
// This is the callback that runs whenever a module is dynamically imported.
|
||||||
|
isolate.setHostImportModuleDynamicallyCallback(Context.dynamicModuleCallback);
|
||||||
|
isolate.setPromiseRejectCallback(promiseRejectCallback);
|
||||||
|
isolate.setMicrotasksPolicy(v8.c.kExplicit);
|
||||||
|
|
||||||
|
isolate.enter();
|
||||||
|
errdefer isolate.exit();
|
||||||
|
|
||||||
|
isolate.setHostInitializeImportMetaObjectCallback(struct {
|
||||||
|
fn callback(c_context: ?*v8.C_Context, c_module: ?*v8.C_Module, c_meta: ?*v8.C_Value) callconv(.c) void {
|
||||||
|
const v8_context = v8.Context{ .handle = c_context.? };
|
||||||
|
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||||
|
context.initializeImportMeta(v8.Module{ .handle = c_module.? }, v8.Object{ .handle = c_meta.? }) catch |err| {
|
||||||
|
log.err(.js, "import meta", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
|
||||||
|
var temp_scope: v8.HandleScope = undefined;
|
||||||
|
v8.HandleScope.init(&temp_scope, isolate);
|
||||||
|
defer temp_scope.deinit();
|
||||||
|
|
||||||
|
const env = try allocator.create(Env);
|
||||||
|
errdefer allocator.destroy(env);
|
||||||
|
|
||||||
|
env.* = .{
|
||||||
|
.context_id = 0,
|
||||||
|
.platform = platform,
|
||||||
|
.isolate = isolate,
|
||||||
|
.templates = undefined,
|
||||||
|
.allocator = allocator,
|
||||||
|
.isolate_params = params,
|
||||||
|
.meta_lookup = undefined,
|
||||||
|
.prototype_lookup = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate our templates lookup. generateClass creates the
|
||||||
|
// v8.FunctionTemplate, which we store in our env.templates.
|
||||||
|
// The ordering doesn't matter. What matters is that, given a type
|
||||||
|
// we can get its index via: @field(types.LOOKUP, type_name)
|
||||||
|
const templates = &env.templates;
|
||||||
|
inline for (Types, 0..) |s, i| {
|
||||||
|
@setEvalBranchQuota(10_000);
|
||||||
|
templates[i] = v8.Persistent(v8.FunctionTemplate).init(isolate, generateClass(s.defaultValue().?, isolate)).castToFunctionTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Above, we've created all our our FunctionTemplates. Now that we
|
||||||
|
// have them all, we can hook up the prototypes.
|
||||||
|
const meta_lookup = &env.meta_lookup;
|
||||||
|
inline for (Types, 0..) |s, i| {
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
if (@hasDecl(Struct, "prototype")) {
|
||||||
|
const TI = @typeInfo(Struct.prototype);
|
||||||
|
const proto_name = @typeName(types.Receiver(TI.pointer.child));
|
||||||
|
if (@hasField(types.Lookup, proto_name) == false) {
|
||||||
|
@compileError(std.fmt.comptimePrint("Prototype '{s}' for '{s}' is undefined", .{ proto_name, @typeName(Struct) }));
|
||||||
|
}
|
||||||
|
// Hey, look! This is our first real usage of the types.LOOKUP.
|
||||||
|
// Just like we said above, given a type, we can get its
|
||||||
|
// template index.
|
||||||
|
|
||||||
|
const proto_index = @field(types.LOOKUP, proto_name);
|
||||||
|
templates[i].inherit(templates[proto_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// while we're here, let's populate our meta lookup
|
||||||
|
const subtype: ?types.Sub = if (@hasDecl(Struct, "subtype")) Struct.subtype else null;
|
||||||
|
|
||||||
|
const proto_offset = comptime blk: {
|
||||||
|
if (!@hasField(Struct, "proto")) {
|
||||||
|
break :blk 0;
|
||||||
|
}
|
||||||
|
const proto_info = std.meta.fieldInfo(Struct, .proto);
|
||||||
|
if (@typeInfo(proto_info.type) == .pointer) {
|
||||||
|
// we store the offset as a negative, to so that,
|
||||||
|
// when we reverse this, we know that it's
|
||||||
|
// behind a pointer that we need to resolve.
|
||||||
|
break :blk -@offsetOf(Struct, "proto");
|
||||||
|
}
|
||||||
|
break :blk @offsetOf(Struct, "proto");
|
||||||
|
};
|
||||||
|
|
||||||
|
meta_lookup[i] = .{
|
||||||
|
.index = i,
|
||||||
|
.subtype = subtype,
|
||||||
|
.proto_offset = proto_offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Env) void {
|
||||||
|
self.isolate.exit();
|
||||||
|
self.isolate.deinit();
|
||||||
|
v8.destroyArrayBufferAllocator(self.isolate_params.array_buffer_allocator.?);
|
||||||
|
self.allocator.destroy(self.isolate_params);
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newInspector(self: *Env, arena: Allocator, ctx: anytype) !Inspector {
|
||||||
|
return Inspector.init(arena, self.isolate, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runMicrotasks(self: *const Env) void {
|
||||||
|
self.isolate.performMicrotasksCheckpoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pumpMessageLoop(self: *const Env) bool {
|
||||||
|
return self.platform.inner.pumpMessageLoop(self.isolate, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runIdleTasks(self: *const Env) void {
|
||||||
|
return self.platform.inner.runIdleTasks(self.isolate, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newExecutionWorld(self: *Env) !ExecutionWorld {
|
||||||
|
return .{
|
||||||
|
.env = self,
|
||||||
|
.context = null,
|
||||||
|
.call_arena = ArenaAllocator.init(self.allocator),
|
||||||
|
.context_arena = ArenaAllocator.init(self.allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// V8 doesn't immediately free memory associated with
|
||||||
|
// a Context, it's managed by the garbage collector. We use the
|
||||||
|
// `lowMemoryNotification` call on the isolate to encourage v8 to free
|
||||||
|
// any contexts which have been freed.
|
||||||
|
pub fn lowMemoryNotification(self: *Env) void {
|
||||||
|
var handle_scope: v8.HandleScope = undefined;
|
||||||
|
v8.HandleScope.init(&handle_scope, self.isolate);
|
||||||
|
defer handle_scope.deinit();
|
||||||
|
self.isolate.lowMemoryNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dumpMemoryStats(self: *Env) void {
|
||||||
|
const stats = self.isolate.getHeapStatistics();
|
||||||
|
std.debug.print(
|
||||||
|
\\ Total Heap Size: {d}
|
||||||
|
\\ Total Heap Size Executable: {d}
|
||||||
|
\\ Total Physical Size: {d}
|
||||||
|
\\ Total Available Size: {d}
|
||||||
|
\\ Used Heap Size: {d}
|
||||||
|
\\ Heap Size Limit: {d}
|
||||||
|
\\ Malloced Memory: {d}
|
||||||
|
\\ External Memory: {d}
|
||||||
|
\\ Peak Malloced Memory: {d}
|
||||||
|
\\ Number Of Native Contexts: {d}
|
||||||
|
\\ Number Of Detached Contexts: {d}
|
||||||
|
\\ Total Global Handles Size: {d}
|
||||||
|
\\ Used Global Handles Size: {d}
|
||||||
|
\\ Zap Garbage: {any}
|
||||||
|
\\
|
||||||
|
, .{ stats.total_heap_size, stats.total_heap_size_executable, stats.total_physical_size, stats.total_available_size, stats.used_heap_size, stats.heap_size_limit, stats.malloced_memory, stats.external_memory, stats.peak_malloced_memory, stats.number_of_native_contexts, stats.number_of_detached_contexts, stats.total_global_handles_size, stats.used_global_handles_size, stats.does_zap_garbage });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
|
||||||
|
const msg = v8.PromiseRejectMessage.initFromC(v8_msg);
|
||||||
|
const isolate = msg.getPromise().toObject().getIsolate();
|
||||||
|
const v8_context = isolate.getCurrentContext();
|
||||||
|
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||||
|
|
||||||
|
const value =
|
||||||
|
if (msg.getValue()) |v8_value| js.valueToString(context.call_arena, v8_value, isolate, v8_context) catch |err| @errorName(err) else "no value";
|
||||||
|
|
||||||
|
log.debug(.js, "unhandled rejection", .{ .value = value });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give it a Zig struct, get back a v8.FunctionTemplate.
|
||||||
|
// The FunctionTemplate is a bit like a struct container - it's where
|
||||||
|
// we'll attach functions/getters/setters and where we'll "inherit" a
|
||||||
|
// prototype type (if there is any)
|
||||||
|
fn generateClass(comptime Struct: type, isolate: v8.Isolate) v8.FunctionTemplate {
|
||||||
|
const template = generateConstructor(Struct, isolate);
|
||||||
|
attachClass(Struct, isolate, template);
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normally this is called from generateClass. Where generateClass creates
|
||||||
|
// the constructor (hence, the FunctionTemplate), attachClass adds all
|
||||||
|
// of its functions, getters, setters, ...
|
||||||
|
// But it's extracted from generateClass because we also have 1 global
|
||||||
|
// object (i.e. the Window), which gets attached not only to the Window
|
||||||
|
// constructor/FunctionTemplate as normal, but also through the default
|
||||||
|
// FunctionTemplate of the isolate (in createContext)
|
||||||
|
pub fn attachClass(comptime Struct: type, isolate: v8.Isolate, template: v8.FunctionTemplate) void {
|
||||||
|
const template_proto = template.getPrototypeTemplate();
|
||||||
|
inline for (@typeInfo(Struct).@"struct".decls) |declaration| {
|
||||||
|
const name = declaration.name;
|
||||||
|
if (comptime name[0] == '_') {
|
||||||
|
switch (@typeInfo(@TypeOf(@field(Struct, name)))) {
|
||||||
|
.@"fn" => generateMethod(Struct, name, isolate, template_proto),
|
||||||
|
else => |ti| if (!comptime js.isComplexAttributeType(ti)) {
|
||||||
|
generateAttribute(Struct, name, isolate, template, template_proto);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if (comptime std.mem.startsWith(u8, name, "get_")) {
|
||||||
|
generateProperty(Struct, name[4..], isolate, template_proto);
|
||||||
|
} else if (comptime std.mem.startsWith(u8, name, "static_")) {
|
||||||
|
generateFunction(Struct, name[7..], isolate, template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(Struct, "get_symbol_toStringTag") == false) {
|
||||||
|
// If this WAS defined, then we would have created it in generateProperty.
|
||||||
|
// But if it isn't, we create a default one
|
||||||
|
const string_tag_callback = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||||
|
fn stringTag(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
const class_name = v8.String.initUtf8(info.getIsolate(), comptime js.classNameForStruct(Struct));
|
||||||
|
info.getReturnValue().set(class_name);
|
||||||
|
}
|
||||||
|
}.stringTag);
|
||||||
|
const key = v8.Symbol.getToStringTag(isolate).toName();
|
||||||
|
template_proto.setAccessorGetter(key, string_tag_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateIndexer(Struct, template_proto);
|
||||||
|
generateNamedIndexer(Struct, template.getInstanceTemplate());
|
||||||
|
generateUndetectable(Struct, template.getInstanceTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if a struct doesn't have a `constructor` function, we still
|
||||||
|
// `generateConstructor`, because this is how we create our
|
||||||
|
// FunctionTemplate. Such classes exist, but they can't be instantiated
|
||||||
|
// via `new ClassName()` - but they could, for example, be created in
|
||||||
|
// Zig and returned from a function call, which is why we need the
|
||||||
|
// FunctionTemplate.
|
||||||
|
fn generateConstructor(comptime Struct: type, isolate: v8.Isolate) v8.FunctionTemplate {
|
||||||
|
const template = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||||
|
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
// See comment above. We generateConstructor on all types
|
||||||
|
// in order to create the FunctionTemplate, but there might
|
||||||
|
// not be an actual "constructor" function. So if someone
|
||||||
|
// does `new ClassName()` where ClassName doesn't have
|
||||||
|
// a constructor function, we'll return an error.
|
||||||
|
if (@hasDecl(Struct, "constructor") == false) {
|
||||||
|
const iso = caller.isolate;
|
||||||
|
log.warn(.js, "Illegal constructor call", .{ .name = @typeName(Struct) });
|
||||||
|
const js_exception = iso.throwException(js._createException(iso, "Illegal Constructor"));
|
||||||
|
info.getReturnValue().set(js_exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe to call now, because if Struct.constructor didn't
|
||||||
|
// exist, the above if block would have returned.
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "constructor");
|
||||||
|
caller.constructor(Struct, named_function, info) catch |err| {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
|
||||||
|
if (comptime types.isEmpty(types.Receiver(Struct)) == false) {
|
||||||
|
// If the struct is empty, we won't store a Zig reference inside
|
||||||
|
// the JS object, so we don't need to set the internal field count
|
||||||
|
template.getInstanceTemplate().setInternalFieldCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const class_name = v8.String.initUtf8(isolate, comptime js.classNameForStruct(Struct));
|
||||||
|
template.setClassName(class_name);
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateMethod(comptime Struct: type, comptime name: []const u8, isolate: v8.Isolate, template_proto: v8.ObjectTemplate) void {
|
||||||
|
var js_name: v8.Name = undefined;
|
||||||
|
if (comptime std.mem.eql(u8, name, "_symbol_iterator")) {
|
||||||
|
js_name = v8.Symbol.getIterator(isolate).toName();
|
||||||
|
} else {
|
||||||
|
js_name = v8.String.initUtf8(isolate, name[1..]).toName();
|
||||||
|
}
|
||||||
|
const function_template = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||||
|
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, name);
|
||||||
|
caller.method(Struct, named_function, info) catch |err| {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
template_proto.set(js_name, function_template, v8.PropertyAttribute.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateFunction(comptime Struct: type, comptime name: []const u8, isolate: v8.Isolate, template: v8.FunctionTemplate) void {
|
||||||
|
const js_name = v8.String.initUtf8(isolate, name).toName();
|
||||||
|
const function_template = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||||
|
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "static_" ++ name);
|
||||||
|
caller.function(Struct, named_function, info) catch |err| {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
template.set(js_name, function_template, v8.PropertyAttribute.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateAttribute(comptime Struct: type, comptime name: []const u8, isolate: v8.Isolate, template: v8.FunctionTemplate, template_proto: v8.ObjectTemplate) void {
|
||||||
|
const zig_value = @field(Struct, name);
|
||||||
|
const js_value = js.simpleZigValueToJs(isolate, zig_value, true);
|
||||||
|
|
||||||
|
const js_name = v8.String.initUtf8(isolate, name[1..]).toName();
|
||||||
|
|
||||||
|
// apply it both to the type itself
|
||||||
|
template.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete);
|
||||||
|
|
||||||
|
// and to instances of the type
|
||||||
|
template_proto.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateProperty(comptime Struct: type, comptime name: []const u8, isolate: v8.Isolate, template_proto: v8.ObjectTemplate) void {
|
||||||
|
var js_name: v8.Name = undefined;
|
||||||
|
if (comptime std.mem.eql(u8, name, "symbol_toStringTag")) {
|
||||||
|
js_name = v8.Symbol.getToStringTag(isolate).toName();
|
||||||
|
} else {
|
||||||
|
js_name = v8.String.initUtf8(isolate, name).toName();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getter_callback = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||||
|
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "get_" ++ name);
|
||||||
|
caller.method(Struct, named_function, info) catch |err| {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
|
||||||
|
const setter_name = "set_" ++ name;
|
||||||
|
if (@hasDecl(Struct, setter_name) == false) {
|
||||||
|
template_proto.setAccessorGetter(js_name, getter_callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setter_callback = v8.FunctionTemplate.initCallback(isolate, struct {
|
||||||
|
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
std.debug.assert(info.length() == 1);
|
||||||
|
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "set_" ++ name);
|
||||||
|
caller.method(Struct, named_function, info) catch |err| {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
|
||||||
|
template_proto.setAccessorGetterAndSetter(js_name, getter_callback, setter_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateIndexer(comptime Struct: type, template_proto: v8.ObjectTemplate) void {
|
||||||
|
if (@hasDecl(Struct, "indexed_get") == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const configuration = v8.IndexedPropertyHandlerConfiguration{
|
||||||
|
.getter = struct {
|
||||||
|
fn callback(idx: u32, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "indexed_get");
|
||||||
|
return caller.getIndex(Struct, named_function, idx, info) catch |err| blk: {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
break :blk v8.Intercepted.No;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback,
|
||||||
|
};
|
||||||
|
|
||||||
|
// If you're trying to implement setter, read:
|
||||||
|
// https://groups.google.com/g/v8-users/c/8tahYBsHpgY/m/IteS7Wn2AAAJ
|
||||||
|
// The issue I had was
|
||||||
|
// (a) where to attache it: does it go on the instance_template
|
||||||
|
// instead of the prototype?
|
||||||
|
// (b) defining the getter or query to respond with the
|
||||||
|
// PropertyAttribute to indicate if the property can be set
|
||||||
|
template_proto.setIndexedProperty(configuration, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateNamedIndexer(comptime Struct: type, template_proto: v8.ObjectTemplate) void {
|
||||||
|
if (@hasDecl(Struct, "named_get") == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var configuration = v8.NamedPropertyHandlerConfiguration{
|
||||||
|
.getter = struct {
|
||||||
|
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "named_get");
|
||||||
|
return caller.getNamedIndex(Struct, named_function, .{ .handle = c_name.? }, info) catch |err| blk: {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
break :blk v8.Intercepted.No;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback,
|
||||||
|
|
||||||
|
// This is really cool. Without this, we'd intercept _all_ properties
|
||||||
|
// even those explicitly set. So, node.length for example would get routed
|
||||||
|
// to our `named_get`, rather than a `get_length`. This might be
|
||||||
|
// useful if we run into a type that we can't model properly in Zig.
|
||||||
|
.flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (@hasDecl(Struct, "named_set")) {
|
||||||
|
configuration.setter = struct {
|
||||||
|
fn callback(c_name: ?*const v8.C_Name, c_value: ?*const v8.C_Value, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "named_set");
|
||||||
|
return caller.setNamedIndex(Struct, named_function, .{ .handle = c_name.? }, .{ .handle = c_value.? }, info) catch |err| blk: {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
break :blk v8.Intercepted.No;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(Struct, "named_delete")) {
|
||||||
|
configuration.deleter = struct {
|
||||||
|
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "named_delete");
|
||||||
|
return caller.deleteNamedIndex(Struct, named_function, .{ .handle = c_name.? }, info) catch |err| blk: {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
break :blk v8.Intercepted.No;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback;
|
||||||
|
}
|
||||||
|
template_proto.setNamedProperty(configuration, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateUndetectable(comptime Struct: type, template: v8.ObjectTemplate) void {
|
||||||
|
const has_js_call_as_function = @hasDecl(Struct, "jsCallAsFunction");
|
||||||
|
|
||||||
|
if (has_js_call_as_function) {
|
||||||
|
template.setCallAsFunctionHandler(struct {
|
||||||
|
fn callback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void {
|
||||||
|
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
|
||||||
|
var caller = Caller.init(info);
|
||||||
|
defer caller.deinit();
|
||||||
|
|
||||||
|
const named_function = comptime NamedFunction.init(Struct, "jsCallAsFunction");
|
||||||
|
caller.method(Struct, named_function, info) catch |err| {
|
||||||
|
caller.handleError(Struct, named_function, err, info);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}.callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(Struct, "mark_as_undetectable") and Struct.mark_as_undetectable) {
|
||||||
|
if (!has_js_call_as_function) {
|
||||||
|
@compileError(@typeName(Struct) ++ ": mark_as_undetectable required jsCallAsFunction to be defined. This is a hard-coded requirement in V8, because mark_as_undetectable only exists for HTMLAllCollection which is also callable.");
|
||||||
|
}
|
||||||
|
template.markAsUndetectable();
|
||||||
|
}
|
||||||
|
}
|
||||||
259
src/browser/js/ExecutionWorld.zig
Normal file
259
src/browser/js/ExecutionWorld.zig
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
const Page = @import("../page.zig").Page;
|
||||||
|
const ScriptManager = @import("../ScriptManager.zig");
|
||||||
|
|
||||||
|
const types = @import("types.zig");
|
||||||
|
const Types = types.Types;
|
||||||
|
const Env = @import("Env.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
|
||||||
|
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
|
const CONTEXT_ARENA_RETAIN = 1024 * 64;
|
||||||
|
|
||||||
|
// ExecutionWorld closely models a JS World.
|
||||||
|
// https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#World
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld
|
||||||
|
const ExecutionWorld = @This();
|
||||||
|
env: *Env,
|
||||||
|
|
||||||
|
// Arena whose lifetime is for a single getter/setter/function/etc.
|
||||||
|
// Largely used to get strings out of V8, like a stack trace from
|
||||||
|
// a TryCatch. The allocator will be owned by the Context, but the
|
||||||
|
// arena itself is owned by the ExecutionWorld so that we can re-use it
|
||||||
|
// from context to context.
|
||||||
|
call_arena: ArenaAllocator,
|
||||||
|
|
||||||
|
// Arena whose lifetime is for a single page load. Where
|
||||||
|
// the call_arena lives for a single function call, the context_arena
|
||||||
|
// lives for the lifetime of the entire page. The allocator will be
|
||||||
|
// owned by the Context, but the arena itself is owned by the ExecutionWorld
|
||||||
|
// so that we can re-use it from context to context.
|
||||||
|
context_arena: ArenaAllocator,
|
||||||
|
|
||||||
|
// Currently a context maps to a Browser's Page. Here though, it's only a
|
||||||
|
// mechanism to organization page-specific memory. The ExecutionWorld
|
||||||
|
// does all the work, but having all page-specific data structures
|
||||||
|
// grouped together helps keep things clean.
|
||||||
|
context: ?Context = null,
|
||||||
|
|
||||||
|
// no init, must be initialized via env.newExecutionWorld()
|
||||||
|
|
||||||
|
pub fn deinit(self: *ExecutionWorld) void {
|
||||||
|
if (self.context != null) {
|
||||||
|
self.removeContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.call_arena.deinit();
|
||||||
|
self.context_arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the top Context in the Main ExecutionWorld should hold a handle_scope.
|
||||||
|
// A v8.HandleScope is like an arena. Once created, any "Local" that
|
||||||
|
// v8 creates will be released (or at least, releasable by the v8 GC)
|
||||||
|
// when the handle_scope is freed.
|
||||||
|
// We also maintain our own "context_arena" which allows us to have
|
||||||
|
// all page related memory easily managed.
|
||||||
|
pub fn createContext(self: *ExecutionWorld, global: anytype, page: *Page, script_manager: ?*ScriptManager, enter: bool, global_callback: ?js.GlobalMissingCallback) !*Context {
|
||||||
|
std.debug.assert(self.context == null);
|
||||||
|
|
||||||
|
const env = self.env;
|
||||||
|
const isolate = env.isolate;
|
||||||
|
const Global = @TypeOf(global.*);
|
||||||
|
const templates = &self.env.templates;
|
||||||
|
|
||||||
|
var v8_context: v8.Context = blk: {
|
||||||
|
var temp_scope: v8.HandleScope = undefined;
|
||||||
|
v8.HandleScope.init(&temp_scope, isolate);
|
||||||
|
defer temp_scope.deinit();
|
||||||
|
|
||||||
|
const js_global = v8.FunctionTemplate.initDefault(isolate);
|
||||||
|
Env.attachClass(Global, isolate, js_global);
|
||||||
|
|
||||||
|
const global_template = js_global.getInstanceTemplate();
|
||||||
|
global_template.setInternalFieldCount(1);
|
||||||
|
|
||||||
|
// Configure the missing property callback on the global
|
||||||
|
// object.
|
||||||
|
if (global_callback != null) {
|
||||||
|
const configuration = v8.NamedPropertyHandlerConfiguration{
|
||||||
|
.getter = struct {
|
||||||
|
fn callback(c_name: ?*const v8.C_Name, raw_info: ?*const v8.C_PropertyCallbackInfo) callconv(.c) u8 {
|
||||||
|
const info = v8.PropertyCallbackInfo.initFromV8(raw_info);
|
||||||
|
const _isolate = info.getIsolate();
|
||||||
|
const v8_context = _isolate.getCurrentContext();
|
||||||
|
|
||||||
|
const context: *Context = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
|
||||||
|
|
||||||
|
const property = js.valueToString(context.call_arena, .{ .handle = c_name.? }, _isolate, v8_context) catch "???";
|
||||||
|
if (context.global_callback.?.missing(property, context)) {
|
||||||
|
return v8.Intercepted.Yes;
|
||||||
|
}
|
||||||
|
return v8.Intercepted.No;
|
||||||
|
}
|
||||||
|
}.callback,
|
||||||
|
.flags = v8.PropertyHandlerFlags.NonMasking | v8.PropertyHandlerFlags.OnlyInterceptStrings,
|
||||||
|
};
|
||||||
|
global_template.setNamedProperty(configuration, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All the FunctionTemplates that we created and setup in Env.init
|
||||||
|
// are now going to get associated with our global instance.
|
||||||
|
inline for (Types, 0..) |s, i| {
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
const class_name = v8.String.initUtf8(isolate, comptime js.classNameForStruct(Struct));
|
||||||
|
global_template.set(class_name.toName(), templates[i], v8.PropertyAttribute.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The global object (Window) has already been hooked into the v8
|
||||||
|
// engine when the Env was initialized - like every other type.
|
||||||
|
// But the V8 global is its own FunctionTemplate instance so even
|
||||||
|
// though it's also a Window, we need to set the prototype for this
|
||||||
|
// specific instance of the the Window.
|
||||||
|
if (@hasDecl(Global, "prototype")) {
|
||||||
|
const proto_type = types.Receiver(@typeInfo(Global.prototype).pointer.child);
|
||||||
|
const proto_name = @typeName(proto_type);
|
||||||
|
const proto_index = @field(types.LOOKUP, proto_name);
|
||||||
|
js_global.inherit(templates[proto_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const context_local = v8.Context.init(isolate, global_template, null);
|
||||||
|
const v8_context = v8.Persistent(v8.Context).init(isolate, context_local).castToContext();
|
||||||
|
v8_context.enter();
|
||||||
|
errdefer if (enter) v8_context.exit();
|
||||||
|
defer if (!enter) v8_context.exit();
|
||||||
|
|
||||||
|
// This shouldn't be necessary, but it is:
|
||||||
|
// https://groups.google.com/g/v8-users/c/qAQQBmbi--8
|
||||||
|
// TODO: see if newer V8 engines have a way around this.
|
||||||
|
inline for (Types, 0..) |s, i| {
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
|
||||||
|
if (@hasDecl(Struct, "prototype")) {
|
||||||
|
const proto_type = types.Receiver(@typeInfo(Struct.prototype).pointer.child);
|
||||||
|
const proto_name = @typeName(proto_type);
|
||||||
|
if (@hasField(types.Lookup, proto_name) == false) {
|
||||||
|
@compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proto_index = @field(types.LOOKUP, proto_name);
|
||||||
|
const proto_obj = templates[proto_index].getFunction(v8_context).toObject();
|
||||||
|
|
||||||
|
const self_obj = templates[i].getFunction(v8_context).toObject();
|
||||||
|
_ = self_obj.setPrototype(v8_context, proto_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break :blk v8_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
// For a Page we only create one HandleScope, it is stored in the main World (enter==true). A page can have multple contexts, 1 for each World.
|
||||||
|
// The main Context that enters and holds the HandleScope should therefore always be created first. Following other worlds for this page
|
||||||
|
// like isolated Worlds, will thereby place their objects on the main page's HandleScope. Note: In the furure the number of context will multiply multiple frames support
|
||||||
|
var handle_scope: ?v8.HandleScope = null;
|
||||||
|
if (enter) {
|
||||||
|
handle_scope = @as(v8.HandleScope, undefined);
|
||||||
|
v8.HandleScope.init(&handle_scope.?, isolate);
|
||||||
|
}
|
||||||
|
errdefer if (enter) handle_scope.?.deinit();
|
||||||
|
|
||||||
|
{
|
||||||
|
// If we want to overwrite the built-in console, we have to
|
||||||
|
// delete the built-in one.
|
||||||
|
const js_obj = v8_context.getGlobal();
|
||||||
|
const console_key = v8.String.initUtf8(isolate, "console");
|
||||||
|
if (js_obj.deleteValue(v8_context, console_key) == false) {
|
||||||
|
return error.ConsoleDeleteError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const context_id = env.context_id;
|
||||||
|
env.context_id = context_id + 1;
|
||||||
|
|
||||||
|
self.context = Context{
|
||||||
|
.page = page,
|
||||||
|
.id = context_id,
|
||||||
|
.isolate = isolate,
|
||||||
|
.v8_context = v8_context,
|
||||||
|
.templates = &env.templates,
|
||||||
|
.meta_lookup = &env.meta_lookup,
|
||||||
|
.handle_scope = handle_scope,
|
||||||
|
.script_manager = script_manager,
|
||||||
|
.call_arena = self.call_arena.allocator(),
|
||||||
|
.context_arena = self.context_arena.allocator(),
|
||||||
|
.global_callback = global_callback,
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = &self.context.?;
|
||||||
|
{
|
||||||
|
// Given a context, we can get our executor.
|
||||||
|
// (we store a pointer to our executor in the context's
|
||||||
|
// embeddeder data)
|
||||||
|
const data = isolate.initBigIntU64(@intCast(@intFromPtr(context)));
|
||||||
|
v8_context.setEmbedderData(1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.call_arena = context.call_arena;
|
||||||
|
|
||||||
|
// Custom exception
|
||||||
|
// NOTE: there is no way in v8 to subclass the Error built-in type
|
||||||
|
// TODO: this is an horrible hack
|
||||||
|
inline for (Types) |s| {
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
if (@hasDecl(Struct, "ErrorSet")) {
|
||||||
|
const script = comptime js.classNameForStruct(Struct) ++ ".prototype.__proto__ = Error.prototype";
|
||||||
|
_ = try context.exec(script, "errorSubclass");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primitive attributes are set directly on the FunctionTemplate
|
||||||
|
// when we setup the environment. But we cannot set more complex
|
||||||
|
// types (v8 will crash).
|
||||||
|
//
|
||||||
|
// Plus, just to create more complex types, we always need a
|
||||||
|
// context, i.e. an Array has to have a Context to exist.
|
||||||
|
//
|
||||||
|
// As far as I can tell, getting the FunctionTemplate's object
|
||||||
|
// and setting values directly on it, for each context, is the
|
||||||
|
// way to do this.
|
||||||
|
inline for (Types, 0..) |s, i| {
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
inline for (@typeInfo(Struct).@"struct".decls) |declaration| {
|
||||||
|
const name = declaration.name;
|
||||||
|
if (comptime name[0] == '_') {
|
||||||
|
const value = @field(Struct, name);
|
||||||
|
|
||||||
|
if (comptime js.isComplexAttributeType(@typeInfo(@TypeOf(value)))) {
|
||||||
|
const js_obj = templates[i].getFunction(v8_context).toObject();
|
||||||
|
const js_name = v8.String.initUtf8(isolate, name[1..]).toName();
|
||||||
|
const js_val = try context.zigValueToJs(value);
|
||||||
|
if (!js_obj.setValue(v8_context, js_name, js_val)) {
|
||||||
|
log.fatal(.app, "set class attribute", .{
|
||||||
|
.@"struct" = @typeName(Struct),
|
||||||
|
.name = name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = try context._mapZigInstanceToJs(v8_context.getGlobal(), global);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeContext(self: *ExecutionWorld) void {
|
||||||
|
self.context.?.deinit();
|
||||||
|
self.context = null;
|
||||||
|
_ = self.context_arena.reset(.{ .retain_with_limit = CONTEXT_ARENA_RETAIN });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn terminateExecution(self: *const ExecutionWorld) void {
|
||||||
|
self.env.isolate.terminateExecution();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resumeExecution(self: *const ExecutionWorld) void {
|
||||||
|
self.env.isolate.cancelTerminateExecution();
|
||||||
|
}
|
||||||
147
src/browser/js/Function.zig
Normal file
147
src/browser/js/Function.zig
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Caller = @import("Caller.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const PersistentFunction = v8.Persistent(v8.Function);
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Function = @This();
|
||||||
|
|
||||||
|
id: usize,
|
||||||
|
context: *js.Context,
|
||||||
|
this: ?v8.Object = null,
|
||||||
|
func: PersistentFunction,
|
||||||
|
|
||||||
|
pub const Result = struct {
|
||||||
|
stack: ?[]const u8,
|
||||||
|
exception: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn getName(self: *const Function, allocator: Allocator) ![]const u8 {
|
||||||
|
const name = self.func.castToFunction().getName();
|
||||||
|
return js.valueToString(allocator, name, self.context.isolate, self.context.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setName(self: *const Function, name: []const u8) void {
|
||||||
|
const v8_name = v8.String.initUtf8(self.context.isolate, name);
|
||||||
|
self.func.castToFunction().setName(v8_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn withThis(self: *const Function, value: anytype) !Function {
|
||||||
|
const this_obj = if (@TypeOf(value) == js.Object)
|
||||||
|
value.js_obj
|
||||||
|
else
|
||||||
|
(try self.context.zigValueToJs(value)).castTo(v8.Object);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.id = self.id,
|
||||||
|
.this = this_obj,
|
||||||
|
.func = self.func,
|
||||||
|
.context = self.context,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newInstance(self: *const Function, result: *Result) !js.Object {
|
||||||
|
const context = self.context;
|
||||||
|
|
||||||
|
var try_catch: js.TryCatch = undefined;
|
||||||
|
try_catch.init(context);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
// This creates a new instance using this Function as a constructor.
|
||||||
|
// This returns a generic Object
|
||||||
|
const js_obj = self.func.castToFunction().initInstance(context.v8_context, &.{}) orelse {
|
||||||
|
if (try_catch.hasCaught()) {
|
||||||
|
const allocator = context.call_arena;
|
||||||
|
result.stack = try_catch.stack(allocator) catch null;
|
||||||
|
result.exception = (try_catch.exception(allocator) catch "???") orelse "???";
|
||||||
|
} else {
|
||||||
|
result.stack = null;
|
||||||
|
result.exception = "???";
|
||||||
|
}
|
||||||
|
return error.JsConstructorFailed;
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.context = context,
|
||||||
|
.js_obj = js_obj,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(self: *const Function, comptime T: type, args: anytype) !T {
|
||||||
|
return self.callWithThis(T, self.getThis(), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tryCall(self: *const Function, comptime T: type, args: anytype, result: *Result) !T {
|
||||||
|
return self.tryCallWithThis(T, self.getThis(), args, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !T {
|
||||||
|
var try_catch: js.TryCatch = undefined;
|
||||||
|
try_catch.init(self.context);
|
||||||
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
return self.callWithThis(T, this, args) catch |err| {
|
||||||
|
if (try_catch.hasCaught()) {
|
||||||
|
const allocator = self.context.call_arena;
|
||||||
|
result.stack = try_catch.stack(allocator) catch null;
|
||||||
|
result.exception = (try_catch.exception(allocator) catch @errorName(err)) orelse @errorName(err);
|
||||||
|
} else {
|
||||||
|
result.stack = null;
|
||||||
|
result.exception = @errorName(err);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn callWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype) !T {
|
||||||
|
const context = self.context;
|
||||||
|
|
||||||
|
const js_this = try context.valueToExistingObject(this);
|
||||||
|
|
||||||
|
const aargs = if (comptime @typeInfo(@TypeOf(args)) == .null) struct {}{} else args;
|
||||||
|
|
||||||
|
const js_args: []const v8.Value = switch (@typeInfo(@TypeOf(aargs))) {
|
||||||
|
.@"struct" => |s| blk: {
|
||||||
|
const fields = s.fields;
|
||||||
|
var js_args: [fields.len]v8.Value = undefined;
|
||||||
|
inline for (fields, 0..) |f, i| {
|
||||||
|
js_args[i] = try context.zigValueToJs(@field(aargs, f.name));
|
||||||
|
}
|
||||||
|
const cargs: [fields.len]v8.Value = js_args;
|
||||||
|
break :blk &cargs;
|
||||||
|
},
|
||||||
|
.pointer => blk: {
|
||||||
|
var values = try context.call_arena.alloc(v8.Value, args.len);
|
||||||
|
for (args, 0..) |a, i| {
|
||||||
|
values[i] = try context.zigValueToJs(a);
|
||||||
|
}
|
||||||
|
break :blk values;
|
||||||
|
},
|
||||||
|
else => @compileError("JS Function called with invalid paremter type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = self.func.castToFunction().call(context.v8_context, js_this, js_args);
|
||||||
|
if (result == null) {
|
||||||
|
return error.JSExecCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@typeInfo(T) == .void) return {};
|
||||||
|
const named_function = comptime Caller.NamedFunction.init(T, "callResult");
|
||||||
|
return context.jsValueToZig(named_function, T, result.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getThis(self: *const Function) v8.Object {
|
||||||
|
return self.this orelse self.context.v8_context.getGlobal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug/helper to print the source of the JS callback
|
||||||
|
pub fn printFunc(self: Function) !void {
|
||||||
|
const context = self.context;
|
||||||
|
const value = self.func.castToFunction().toValue();
|
||||||
|
const src = try js.valueToString(context.call_arena, value, context.isolate, context.v8_context);
|
||||||
|
std.debug.print("{s}\n", .{src});
|
||||||
|
}
|
||||||
153
src/browser/js/Inspector.zig
Normal file
153
src/browser/js/Inspector.zig
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Inspector = @This();
|
||||||
|
|
||||||
|
pub const RemoteObject = v8.RemoteObject;
|
||||||
|
|
||||||
|
isolate: v8.Isolate,
|
||||||
|
inner: *v8.Inspector,
|
||||||
|
session: v8.InspectorSession,
|
||||||
|
|
||||||
|
// We expect allocator to be an arena
|
||||||
|
pub fn init(allocator: Allocator, isolate: v8.Isolate, ctx: anytype) !Inspector {
|
||||||
|
const ContextT = @TypeOf(ctx);
|
||||||
|
|
||||||
|
const InspectorContainer = switch (@typeInfo(ContextT)) {
|
||||||
|
.@"struct" => ContextT,
|
||||||
|
.pointer => |ptr| ptr.child,
|
||||||
|
.void => NoopInspector,
|
||||||
|
else => @compileError("invalid context type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If necessary, turn a void context into something we can safely ptrCast
|
||||||
|
const safe_context: *anyopaque = if (ContextT == void) @ptrCast(@constCast(&{})) else ctx;
|
||||||
|
|
||||||
|
const channel = v8.InspectorChannel.init(safe_context, InspectorContainer.onInspectorResponse, InspectorContainer.onInspectorEvent, isolate);
|
||||||
|
|
||||||
|
const client = v8.InspectorClient.init();
|
||||||
|
|
||||||
|
const inner = try allocator.create(v8.Inspector);
|
||||||
|
v8.Inspector.init(inner, client, channel, isolate);
|
||||||
|
return .{ .inner = inner, .isolate = isolate, .session = inner.connect() };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const Inspector) void {
|
||||||
|
self.session.deinit();
|
||||||
|
self.inner.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send(self: *const Inspector, msg: []const u8) void {
|
||||||
|
// Can't assume the main Context exists (with its HandleScope)
|
||||||
|
// available when doing this. Pages (and thus the HandleScope)
|
||||||
|
// comes and goes, but CDP can keep sending messages.
|
||||||
|
const isolate = self.isolate;
|
||||||
|
var temp_scope: v8.HandleScope = undefined;
|
||||||
|
v8.HandleScope.init(&temp_scope, isolate);
|
||||||
|
defer temp_scope.deinit();
|
||||||
|
|
||||||
|
self.session.dispatchProtocolMessage(isolate, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// From CDP docs
|
||||||
|
// https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ExecutionContextDescription
|
||||||
|
// ----
|
||||||
|
// - name: Human readable name describing given context.
|
||||||
|
// - origin: Execution context origin (ie. URL who initialised the request)
|
||||||
|
// - auxData: Embedder-specific auxiliary data likely matching
|
||||||
|
// {isDefault: boolean, type: 'default'|'isolated'|'worker', frameId: string}
|
||||||
|
// - is_default_context: Whether the execution context is default, should match the auxData
|
||||||
|
pub fn contextCreated(
|
||||||
|
self: *const Inspector,
|
||||||
|
context: *const Context,
|
||||||
|
name: []const u8,
|
||||||
|
origin: []const u8,
|
||||||
|
aux_data: ?[]const u8,
|
||||||
|
is_default_context: bool,
|
||||||
|
) void {
|
||||||
|
self.inner.contextCreated(context.v8_context, name, origin, aux_data, is_default_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves the RemoteObject for a given value.
|
||||||
|
// The value is loaded through the ExecutionWorld's mapZigInstanceToJs function,
|
||||||
|
// just like a method return value. Therefore, if we've mapped this
|
||||||
|
// value before, we'll get the existing JS PersistedObject and if not
|
||||||
|
// we'll create it and track it for cleanup when the context ends.
|
||||||
|
pub fn getRemoteObject(
|
||||||
|
self: *const Inspector,
|
||||||
|
context: *const Context,
|
||||||
|
group: []const u8,
|
||||||
|
value: anytype,
|
||||||
|
) !RemoteObject {
|
||||||
|
const js_value = try context.zigValueToJs(value);
|
||||||
|
|
||||||
|
// We do not want to expose this as a parameter for now
|
||||||
|
const generate_preview = false;
|
||||||
|
return self.session.wrapObject(
|
||||||
|
context.isolate,
|
||||||
|
context.v8_context,
|
||||||
|
js_value,
|
||||||
|
group,
|
||||||
|
generate_preview,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a value by object ID regardless of which context it is in.
|
||||||
|
pub fn getNodePtr(self: *const Inspector, allocator: Allocator, object_id: []const u8) !?*anyopaque {
|
||||||
|
const unwrapped = try self.session.unwrapObject(allocator, object_id);
|
||||||
|
// The values context and groupId are not used here
|
||||||
|
const toa = getTaggedAnyOpaque(unwrapped.value) orelse return null;
|
||||||
|
if (toa.subtype == null or toa.subtype != .node) return error.ObjectIdIsNotANode;
|
||||||
|
return toa.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoopInspector = struct {
|
||||||
|
pub fn onInspectorResponse(_: *anyopaque, _: u32, _: []const u8) void {}
|
||||||
|
pub fn onInspectorEvent(_: *anyopaque, _: []const u8) void {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is called from V8. Whenever the v8 inspector has to describe a value
|
||||||
|
// it'll call this function to gets its [optional] subtype - which, from V8's
|
||||||
|
// point of view, is an arbitrary string.
|
||||||
|
pub export fn v8_inspector__Client__IMPL__valueSubtype(
|
||||||
|
_: *v8.c.InspectorClientImpl,
|
||||||
|
c_value: *const v8.C_Value,
|
||||||
|
) callconv(.c) [*c]const u8 {
|
||||||
|
const external_entry = getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null;
|
||||||
|
return if (external_entry.subtype) |st| @tagName(st) else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as valueSubType above, but for the optional description field.
|
||||||
|
// From what I can tell, some drivers _need_ the description field to be
|
||||||
|
// present, even if it's empty. So if we have a subType for the value, we'll
|
||||||
|
// put an empty description.
|
||||||
|
pub export fn v8_inspector__Client__IMPL__descriptionForValueSubtype(
|
||||||
|
_: *v8.c.InspectorClientImpl,
|
||||||
|
v8_context: *const v8.C_Context,
|
||||||
|
c_value: *const v8.C_Value,
|
||||||
|
) callconv(.c) [*c]const u8 {
|
||||||
|
_ = v8_context;
|
||||||
|
|
||||||
|
// We _must_ include a non-null description in order for the subtype value
|
||||||
|
// to be included. Besides that, I don't know if the value has any meaning
|
||||||
|
const external_entry = getTaggedAnyOpaque(.{ .handle = c_value }) orelse return null;
|
||||||
|
return if (external_entry.subtype == null) null else "";
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getTaggedAnyOpaque(value: v8.Value) ?*js.TaggedAnyOpaque {
|
||||||
|
if (value.isObject() == false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const obj = value.castTo(v8.Object);
|
||||||
|
if (obj.internalFieldCount() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const external_data = obj.getInternalField(0).castTo(v8.External).get().?;
|
||||||
|
return @ptrCast(@alignCast(external_data));
|
||||||
|
}
|
||||||
156
src/browser/js/Object.zig
Normal file
156
src/browser/js/Object.zig
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Caller = @import("Caller.zig");
|
||||||
|
const Context = @import("Context.zig");
|
||||||
|
const PersistentObject = v8.Persistent(v8.Object);
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Object = @This();
|
||||||
|
js_obj: v8.Object,
|
||||||
|
context: *js.Context,
|
||||||
|
|
||||||
|
pub const SetOpts = packed struct(u32) {
|
||||||
|
READ_ONLY: bool = false,
|
||||||
|
DONT_ENUM: bool = false,
|
||||||
|
DONT_DELETE: bool = false,
|
||||||
|
_: u29 = 0,
|
||||||
|
};
|
||||||
|
pub fn setIndex(self: Object, index: u32, value: anytype, opts: SetOpts) !void {
|
||||||
|
@setEvalBranchQuota(10000);
|
||||||
|
const key = switch (index) {
|
||||||
|
inline 0...20 => |i| std.fmt.comptimePrint("{d}", .{i}),
|
||||||
|
else => try std.fmt.allocPrint(self.context.context_arena, "{d}", .{index}),
|
||||||
|
};
|
||||||
|
return self.set(key, value, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(self: Object, key: []const u8, value: anytype, opts: SetOpts) !void {
|
||||||
|
const context = self.context;
|
||||||
|
|
||||||
|
const js_key = v8.String.initUtf8(context.isolate, key);
|
||||||
|
const js_value = try context.zigValueToJs(value);
|
||||||
|
|
||||||
|
const res = self.js_obj.defineOwnProperty(context.v8_context, js_key.toName(), js_value, @bitCast(opts)) orelse false;
|
||||||
|
if (!res) {
|
||||||
|
return error.FailedToSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Object, key: []const u8) !js.Value {
|
||||||
|
const context = self.context;
|
||||||
|
const js_key = v8.String.initUtf8(context.isolate, key);
|
||||||
|
const js_val = try self.js_obj.getValue(context.v8_context, js_key);
|
||||||
|
return context.createValue(js_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isTruthy(self: Object) bool {
|
||||||
|
const js_value = self.js_obj.toValue();
|
||||||
|
return js_value.toBool(self.context.isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toString(self: Object) ![]const u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const js_value = self.js_obj.toValue();
|
||||||
|
return js.valueToString(context.call_arena, js_value, context.isolate, context.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toDetailString(self: Object) ![]const u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const js_value = self.js_obj.toValue();
|
||||||
|
return js.valueToDetailString(context.call_arena, js_value, context.isolate, context.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(self: Object, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
|
return writer.writeAll(try self.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toJson(self: Object, allocator: std.mem.Allocator) ![]u8 {
|
||||||
|
const json_string = try v8.Json.stringify(self.context.v8_context, self.js_obj.toValue(), null);
|
||||||
|
const str = try js.stringToZig(allocator, json_string, self.context.isolate);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn persist(self: Object) !Object {
|
||||||
|
var context = self.context;
|
||||||
|
const js_obj = self.js_obj;
|
||||||
|
|
||||||
|
const persisted = PersistentObject.init(context.isolate, js_obj);
|
||||||
|
try context.js_object_list.append(context.context_arena, persisted);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.context = context,
|
||||||
|
.js_obj = persisted.castToObject(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFunction(self: Object, name: []const u8) !?js.Function {
|
||||||
|
if (self.isNullOrUndefined()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const context = self.context;
|
||||||
|
|
||||||
|
const js_name = v8.String.initUtf8(context.isolate, name);
|
||||||
|
|
||||||
|
const js_value = try self.js_obj.getValue(context.v8_context, js_name.toName());
|
||||||
|
if (!js_value.isFunction()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return try context.createFunction(js_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isNull(self: Object) bool {
|
||||||
|
return self.js_obj.toValue().isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isUndefined(self: Object) bool {
|
||||||
|
return self.js_obj.toValue().isUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn triState(self: Object, comptime Struct: type, comptime name: []const u8, comptime T: type) !TriState(T) {
|
||||||
|
if (self.isNull()) {
|
||||||
|
return .{ .null = {} };
|
||||||
|
}
|
||||||
|
if (self.isUndefined()) {
|
||||||
|
return .{ .undefined = {} };
|
||||||
|
}
|
||||||
|
return .{ .value = try self.toZig(Struct, name, T) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isNullOrUndefined(self: Object) bool {
|
||||||
|
return self.js_obj.toValue().isNullOrUndefined();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nameIterator(self: Object) js.ValueIterator {
|
||||||
|
const context = self.context;
|
||||||
|
const js_obj = self.js_obj;
|
||||||
|
|
||||||
|
const array = js_obj.getPropertyNames(context.v8_context);
|
||||||
|
const count = array.length();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.count = count,
|
||||||
|
.context = context,
|
||||||
|
.js_obj = array.castTo(v8.Object),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn constructorName(self: Object, allocator: Allocator) ![]const u8 {
|
||||||
|
const str = try self.js_obj.getConstructorName();
|
||||||
|
return js.StringToZig(allocator, str, self.context.isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toZig(self: Object, comptime Struct: type, comptime name: []const u8, comptime T: type) !T {
|
||||||
|
const named_function = comptime Caller.NamedFunction.init(Struct, name);
|
||||||
|
return self.context.jsValueToZig(named_function, T, self.js_obj.toValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TriState(comptime T: type) type {
|
||||||
|
return union(enum) {
|
||||||
|
null: void,
|
||||||
|
undefined: void,
|
||||||
|
value: T,
|
||||||
|
};
|
||||||
|
}
|
||||||
21
src/browser/js/Platform.zig
Normal file
21
src/browser/js/Platform.zig
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Platform = @This();
|
||||||
|
inner: v8.Platform,
|
||||||
|
|
||||||
|
pub fn init() !Platform {
|
||||||
|
if (v8.initV8ICU() == false) {
|
||||||
|
return error.FailedToInitializeICU;
|
||||||
|
}
|
||||||
|
const platform = v8.Platform.initDefault(0, true);
|
||||||
|
v8.initV8Platform(platform);
|
||||||
|
v8.initV8();
|
||||||
|
return .{ .inner = platform };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Platform) void {
|
||||||
|
_ = v8.deinitV8();
|
||||||
|
v8.deinitV8Platform();
|
||||||
|
self.inner.deinit();
|
||||||
|
}
|
||||||
29
src/browser/js/This.zig
Normal file
29
src/browser/js/This.zig
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
// This only exists so that we know whether a function wants the opaque
|
||||||
|
// JS argument (js.Object), or if it wants the receiver as an opaque
|
||||||
|
// value.
|
||||||
|
// js.Object is normally used when a method wants an opaque JS object
|
||||||
|
// that it'll pass into a callback.
|
||||||
|
// This is used when the function wants to do advanced manipulation
|
||||||
|
// of the v8.Object bound to the instance. For example, postAttach is an
|
||||||
|
// example of using This.
|
||||||
|
|
||||||
|
const This = @This();
|
||||||
|
obj: js.Object,
|
||||||
|
|
||||||
|
pub fn setIndex(self: This, index: u32, value: anytype, opts: js.Object.SetOpts) !void {
|
||||||
|
return self.obj.setIndex(index, value, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(self: This, key: []const u8, value: anytype, opts: js.Object.SetOpts) !void {
|
||||||
|
return self.obj.set(key, value, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn constructorName(self: This, allocator: Allocator) ![]const u8 {
|
||||||
|
return try self.obj.constructorName(allocator);
|
||||||
|
}
|
||||||
65
src/browser/js/TryCatch.zig
Normal file
65
src/browser/js/TryCatch.zig
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const js = @import("js.zig");
|
||||||
|
const v8 = js.v8;
|
||||||
|
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const TryCatch = @This();
|
||||||
|
|
||||||
|
inner: v8.TryCatch,
|
||||||
|
context: *const js.Context,
|
||||||
|
|
||||||
|
pub fn init(self: *TryCatch, context: *const js.Context) void {
|
||||||
|
self.context = context;
|
||||||
|
self.inner.init(context.isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasCaught(self: TryCatch) bool {
|
||||||
|
return self.inner.hasCaught();
|
||||||
|
}
|
||||||
|
|
||||||
|
// the caller needs to deinit the string returned
|
||||||
|
pub fn exception(self: TryCatch, allocator: Allocator) !?[]const u8 {
|
||||||
|
const msg = self.inner.getException() orelse return null;
|
||||||
|
const context = self.context;
|
||||||
|
return try js.valueToString(allocator, msg, context.isolate, context.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the caller needs to deinit the string returned
|
||||||
|
pub fn stack(self: TryCatch, allocator: Allocator) !?[]const u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const s = self.inner.getStackTrace(context.v8_context) orelse return null;
|
||||||
|
return try js.valueToString(allocator, s, context.isolate, context.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the caller needs to deinit the string returned
|
||||||
|
pub fn sourceLine(self: TryCatch, allocator: Allocator) !?[]const u8 {
|
||||||
|
const context = self.context;
|
||||||
|
const msg = self.inner.getMessage() orelse return null;
|
||||||
|
const sl = msg.getSourceLine(context.v8_context) orelse return null;
|
||||||
|
return try js.stringToZig(allocator, sl, context.isolate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sourceLineNumber(self: TryCatch) ?u32 {
|
||||||
|
const context = self.context;
|
||||||
|
const msg = self.inner.getMessage() orelse return null;
|
||||||
|
return msg.getLineNumber(context.v8_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// a shorthand method to return either the entire stack message
|
||||||
|
// or just the exception message
|
||||||
|
// - in Debug mode return the stack if available
|
||||||
|
// - otherwise return the exception if available
|
||||||
|
// the caller needs to deinit the string returned
|
||||||
|
pub fn err(self: TryCatch, allocator: Allocator) !?[]const u8 {
|
||||||
|
if (comptime @import("builtin").mode == .Debug) {
|
||||||
|
if (try self.stack(allocator)) |msg| {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return try self.exception(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *TryCatch) void {
|
||||||
|
self.inner.deinit();
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,20 +0,0 @@
|
|||||||
pub const SubType = enum {
|
|
||||||
@"error",
|
|
||||||
array,
|
|
||||||
arraybuffer,
|
|
||||||
dataview,
|
|
||||||
date,
|
|
||||||
generator,
|
|
||||||
iterator,
|
|
||||||
map,
|
|
||||||
node,
|
|
||||||
promise,
|
|
||||||
proxy,
|
|
||||||
regexp,
|
|
||||||
set,
|
|
||||||
typedarray,
|
|
||||||
wasmvalue,
|
|
||||||
weakmap,
|
|
||||||
weakset,
|
|
||||||
webassemblymemory,
|
|
||||||
};
|
|
||||||
183
src/browser/js/types.zig
Normal file
183
src/browser/js/types.zig
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const generate = @import("generate.zig");
|
||||||
|
|
||||||
|
const Interfaces = generate.Tuple(.{
|
||||||
|
@import("../crypto/crypto.zig").Crypto,
|
||||||
|
@import("../console/console.zig").Console,
|
||||||
|
@import("../css/css.zig").Interfaces,
|
||||||
|
@import("../cssom/cssom.zig").Interfaces,
|
||||||
|
@import("../dom/dom.zig").Interfaces,
|
||||||
|
@import("../dom/shadow_root.zig").ShadowRoot,
|
||||||
|
@import("../encoding/encoding.zig").Interfaces,
|
||||||
|
@import("../events/event.zig").Interfaces,
|
||||||
|
@import("../html/html.zig").Interfaces,
|
||||||
|
@import("../iterator/iterator.zig").Interfaces,
|
||||||
|
@import("../storage/storage.zig").Interfaces,
|
||||||
|
@import("../url/url.zig").Interfaces,
|
||||||
|
@import("../xhr/xhr.zig").Interfaces,
|
||||||
|
@import("../xhr/form_data.zig").Interfaces,
|
||||||
|
@import("../xhr/File.zig"),
|
||||||
|
@import("../xmlserializer/xmlserializer.zig").Interfaces,
|
||||||
|
@import("../fetch/fetch.zig").Interfaces,
|
||||||
|
@import("../streams/streams.zig").Interfaces,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const Types = @typeInfo(Interfaces).@"struct".fields;
|
||||||
|
|
||||||
|
// Imagine we have a type Cat which has a getter:
|
||||||
|
//
|
||||||
|
// fn get_owner(self: *Cat) *Owner {
|
||||||
|
// return self.owner;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// When we execute caller.getter, we'll end up doing something like:
|
||||||
|
// const res = @call(.auto, Cat.get_owner, .{cat_instance});
|
||||||
|
//
|
||||||
|
// How do we turn `res`, which is an *Owner, into something we can return
|
||||||
|
// to v8? We need the ObjectTemplate associated with Owner. How do we
|
||||||
|
// get that? Well, we store all the ObjectTemplates in an array that's
|
||||||
|
// tied to env. So we do something like:
|
||||||
|
//
|
||||||
|
// env.templates[index_of_owner].initInstance(...);
|
||||||
|
//
|
||||||
|
// But how do we get that `index_of_owner`? `Lookup` is a struct
|
||||||
|
// that looks like:
|
||||||
|
//
|
||||||
|
// const Lookup = struct {
|
||||||
|
// comptime cat: usize = 0,
|
||||||
|
// comptime owner: usize = 1,
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// So to get the template index of `owner`, we can do:
|
||||||
|
//
|
||||||
|
// const index_id = @field(type_lookup, @typeName(@TypeOf(res));
|
||||||
|
//
|
||||||
|
pub const Lookup = blk: {
|
||||||
|
var fields: [Types.len]std.builtin.Type.StructField = undefined;
|
||||||
|
for (Types, 0..) |s, i| {
|
||||||
|
|
||||||
|
// This prototype type check has nothing to do with building our
|
||||||
|
// Lookup. But we put it here, early, so that the rest of the
|
||||||
|
// code doesn't have to worry about checking if Struct.prototype is
|
||||||
|
// a pointer.
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
if (@hasDecl(Struct, "prototype") and @typeInfo(Struct.prototype) != .pointer) {
|
||||||
|
@compileError(std.fmt.comptimePrint("Prototype '{s}' for type '{s} must be a pointer", .{ @typeName(Struct.prototype), @typeName(Struct) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[i] = .{
|
||||||
|
.name = @typeName(Receiver(Struct)),
|
||||||
|
.type = usize,
|
||||||
|
.is_comptime = true,
|
||||||
|
.alignment = @alignOf(usize),
|
||||||
|
.default_value_ptr = &i,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break :blk @Type(.{ .@"struct" = .{
|
||||||
|
.layout = .auto,
|
||||||
|
.decls = &.{},
|
||||||
|
.is_tuple = false,
|
||||||
|
.fields = &fields,
|
||||||
|
} });
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LOOKUP = Lookup{};
|
||||||
|
|
||||||
|
// Creates a list where the index of a type contains its prototype index
|
||||||
|
// const Animal = struct{};
|
||||||
|
// const Cat = struct{
|
||||||
|
// pub const prototype = *Animal;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// Would create an array: [0, 0]
|
||||||
|
// Animal, at index, 0, has no prototype, so we set it to itself
|
||||||
|
// Cat, at index 1, has an Animal prototype, so we set it to 0.
|
||||||
|
//
|
||||||
|
// When we're trying to pass an argument to a Zig function, we'll know the
|
||||||
|
// target type (the function parameter type), and we'll have a
|
||||||
|
// TaggedAnyOpaque which will have the index of the type of that parameter.
|
||||||
|
// We'll use the PROTOTYPE_TABLE to see if the TaggedAnyType should be
|
||||||
|
// cast to a prototype.
|
||||||
|
pub const PROTOTYPE_TABLE = blk: {
|
||||||
|
var table: [Types.len]u16 = undefined;
|
||||||
|
for (Types, 0..) |s, i| {
|
||||||
|
var prototype_index = i;
|
||||||
|
const Struct = s.defaultValue().?;
|
||||||
|
if (@hasDecl(Struct, "prototype")) {
|
||||||
|
const TI = @typeInfo(Struct.prototype);
|
||||||
|
const proto_name = @typeName(Receiver(TI.pointer.child));
|
||||||
|
prototype_index = @field(LOOKUP, proto_name);
|
||||||
|
}
|
||||||
|
table[i] = prototype_index;
|
||||||
|
}
|
||||||
|
break :blk table;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is essentially meta data for each type. Each is stored in env.meta_lookup
|
||||||
|
// The index for a type can be retrieved via:
|
||||||
|
// const index = @field(TYPE_LOOKUP, @typeName(Receiver(Struct)));
|
||||||
|
// const meta = env.meta_lookup[index];
|
||||||
|
pub const Meta = struct {
|
||||||
|
// Every type is given a unique index. That index is used to lookup various
|
||||||
|
// things, i.e. the prototype chain.
|
||||||
|
index: u16,
|
||||||
|
|
||||||
|
// We store the type's subtype here, so that when we create an instance of
|
||||||
|
// the type, and bind it to JavaScript, we can store the subtype along with
|
||||||
|
// the created TaggedAnyOpaque.s
|
||||||
|
subtype: ?Sub,
|
||||||
|
|
||||||
|
// If this type has composition-based prototype, represents the byte-offset
|
||||||
|
// from ptr where the `proto` field is located. A negative offsets is used
|
||||||
|
// to indicate that the prototype field is behind a pointer.
|
||||||
|
proto_offset: i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Sub = enum {
|
||||||
|
@"error",
|
||||||
|
array,
|
||||||
|
arraybuffer,
|
||||||
|
dataview,
|
||||||
|
date,
|
||||||
|
generator,
|
||||||
|
iterator,
|
||||||
|
map,
|
||||||
|
node,
|
||||||
|
promise,
|
||||||
|
proxy,
|
||||||
|
regexp,
|
||||||
|
set,
|
||||||
|
typedarray,
|
||||||
|
wasmvalue,
|
||||||
|
weakmap,
|
||||||
|
weakset,
|
||||||
|
webassemblymemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
// When we map a Zig instance into a JsObject, we'll normally store the a
|
||||||
|
// TaggedAnyOpaque (TAO) inside of the JsObject's internal field. This requires
|
||||||
|
// ensuring that the instance template has an InternalFieldCount of 1. However,
|
||||||
|
// for empty objects, we don't need to store the TAO, because we can't just cast
|
||||||
|
// one empty object to another, so for those, as an optimization, we do not set
|
||||||
|
// the InternalFieldCount.
|
||||||
|
pub fn isEmpty(comptime T: type) bool {
|
||||||
|
return @typeInfo(T) != .@"opaque" and @sizeOf(T) == 0 and @hasDecl(T, "js_legacy_factory") == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a struct:
|
||||||
|
// const Cat = struct {
|
||||||
|
// pub fn meow(self: *Cat) void { ... }
|
||||||
|
// }
|
||||||
|
// Then obviously, the receiver of its methods are going to be a *Cat (or *const Cat)
|
||||||
|
//
|
||||||
|
// However, we can also do:
|
||||||
|
// const Cat = struct {
|
||||||
|
// pub const Self = OtherImpl;
|
||||||
|
// pub fn meow(self: *OtherImpl) void { ... }
|
||||||
|
// }
|
||||||
|
// In which case, as we see above, the receiver is derived from the Self declaration
|
||||||
|
pub fn Receiver(comptime Struct: type) type {
|
||||||
|
return if (@hasDecl(Struct, "Self")) Struct.Self else Struct;
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ pub const Page = struct {
|
|||||||
|
|
||||||
// Our JavaScript context for this specific page. This is what we use to
|
// Our JavaScript context for this specific page. This is what we use to
|
||||||
// execute any JavaScript
|
// execute any JavaScript
|
||||||
main_context: *js.JsContext,
|
main_context: *js.Context,
|
||||||
|
|
||||||
// indicates intention to navigate to another page on the next loop execution.
|
// indicates intention to navigate to another page on the next loop execution.
|
||||||
delayed_navigation: bool = false,
|
delayed_navigation: bool = false,
|
||||||
@@ -143,7 +143,7 @@ pub const Page = struct {
|
|||||||
.main_context = undefined,
|
.main_context = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.main_context = try session.executor.createJsContext(&self.window, self, &self.script_manager, true, js.GlobalMissingCallback.init(&self.polyfill_loader));
|
self.main_context = try session.executor.createContext(&self.window, self, &self.script_manager, true, js.GlobalMissingCallback.init(&self.polyfill_loader));
|
||||||
try polyfill.preload(self.arena, self.main_context);
|
try polyfill.preload(self.arena, self.main_context);
|
||||||
|
|
||||||
try self.scheduler.add(self, runMicrotasks, 5, .{ .name = "page.microtasks" });
|
try self.scheduler.add(self, runMicrotasks, 5, .{ .name = "page.microtasks" });
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ pub const Loader = struct {
|
|||||||
webcomponents: bool = false,
|
webcomponents: bool = false,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
fn load(self: *Loader, comptime name: []const u8, source: []const u8, js_context: *js.JsContext) void {
|
fn load(self: *Loader, comptime name: []const u8, source: []const u8, js_context: *js.Context) void {
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(js_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
@@ -49,7 +49,7 @@ pub const Loader = struct {
|
|||||||
@field(self.done, name) = true;
|
@field(self.done, name) = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn missing(self: *Loader, name: []const u8, js_context: *js.JsContext) bool {
|
pub fn missing(self: *Loader, name: []const u8, js_context: *js.Context) bool {
|
||||||
// Avoid recursive calls during polyfill loading.
|
// Avoid recursive calls during polyfill loading.
|
||||||
if (self.state == .loading) {
|
if (self.state == .loading) {
|
||||||
return false;
|
return false;
|
||||||
@@ -82,7 +82,7 @@ pub const Loader = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn preload(allocator: Allocator, js_context: *js.JsContext) !void {
|
pub fn preload(allocator: Allocator, js_context: *js.Context) !void {
|
||||||
var try_catch: js.TryCatch = undefined;
|
var try_catch: js.TryCatch = undefined;
|
||||||
try_catch.init(js_context);
|
try_catch.init(js_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ pub const Session = struct {
|
|||||||
// registered a destructor (e.g. XMLHttpRequest).
|
// registered a destructor (e.g. XMLHttpRequest).
|
||||||
// Should be called before we deinit the page, because these objects
|
// Should be called before we deinit the page, because these objects
|
||||||
// could be referencing it.
|
// could be referencing it.
|
||||||
self.executor.removeJsContext();
|
self.executor.removeContext();
|
||||||
|
|
||||||
self.page.?.deinit();
|
self.page.?.deinit();
|
||||||
self.page = null;
|
self.page = null;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const State = union(enum) {
|
|||||||
readable,
|
readable,
|
||||||
closed: ?[]const u8,
|
closed: ?[]const u8,
|
||||||
cancelled: ?[]const u8,
|
cancelled: ?[]const u8,
|
||||||
errored: js.JsObject,
|
errored: js.Object,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This promise resolves when a stream is canceled.
|
// This promise resolves when a stream is canceled.
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ pub fn _enqueue(self: *ReadableStreamDefaultController, chunk: ReadableStream.Ch
|
|||||||
try self.stream.pullIf();
|
try self.stream.pullIf();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _error(self: *ReadableStreamDefaultController, err: js.JsObject) !void {
|
pub fn _error(self: *ReadableStreamDefaultController, err: js.Object) !void {
|
||||||
self.stream.state = .{ .errored = err };
|
self.stream.state = .{ .errored = err };
|
||||||
|
|
||||||
if (self.stream.reader_resolver) |*rr| {
|
if (self.stream.reader_resolver) |*rr| {
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ pub const URLSearchParams = struct {
|
|||||||
const URLSearchParamsOpts = union(enum) {
|
const URLSearchParamsOpts = union(enum) {
|
||||||
qs: []const u8,
|
qs: []const u8,
|
||||||
form_data: *const FormData,
|
form_data: *const FormData,
|
||||||
js_obj: js.JsObject,
|
js_obj: js.Object,
|
||||||
};
|
};
|
||||||
pub fn constructor(opts_: ?URLSearchParamsOpts, page: *Page) !URLSearchParams {
|
pub fn constructor(opts_: ?URLSearchParamsOpts, page: *Page) !URLSearchParams {
|
||||||
const opts = opts_ orelse return .{ .entries = .{} };
|
const opts = opts_ orelse return .{ .entries = .{} };
|
||||||
|
|||||||
@@ -671,8 +671,8 @@ const IsolatedWorld = struct {
|
|||||||
self.executor.deinit();
|
self.executor.deinit();
|
||||||
}
|
}
|
||||||
pub fn removeContext(self: *IsolatedWorld) !void {
|
pub fn removeContext(self: *IsolatedWorld) !void {
|
||||||
if (self.executor.js_context == null) return error.NoIsolatedContextToRemove;
|
if (self.executor.context == null) return error.NoIsolatedContextToRemove;
|
||||||
self.executor.removeJsContext();
|
self.executor.removeContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
|
// The isolate world must share at least some of the state with the related page, specifically the DocumentHTML
|
||||||
@@ -681,15 +681,15 @@ const IsolatedWorld = struct {
|
|||||||
// This also means this pointer becomes invalid after removePage untill a new page is created.
|
// This also means this pointer becomes invalid after removePage untill a new page is created.
|
||||||
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
|
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
|
||||||
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
|
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
|
||||||
// if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
|
// if (self.executor.context != null) return error.Only1IsolatedContextSupported;
|
||||||
if (self.executor.js_context != null) {
|
if (self.executor.context != null) {
|
||||||
log.warn(.cdp, "not implemented", .{
|
log.warn(.cdp, "not implemented", .{
|
||||||
.feature = "createContext: Not implemented second isolated context creation",
|
.feature = "createContext: Not implemented second isolated context creation",
|
||||||
.info = "reuse existing context",
|
.info = "reuse existing context",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ = try self.executor.createJsContext(
|
_ = try self.executor.createContext(
|
||||||
&page.window,
|
&page.window,
|
||||||
page,
|
page,
|
||||||
null,
|
null,
|
||||||
@@ -703,7 +703,7 @@ const IsolatedWorld = struct {
|
|||||||
try self.createContext(page);
|
try self.createContext(page);
|
||||||
|
|
||||||
const loader = @import("../browser/polyfill/polyfill.zig");
|
const loader = @import("../browser/polyfill/polyfill.zig");
|
||||||
try loader.preload(arena, &self.executor.js_context.?);
|
try loader.preload(arena, &self.executor.context.?);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ fn resolveNode(cmd: anytype) !void {
|
|||||||
if (params.executionContextId) |context_id| {
|
if (params.executionContextId) |context_id| {
|
||||||
if (js_context.v8_context.debugContextId() != context_id) {
|
if (js_context.v8_context.debugContextId() != context_id) {
|
||||||
for (bc.isolated_worlds.items) |*isolated_world| {
|
for (bc.isolated_worlds.items) |*isolated_world| {
|
||||||
js_context = &(isolated_world.executor.js_context orelse return error.ContextNotFound);
|
js_context = &(isolated_world.executor.context orelse return error.ContextNotFound);
|
||||||
if (js_context.v8_context.debugContextId() == context_id) {
|
if (js_context.v8_context.debugContextId() == context_id) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ fn createIsolatedWorld(cmd: anytype) !void {
|
|||||||
const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess);
|
const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess);
|
||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
try world.createContextAndLoadPolyfills(bc.arena, page);
|
try world.createContextAndLoadPolyfills(bc.arena, page);
|
||||||
const js_context = &world.executor.js_context.?;
|
const js_context = &world.executor.context.?;
|
||||||
|
|
||||||
// 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
|
||||||
@@ -262,7 +262,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
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
|
||||||
bc.inspector.contextCreated(
|
bc.inspector.contextCreated(
|
||||||
&isolated_world.executor.js_context.?,
|
&isolated_world.executor.context.?,
|
||||||
isolated_world.name,
|
isolated_world.name,
|
||||||
"://",
|
"://",
|
||||||
aux_json,
|
aux_json,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="../testing.js"></script>
|
<script src="../testing.js"></script>
|
||||||
|
|
||||||
<script id=readable_stream>
|
<!-- <script id=readable_stream>
|
||||||
const stream = new ReadableStream({
|
const stream = new ReadableStream({
|
||||||
start(controller) {
|
start(controller) {
|
||||||
controller.enqueue("hello");
|
controller.enqueue("hello");
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
testing.expectEqual("hello", data.value);
|
testing.expectEqual("hello", data.value);
|
||||||
testing.expectEqual(false, data.done);
|
testing.expectEqual(false, data.done);
|
||||||
});
|
});
|
||||||
</script>
|
</script> -->
|
||||||
|
|
||||||
<script id=readable_stream_binary>
|
<script id=readable_stream_binary>
|
||||||
const input = new TextEncoder().encode('over 9000!');
|
const input = new TextEncoder().encode('over 9000!');
|
||||||
|
|||||||
Reference in New Issue
Block a user