diff --git a/src/app.zig b/src/app.zig
index 3b3772c47..a5ceab82c 100644
--- a/src/app.zig
+++ b/src/app.zig
@@ -4,7 +4,7 @@ const Allocator = std.mem.Allocator;
const log = @import("log.zig");
const Http = @import("http/Http.zig");
-const Platform = @import("runtime/js.zig").Platform;
+const Platform = @import("browser/js/js.zig").Platform;
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
const Notification = @import("notification.zig").Notification;
diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig
index 437d13c5a..82e760133 100644
--- a/src/browser/ScriptManager.zig
+++ b/src/browser/ScriptManager.zig
@@ -18,10 +18,10 @@
const std = @import("std");
+const js = @import("js/js.zig");
const log = @import("../log.zig");
const parser = @import("netsurf.zig");
-const Env = @import("env.zig").Env;
const Page = @import("page.zig").Page;
const DataURI = @import("DataURI.zig");
const Http = @import("../http/Http.zig");
@@ -627,7 +627,7 @@ const Script = struct {
const Callback = union(enum) {
string: []const u8,
- function: Env.Function,
+ function: js.Function,
};
const Source = union(enum) {
@@ -664,7 +664,7 @@ const Script = struct {
});
const js_context = page.main_context;
- var try_catch: Env.TryCatch = undefined;
+ var try_catch: js.TryCatch = undefined;
try_catch.init(js_context);
defer try_catch.deinit();
@@ -706,7 +706,7 @@ const Script = struct {
switch (callback) {
.string => |str| {
- var try_catch: Env.TryCatch = undefined;
+ var try_catch: js.TryCatch = undefined;
try_catch.init(page.main_context);
defer try_catch.deinit();
@@ -728,7 +728,7 @@ const Script = struct {
};
defer parser.eventDestroy(loadevt);
- var result: Env.Function.Result = undefined;
+ var result: js.Function.Result = undefined;
const iface = Event.toInterface(loadevt);
f.tryCall(void, .{iface}, &result) catch {
log.warn(.user_script, "script callback", .{
diff --git a/src/browser/State.zig b/src/browser/State.zig
index 17817e305..77165e5b8 100644
--- a/src/browser/State.zig
+++ b/src/browser/State.zig
@@ -26,7 +26,7 @@
// this quickly proved necessary, since different fields are needed on the same
// data at different levels of the prototype chain. This isn't memory efficient.
-const Env = @import("env.zig").Env;
+const js = @import("js/js.zig");
const parser = @import("netsurf.zig");
const DataSet = @import("html/DataSet.zig");
const ShadowRoot = @import("dom/shadow_root.zig").ShadowRoot;
@@ -34,8 +34,8 @@ const StyleSheet = @import("cssom/StyleSheet.zig");
const CSSStyleDeclaration = @import("cssom/CSSStyleDeclaration.zig");
// for HTMLScript (but probably needs to be added to more)
-onload: ?Env.Function = null,
-onerror: ?Env.Function = null,
+onload: ?js.Function = null,
+onerror: ?js.Function = null,
// for HTMLElement
style: CSSStyleDeclaration = .empty,
@@ -53,7 +53,7 @@ style_sheet: ?*StyleSheet = null,
// for dom/document
active_element: ?*parser.Element = null,
-adopted_style_sheets: ?Env.JsObject = null,
+adopted_style_sheets: ?js.JsObject = null,
// for HTMLSelectElement
// By default, if no option is explicitly selected, the first option should
diff --git a/src/browser/browser.zig b/src/browser/browser.zig
index 09ceef1ca..f787a6dbc 100644
--- a/src/browser/browser.zig
+++ b/src/browser/browser.zig
@@ -21,8 +21,8 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
+const js = @import("js/js.zig");
const State = @import("State.zig");
-const Env = @import("env.zig").Env;
const App = @import("../app.zig").App;
const Session = @import("session.zig").Session;
const Notification = @import("../notification.zig").Notification;
@@ -34,7 +34,7 @@ const HttpClient = @import("../http/Client.zig");
// You can create multiple browser instances.
// A browser contains only one session.
pub const Browser = struct {
- env: *Env,
+ env: *js.Env,
app: *App,
session: ?Session,
allocator: Allocator,
@@ -48,7 +48,7 @@ pub const Browser = struct {
pub fn init(app: *App) !Browser {
const allocator = app.allocator;
- const env = try Env.init(allocator, &app.platform, .{});
+ const env = try js.Env.init(allocator, &app.platform, .{});
errdefer env.deinit();
const notification = try Notification.init(allocator, app.notification);
diff --git a/src/browser/console/console.zig b/src/browser/console/console.zig
index 2ab3a5d34..16d3731aa 100644
--- a/src/browser/console/console.zig
+++ b/src/browser/console/console.zig
@@ -20,47 +20,47 @@ const std = @import("std");
const builtin = @import("builtin");
const log = @import("../../log.zig");
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
-const JsObject = @import("../env.zig").Env.JsObject;
pub const Console = struct {
// TODO: configurable writer
timers: std.StringHashMapUnmanaged(u32) = .{},
counts: std.StringHashMapUnmanaged(u32) = .{},
- pub fn _lp(values: []JsObject, page: *Page) !void {
+ pub fn _lp(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.fatal(.console, "lightpanda", .{ .args = try serializeValues(values, page) });
}
- pub fn _log(values: []JsObject, page: *Page) !void {
+ pub fn _log(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.info(.console, "info", .{ .args = try serializeValues(values, page) });
}
- pub fn _info(values: []JsObject, page: *Page) !void {
+ pub fn _info(values: []js.JsObject, page: *Page) !void {
return _log(values, page);
}
- pub fn _debug(values: []JsObject, page: *Page) !void {
+ pub fn _debug(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.debug(.console, "debug", .{ .args = try serializeValues(values, page) });
}
- pub fn _warn(values: []JsObject, page: *Page) !void {
+ pub fn _warn(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.warn(.console, "warn", .{ .args = try serializeValues(values, page) });
}
- pub fn _error(values: []JsObject, page: *Page) !void {
+ pub fn _error(values: []js.JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
@@ -132,7 +132,7 @@ pub const Console = struct {
log.warn(.console, "timer stop", .{ .label = label, .elapsed = elapsed - kv.value });
}
- pub fn _assert(assertion: JsObject, values: []JsObject, page: *Page) !void {
+ pub fn _assert(assertion: js.JsObject, values: []js.JsObject, page: *Page) !void {
if (assertion.isTruthy()) {
return;
}
@@ -143,7 +143,7 @@ pub const Console = struct {
log.info(.console, "assertion failed", .{ .values = serialized_values });
}
- fn serializeValues(values: []JsObject, page: *Page) ![]const u8 {
+ fn serializeValues(values: []js.JsObject, page: *Page) ![]const u8 {
if (values.len == 0) {
return "";
}
diff --git a/src/browser/crypto/crypto.zig b/src/browser/crypto/crypto.zig
index 27813b4b7..8d69176aa 100644
--- a/src/browser/crypto/crypto.zig
+++ b/src/browser/crypto/crypto.zig
@@ -17,14 +17,14 @@
// along with this program. If not, see .
const std = @import("std");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const uuidv4 = @import("../../id.zig").uuidv4;
// https://w3c.github.io/webcrypto/#crypto-interface
pub const Crypto = struct {
_not_empty: bool = true,
- pub fn _getRandomValues(_: *const Crypto, js_obj: Env.JsObject) !Env.JsObject {
+ pub fn _getRandomValues(_: *const Crypto, js_obj: js.JsObject) !js.JsObject {
var into = try js_obj.toZig(Crypto, "getRandomValues", RandomValues);
const buf = into.asBuffer();
if (buf.len > 65_536) {
diff --git a/src/browser/cssom/CSSStyleSheet.zig b/src/browser/cssom/CSSStyleSheet.zig
index b963cc57a..13d2a0a62 100644
--- a/src/browser/cssom/CSSStyleSheet.zig
+++ b/src/browser/cssom/CSSStyleSheet.zig
@@ -18,7 +18,7 @@
const std = @import("std");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const StyleSheet = @import("StyleSheet.zig");
const CSSRuleList = @import("CSSRuleList.zig");
@@ -73,7 +73,7 @@ pub fn _deleteRule(self: *CSSStyleSheet, index: usize) !void {
_ = self.css_rules.list.orderedRemove(index);
}
-pub fn _replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !Env.Promise {
+pub fn _replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise {
_ = self;
_ = text;
// TODO: clear self.css_rules
diff --git a/src/browser/dom/Animation.zig b/src/browser/dom/Animation.zig
index 090c8aa73..8f25af6e0 100644
--- a/src/browser/dom/Animation.zig
+++ b/src/browser/dom/Animation.zig
@@ -18,19 +18,17 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
-const JsObject = @import("../env.zig").JsObject;
-const Promise = @import("../env.zig").Promise;
-const PromiseResolver = @import("../env.zig").PromiseResolver;
const Animation = @This();
-effect: ?JsObject,
-timeline: ?JsObject,
-ready_resolver: ?PromiseResolver,
-finished_resolver: ?PromiseResolver,
+effect: ?js.JsObject,
+timeline: ?js.JsObject,
+ready_resolver: ?js.PromiseResolver,
+finished_resolver: ?js.PromiseResolver,
-pub fn constructor(effect: ?JsObject, timeline: ?JsObject) !Animation {
+pub fn constructor(effect: ?js.JsObject, timeline: ?js.JsObject) !Animation {
return .{
.effect = if (effect) |eo| try eo.persist() else null,
.timeline = if (timeline) |to| try to.persist() else null,
@@ -49,7 +47,7 @@ pub fn get_pending(self: *const Animation) bool {
return false;
}
-pub fn get_finished(self: *Animation, page: *Page) !Promise {
+pub fn get_finished(self: *Animation, page: *Page) !js.Promise {
if (self.finished_resolver == null) {
const resolver = page.main_context.createPromiseResolver();
try resolver.resolve(self);
@@ -58,7 +56,7 @@ pub fn get_finished(self: *Animation, page: *Page) !Promise {
return self.finished_resolver.?.promise();
}
-pub fn get_ready(self: *Animation, page: *Page) !Promise {
+pub fn get_ready(self: *Animation, page: *Page) !js.Promise {
// never resolved, because we're always "finished"
if (self.ready_resolver == null) {
const resolver = page.main_context.createPromiseResolver();
@@ -67,19 +65,19 @@ pub fn get_ready(self: *Animation, page: *Page) !Promise {
return self.ready_resolver.?.promise();
}
-pub fn get_effect(self: *const Animation) ?JsObject {
+pub fn get_effect(self: *const Animation) ?js.JsObject {
return self.effect;
}
-pub fn set_effect(self: *Animation, effect: JsObject) !void {
+pub fn set_effect(self: *Animation, effect: js.JsObject) !void {
self.effect = try effect.persist();
}
-pub fn get_timeline(self: *const Animation) ?JsObject {
+pub fn get_timeline(self: *const Animation) ?js.JsObject {
return self.timeline;
}
-pub fn set_timeline(self: *Animation, timeline: JsObject) !void {
+pub fn set_timeline(self: *Animation, timeline: js.JsObject) !void {
self.timeline = try timeline.persist();
}
diff --git a/src/browser/dom/MessageChannel.zig b/src/browser/dom/MessageChannel.zig
index c2816616a..354c990b0 100644
--- a/src/browser/dom/MessageChannel.zig
+++ b/src/browser/dom/MessageChannel.zig
@@ -20,13 +20,11 @@ const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
-const JsObject = Env.JsObject;
-const Function = Env.Function;
const Allocator = std.mem.Allocator;
const MAX_QUEUE_SIZE = 10;
@@ -72,22 +70,22 @@ pub const MessagePort = struct {
pair: *MessagePort,
closed: bool = false,
started: bool = false,
- onmessage_cbk: ?Function = null,
- onmessageerror_cbk: ?Function = null,
+ onmessage_cbk: ?js.Function = null,
+ onmessageerror_cbk: ?js.Function = null,
// This is the queue of messages to dispatch to THIS MessagePort when the
// MessagePort is started.
- queue: std.ArrayListUnmanaged(JsObject) = .empty,
+ queue: std.ArrayListUnmanaged(js.JsObject) = .empty,
pub const PostMessageOption = union(enum) {
- transfer: JsObject,
+ transfer: js.JsObject,
options: Opts,
pub const Opts = struct {
- transfer: JsObject,
+ transfer: js.JsObject,
};
};
- pub fn _postMessage(self: *MessagePort, obj: JsObject, opts_: ?PostMessageOption, page: *Page) !void {
+ pub fn _postMessage(self: *MessagePort, obj: js.JsObject, opts_: ?PostMessageOption, page: *Page) !void {
if (self.closed) {
return;
}
@@ -124,10 +122,10 @@ pub const MessagePort = struct {
self.pair.closed = true;
}
- pub fn get_onmessage(self: *MessagePort) ?Function {
+ pub fn get_onmessage(self: *MessagePort) ?js.Function {
return self.onmessage_cbk;
}
- pub fn get_onmessageerror(self: *MessagePort) ?Function {
+ pub fn get_onmessageerror(self: *MessagePort) ?js.Function {
return self.onmessageerror_cbk;
}
@@ -152,7 +150,7 @@ pub const MessagePort = struct {
// called from our pair. If port1.postMessage("x") is called, then this
// will be called on port2.
- fn dispatchOrQueue(self: *MessagePort, obj: JsObject, arena: Allocator) !void {
+ fn dispatchOrQueue(self: *MessagePort, obj: js.JsObject, arena: Allocator) !void {
// our pair should have checked this already
std.debug.assert(self.closed == false);
@@ -167,7 +165,7 @@ pub const MessagePort = struct {
return self.queue.append(arena, try obj.persist());
}
- fn dispatch(self: *MessagePort, obj: JsObject) !void {
+ fn dispatch(self: *MessagePort, obj: js.JsObject) !void {
// obj is already persisted, don't use `MessageEvent.constructor`, but
// go directly to `init`, which assumes persisted objects.
var evt = try MessageEvent.init(.{ .data = obj });
@@ -182,7 +180,7 @@ pub const MessagePort = struct {
alloc: Allocator,
typ: []const u8,
listener: EventHandler.Listener,
- ) !?Function {
+ ) !?js.Function {
const target = @as(*parser.EventTarget, @ptrCast(self));
const eh = (try EventHandler.register(alloc, target, typ, listener, null)) orelse unreachable;
return eh.callback;
@@ -207,12 +205,12 @@ pub const MessageEvent = struct {
pub const union_make_copy = true;
proto: parser.Event,
- data: ?JsObject,
+ data: ?js.JsObject,
// 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
// null. It can always be set explicitly via the constructor;
- source: ?JsObject,
+ source: ?js.JsObject,
origin: []const u8,
@@ -226,8 +224,8 @@ pub const MessageEvent = struct {
ports: []*MessagePort,
const Options = struct {
- data: ?JsObject = null,
- source: ?JsObject = null,
+ data: ?js.JsObject = null,
+ source: ?js.JsObject = null,
origin: []const u8 = "",
lastEventId: []const u8 = "",
ports: []*MessagePort = &.{},
@@ -243,7 +241,7 @@ pub const MessageEvent = struct {
});
}
- // This is like "constructor", but it assumes JsObjects have already been
+ // This is like "constructor", but it assumes js.JsObjects have already been
// persisted. Necessary because this `new MessageEvent()` can be called
// 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);
@@ -263,7 +261,7 @@ pub const MessageEvent = struct {
};
}
- pub fn get_data(self: *const MessageEvent) !?JsObject {
+ pub fn get_data(self: *const MessageEvent) !?js.JsObject {
return self.data;
}
@@ -271,7 +269,7 @@ pub const MessageEvent = struct {
return self.origin;
}
- pub fn get_source(self: *const MessageEvent) ?JsObject {
+ pub fn get_source(self: *const MessageEvent) ?js.JsObject {
return self.source;
}
diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig
index 6dfd069bc..002333f67 100644
--- a/src/browser/dom/document.zig
+++ b/src/browser/dom/document.zig
@@ -18,6 +18,7 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page;
@@ -37,8 +38,6 @@ const Range = @import("range.zig").Range;
const CustomEvent = @import("../events/custom_event.zig").CustomEvent;
-const Env = @import("../env.zig").Env;
-
const DOMImplementation = @import("implementation.zig").DOMImplementation;
// WEB IDL https://dom.spec.whatwg.org/#document
@@ -155,13 +154,13 @@ pub const Document = struct {
// the spec changed to return an HTMLCollection instead.
// That's why we reimplemented getElementsByTagName by using an
// HTMLCollection in zig here.
- pub fn _getElementsByTagName(self: *parser.Document, tag_name: Env.String) !collection.HTMLCollection {
+ pub fn _getElementsByTagName(self: *parser.Document, tag_name: js.String) !collection.HTMLCollection {
return collection.HTMLCollectionByTagName(parser.documentToNode(self), tag_name.string, .{
.include_root = true,
});
}
- pub fn _getElementsByClassName(self: *parser.Document, class_names: Env.String) !collection.HTMLCollection {
+ pub fn _getElementsByClassName(self: *parser.Document, class_names: js.String) !collection.HTMLCollection {
return collection.HTMLCollectionByClassName(parser.documentToNode(self), class_names.string, .{
.include_root = true,
});
@@ -299,7 +298,7 @@ pub const Document = struct {
return &.{};
}
- pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !Env.JsObject {
+ pub fn get_adoptedStyleSheets(self: *parser.Document, page: *Page) !js.JsObject {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
if (state.adopted_style_sheets) |obj| {
return obj;
@@ -310,7 +309,7 @@ pub const Document = struct {
return obj;
}
- pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: Env.JsObject, page: *Page) !void {
+ pub fn set_adoptedStyleSheets(self: *parser.Document, sheets: js.JsObject, page: *Page) !void {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
state.adopted_style_sheets = try sheets.persist();
}
diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig
index a1a7f33e7..65b34dd43 100644
--- a/src/browser/dom/element.zig
+++ b/src/browser/dom/element.zig
@@ -18,8 +18,8 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const css = @import("css.zig");
@@ -34,7 +34,6 @@ const HTMLElem = @import("../html/elements.zig");
const ShadowRoot = @import("../dom/shadow_root.zig").ShadowRoot;
const Animation = @import("Animation.zig");
-const JsObject = @import("../env.zig").JsObject;
pub const Union = @import("../html/elements.zig").Union;
@@ -436,7 +435,7 @@ pub const Element = struct {
return try parser.elementRemoveAttributeNode(self, attr);
}
- pub fn _getElementsByTagName(self: *parser.Element, tag_name: Env.String) !collection.HTMLCollection {
+ pub fn _getElementsByTagName(self: *parser.Element, tag_name: js.String) !collection.HTMLCollection {
return collection.HTMLCollectionByTagName(
parser.elementToNode(self),
tag_name.string,
@@ -444,7 +443,7 @@ pub const Element = struct {
);
}
- pub fn _getElementsByClassName(self: *parser.Element, class_names: Env.String) !collection.HTMLCollection {
+ pub fn _getElementsByClassName(self: *parser.Element, class_names: js.String) !collection.HTMLCollection {
return try collection.HTMLCollectionByClassName(
parser.elementToNode(self),
class_names.string,
@@ -661,7 +660,7 @@ pub const Element = struct {
return sr;
}
- pub fn _animate(self: *parser.Element, effect: JsObject, opts: JsObject) !Animation {
+ pub fn _animate(self: *parser.Element, effect: js.JsObject, opts: js.JsObject) !Animation {
_ = self;
_ = opts;
return Animation.constructor(effect, null);
diff --git a/src/browser/dom/html_collection.zig b/src/browser/dom/html_collection.zig
index a3c7ccc01..34b957e2a 100644
--- a/src/browser/dom/html_collection.zig
+++ b/src/browser/dom/html_collection.zig
@@ -23,7 +23,6 @@ const parser = @import("../netsurf.zig");
const Element = @import("element.zig").Element;
const Union = @import("element.zig").Union;
-const JsThis = @import("../env.zig").JsThis;
const Walker = @import("walker.zig").Walker;
const Matcher = union(enum) {
diff --git a/src/browser/dom/intersection_observer.zig b/src/browser/dom/intersection_observer.zig
index c493a5399..8dccd29fa 100644
--- a/src/browser/dom/intersection_observer.zig
+++ b/src/browser/dom/intersection_observer.zig
@@ -18,11 +18,11 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page;
-const Env = @import("../env.zig").Env;
const Element = @import("element.zig").Element;
pub const Interfaces = .{
@@ -40,14 +40,14 @@ pub const Interfaces = .{
// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
pub const IntersectionObserver = struct {
page: *Page,
- callback: Env.Function,
+ callback: js.Function,
options: IntersectionObserverOptions,
observed_entries: std.ArrayListUnmanaged(IntersectionObserverEntry),
// new IntersectionObserver(callback)
// new IntersectionObserver(callback, options) [not supported yet]
- pub fn constructor(callback: Env.Function, options_: ?IntersectionObserverOptions, page: *Page) !IntersectionObserver {
+ pub fn constructor(callback: js.Function, options_: ?IntersectionObserverOptions, page: *Page) !IntersectionObserver {
var options = IntersectionObserverOptions{
.root = parser.documentToNode(parser.documentHTMLToDocument(page.window.document)),
.rootMargin = "0px 0px 0px 0px",
@@ -84,7 +84,7 @@ pub const IntersectionObserver = struct {
.options = &self.options,
});
- var result: Env.Function.Result = undefined;
+ var result: js.Function.Result = undefined;
self.callback.tryCall(void, .{self.observed_entries.items}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,
diff --git a/src/browser/dom/mutation_observer.zig b/src/browser/dom/mutation_observer.zig
index 24f5afa36..703156a76 100644
--- a/src/browser/dom/mutation_observer.zig
+++ b/src/browser/dom/mutation_observer.zig
@@ -18,11 +18,11 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page;
-const Env = @import("../env.zig").Env;
const NodeList = @import("nodelist.zig").NodeList;
pub const Interfaces = .{
@@ -35,7 +35,7 @@ const Walker = @import("../dom/walker.zig").WalkerChildren;
// WEB IDL https://dom.spec.whatwg.org/#interface-mutationobserver
pub const MutationObserver = struct {
page: *Page,
- cbk: Env.Function,
+ cbk: js.Function,
scheduled: bool,
observers: std.ArrayListUnmanaged(*Observer),
@@ -43,7 +43,7 @@ pub const MutationObserver = struct {
// execute our callback with it.
observed: std.ArrayListUnmanaged(MutationRecord),
- pub fn constructor(cbk: Env.Function, page: *Page) !MutationObserver {
+ pub fn constructor(cbk: js.Function, page: *Page) !MutationObserver {
return .{
.cbk = cbk,
.page = page,
@@ -122,7 +122,7 @@ pub const MutationObserver = struct {
defer self.observed.clearRetainingCapacity();
- var result: Env.Function.Result = undefined;
+ var result: js.Function.Result = undefined;
self.cbk.tryCallWithThis(void, self, .{records}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,
diff --git a/src/browser/dom/node.zig b/src/browser/dom/node.zig
index b95a549bb..71b88e0e9 100644
--- a/src/browser/dom/node.zig
+++ b/src/browser/dom/node.zig
@@ -20,7 +20,7 @@ const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
-const generate = @import("../../runtime/generate.zig");
+const generate = @import("../js/generate.zig");
const Page = @import("../page.zig").Page;
const EventTarget = @import("event_target.zig").EventTarget;
diff --git a/src/browser/dom/node_filter.zig b/src/browser/dom/node_filter.zig
index b8b9cc440..56cdd4930 100644
--- a/src/browser/dom/node_filter.zig
+++ b/src/browser/dom/node_filter.zig
@@ -17,8 +17,8 @@
// along with this program. If not, see .
const std = @import("std");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
const Node = @import("node.zig").Node;
pub const NodeFilter = struct {
@@ -43,7 +43,7 @@ pub const NodeFilter = struct {
const VerifyResult = enum { accept, skip, reject };
-pub fn verify(what_to_show: u32, filter: ?Env.Function, node: *parser.Node) !VerifyResult {
+pub fn verify(what_to_show: u32, filter: ?js.Function, node: *parser.Node) !VerifyResult {
const node_type = parser.nodeType(node);
// Verify that we can show this node type.
diff --git a/src/browser/dom/node_iterator.zig b/src/browser/dom/node_iterator.zig
index 5cce23994..ebeee7a22 100644
--- a/src/browser/dom/node_iterator.zig
+++ b/src/browser/dom/node_iterator.zig
@@ -18,8 +18,8 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
const NodeFilter = @import("node_filter.zig");
const Node = @import("node.zig").Node;
const NodeUnion = @import("node.zig").Union;
@@ -37,7 +37,7 @@ pub const NodeIterator = struct {
reference_node: *parser.Node,
what_to_show: u32,
filter: ?NodeIteratorOpts,
- filter_func: ?Env.Function,
+ filter_func: ?js.Function,
pointer_before_current: bool = true,
// used to track / block recursive filters
is_in_callback: bool = false,
@@ -45,15 +45,15 @@ pub const NodeIterator = struct {
// 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:
// null, undefined or i32.
- pub const WhatToShow = Env.JsObject;
+ pub const WhatToShow = js.JsObject;
pub const NodeIteratorOpts = union(enum) {
- function: Env.Function,
- object: struct { acceptNode: Env.Function },
+ function: js.Function,
+ object: struct { acceptNode: js.Function },
};
pub fn init(node: *parser.Node, what_to_show_: ?WhatToShow, filter: ?NodeIteratorOpts) !NodeIterator {
- var filter_func: ?Env.Function = null;
+ var filter_func: ?js.Function = null;
if (filter) |f| {
filter_func = switch (f) {
.function => |func| func,
diff --git a/src/browser/dom/nodelist.zig b/src/browser/dom/nodelist.zig
index 6ee5bca44..7a32f7bb3 100644
--- a/src/browser/dom/nodelist.zig
+++ b/src/browser/dom/nodelist.zig
@@ -19,11 +19,10 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
-const JsThis = @import("../env.zig").JsThis;
-const Function = @import("../env.zig").Function;
const NodeUnion = @import("node.zig").Union;
const Node = @import("node.zig").Node;
@@ -148,10 +147,10 @@ pub const NodeList = struct {
// };
// }
- pub fn _forEach(self: *NodeList, cbk: Function) !void { // TODO handle thisArg
+ pub fn _forEach(self: *NodeList, cbk: js.Function) !void { // TODO handle thisArg
for (self.nodes.items, 0..) |n, i| {
const ii: u32 = @intCast(i);
- var result: Function.Result = undefined;
+ var result: js.Function.Result = undefined;
cbk.tryCall(void, .{ n, ii, self }, &result) catch {
log.debug(.user_script, "forEach callback", .{ .err = result.exception, .stack = result.stack });
};
@@ -175,7 +174,7 @@ pub const NodeList = struct {
}
// TODO entries() https://developer.mozilla.org/en-US/docs/Web/API/NodeList/entries
- pub fn postAttach(self: *NodeList, js_this: JsThis) !void {
+ pub fn postAttach(self: *NodeList, js_this: js.JsThis) !void {
const len = self.get_length();
for (0..len) |i| {
const node = try self._item(@intCast(i)) orelse unreachable;
diff --git a/src/browser/dom/performance.zig b/src/browser/dom/performance.zig
index 09c0c6f87..d5de884dd 100644
--- a/src/browser/dom/performance.zig
+++ b/src/browser/dom/performance.zig
@@ -18,9 +18,9 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const EventTarget = @import("../dom/event_target.zig").EventTarget;
-const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const milliTimestamp = @import("../../datetime.zig").milliTimestamp;
@@ -61,7 +61,7 @@ pub const Performance = struct {
return milliTimestamp() - self.time_origin;
}
- pub fn _mark(_: *Performance, name: Env.String, _options: ?PerformanceMark.Options, page: *Page) !PerformanceMark {
+ pub fn _mark(_: *Performance, name: js.String, _options: ?PerformanceMark.Options, page: *Page) !PerformanceMark {
const mark: PerformanceMark = try .constructor(name, _options, page);
// TODO: Should store this in an entries list
return mark;
@@ -148,14 +148,14 @@ pub const PerformanceMark = struct {
pub const prototype = *PerformanceEntry;
proto: PerformanceEntry,
- detail: ?Env.JsObject,
+ detail: ?js.JsObject,
const Options = struct {
- detail: ?Env.JsObject = null,
+ detail: ?js.JsObject = null,
startTime: ?f64 = null,
};
- pub fn constructor(name: Env.String, _options: ?Options, page: *Page) !PerformanceMark {
+ pub fn constructor(name: js.String, _options: ?Options, page: *Page) !PerformanceMark {
const perf = &page.window.performance;
const options = _options orelse Options{};
@@ -171,7 +171,7 @@ pub const PerformanceMark = struct {
return .{ .proto = proto, .detail = detail };
}
- pub fn get_detail(self: *const PerformanceMark) ?Env.JsObject {
+ pub fn get_detail(self: *const PerformanceMark) ?js.JsObject {
return self.detail;
}
};
diff --git a/src/browser/dom/performance_observer.zig b/src/browser/dom/performance_observer.zig
index 5a53090d4..2e835ef8a 100644
--- a/src/browser/dom/performance_observer.zig
+++ b/src/browser/dom/performance_observer.zig
@@ -17,7 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const PerformanceEntry = @import("performance.zig").PerformanceEntry;
@@ -25,7 +25,7 @@ const PerformanceEntry = @import("performance.zig").PerformanceEntry;
pub const PerformanceObserver = struct {
pub const _supportedEntryTypes = [0][]const u8{};
- pub fn constructor(cbk: Env.Function) PerformanceObserver {
+ pub fn constructor(cbk: js.Function) PerformanceObserver {
_ = cbk;
return .{};
}
diff --git a/src/browser/dom/resize_observer.zig b/src/browser/dom/resize_observer.zig
index 4f1e37c35..448825ab6 100644
--- a/src/browser/dom/resize_observer.zig
+++ b/src/browser/dom/resize_observer.zig
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
pub const Interfaces = .{
@@ -25,7 +25,7 @@ pub const Interfaces = .{
// WEB IDL https://drafts.csswg.org/resize-observer/#resize-observer-interface
pub const ResizeObserver = struct {
- pub fn constructor(cbk: Env.Function) ResizeObserver {
+ pub fn constructor(cbk: js.Function) ResizeObserver {
_ = cbk;
return .{};
}
diff --git a/src/browser/dom/shadow_root.zig b/src/browser/dom/shadow_root.zig
index 6c230e18b..f7d6d1da4 100644
--- a/src/browser/dom/shadow_root.zig
+++ b/src/browser/dom/shadow_root.zig
@@ -20,7 +20,7 @@ const std = @import("std");
const dump = @import("../dump.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
+const js = @import(".././js/js.zig");
const Page = @import("../page.zig").Page;
const Node = @import("node.zig").Node;
const Element = @import("element.zig").Element;
@@ -34,7 +34,7 @@ pub const ShadowRoot = struct {
mode: Mode,
host: *parser.Element,
proto: *parser.DocumentFragment,
- adopted_style_sheets: ?Env.JsObject = null,
+ adopted_style_sheets: ?js.JsObject = null,
pub const Mode = enum {
open,
@@ -45,7 +45,7 @@ pub const ShadowRoot = struct {
return Element.toInterface(self.host);
}
- pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !Env.JsObject {
+ pub fn get_adoptedStyleSheets(self: *ShadowRoot, page: *Page) !js.JsObject {
if (self.adopted_style_sheets) |obj| {
return obj;
}
@@ -55,7 +55,7 @@ pub const ShadowRoot = struct {
return obj;
}
- pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: Env.JsObject) !void {
+ pub fn set_adoptedStyleSheets(self: *ShadowRoot, sheets: js.JsObject) !void {
self.adopted_style_sheets = try sheets.persist();
}
diff --git a/src/browser/dom/token_list.zig b/src/browser/dom/token_list.zig
index 37a78df53..4d989a90a 100644
--- a/src/browser/dom/token_list.zig
+++ b/src/browser/dom/token_list.zig
@@ -18,12 +18,11 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const iterator = @import("../iterator/iterator.zig");
-const Function = @import("../env.zig").Function;
-const JsObject = @import("../env.zig").JsObject;
const DOMException = @import("exceptions.zig").DOMException;
pub const Interfaces = .{
@@ -137,10 +136,10 @@ pub const DOMTokenList = struct {
}
// TODO handle thisArg
- pub fn _forEach(self: *parser.TokenList, cbk: Function, this_arg: JsObject) !void {
+ pub fn _forEach(self: *parser.TokenList, cbk: js.Function, this_arg: js.JsObject) !void {
var entries = _entries(self);
while (try entries._next()) |entry| {
- var result: Function.Result = undefined;
+ var result: js.Function.Result = undefined;
cbk.tryCallWithThis(void, this_arg, .{ entry.@"1", entry.@"0", self }, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,
diff --git a/src/browser/dom/tree_walker.zig b/src/browser/dom/tree_walker.zig
index 8ece93cc7..06a6552a5 100644
--- a/src/browser/dom/tree_walker.zig
+++ b/src/browser/dom/tree_walker.zig
@@ -17,10 +17,10 @@
// along with this program. If not, see .
const std = @import("std");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const NodeFilter = @import("node_filter.zig");
-const Env = @import("../env.zig").Env;
const Node = @import("node.zig").Node;
const NodeUnion = @import("node.zig").Union;
@@ -30,20 +30,20 @@ pub const TreeWalker = struct {
current_node: *parser.Node,
what_to_show: u32,
filter: ?TreeWalkerOpts,
- filter_func: ?Env.Function,
+ filter_func: ?js.Function,
// 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:
// null, undefined or i32.
- pub const WhatToShow = Env.JsObject;
+ pub const WhatToShow = js.JsObject;
pub const TreeWalkerOpts = union(enum) {
- function: Env.Function,
- object: struct { acceptNode: Env.Function },
+ function: js.Function,
+ object: struct { acceptNode: js.Function },
};
pub fn init(node: *parser.Node, what_to_show_: ?WhatToShow, filter: ?TreeWalkerOpts) !TreeWalker {
- var filter_func: ?Env.Function = null;
+ var filter_func: ?js.Function = null;
if (filter) |f| {
filter_func = switch (f) {
diff --git a/src/browser/encoding/TextDecoder.zig b/src/browser/encoding/TextDecoder.zig
index 811945b6e..a15ba4364 100644
--- a/src/browser/encoding/TextDecoder.zig
+++ b/src/browser/encoding/TextDecoder.zig
@@ -19,7 +19,6 @@
const std = @import("std");
const log = @import("../../log.zig");
-const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
// https://encoding.spec.whatwg.org/#interface-textdecoder
diff --git a/src/browser/encoding/TextEncoder.zig b/src/browser/encoding/TextEncoder.zig
index a1551e815..4ac32cd83 100644
--- a/src/browser/encoding/TextEncoder.zig
+++ b/src/browser/encoding/TextEncoder.zig
@@ -18,7 +18,7 @@
const std = @import("std");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
// https://encoding.spec.whatwg.org/#interface-textencoder
const TextEncoder = @This();
@@ -31,7 +31,7 @@ pub fn get_encoding(_: *const TextEncoder) []const u8 {
return "utf-8";
}
-pub fn _encode(_: *const TextEncoder, v: []const u8) !Env.TypedArray(u8) {
+pub fn _encode(_: *const TextEncoder, v: []const u8) !js.TypedArray(u8) {
// Ensure the input is a valid utf-8
// It seems chrome accepts invalid utf-8 sequence.
//
diff --git a/src/browser/env.zig b/src/browser/env.zig
deleted file mode 100644
index d7a20cf0d..000000000
--- a/src/browser/env.zig
+++ /dev/null
@@ -1,51 +0,0 @@
-const std = @import("std");
-
-const Page = @import("page.zig").Page;
-const js = @import("../runtime/js.zig");
-const generate = @import("../runtime/generate.zig");
-
-const WebApis = struct {
- // Wrapped like this for debug ergonomics.
- // When we create our Env, a few lines down, we define it as:
- // pub const Env = js.Env(*Page, WebApis);
- //
- // If there's a compile time error witht he Env, it's type will be readable,
- // i.e.: runtime.js.Env(*browser.env.Page, browser.env.WebApis)
- //
- // But if we didn't wrap it in the struct, like we once didn't, and defined
- // env as:
- // pub const Env = js.Env(*Page, Interfaces);
- //
- // Because Interfaces is an anynoumous type, it doesn't have a friendly name
- // and errors would be something like:
- // runtime.js.Env(*browser.Page, .{...A HUNDRED TYPES...})
- pub 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 JsThis = Env.JsThis;
-pub const JsObject = Env.JsObject;
-pub const Function = Env.Function;
-pub const Promise = Env.Promise;
-pub const PromiseResolver = Env.PromiseResolver;
-
-pub const Env = js.Env(*Page, WebApis);
-pub const Global = @import("html/window.zig").Window;
diff --git a/src/browser/events/custom_event.zig b/src/browser/events/custom_event.zig
index 530eaf5c8..bf54d2b2e 100644
--- a/src/browser/events/custom_event.zig
+++ b/src/browser/events/custom_event.zig
@@ -16,9 +16,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
const Event = @import("event.zig").Event;
-const JsObject = @import("../env.zig").JsObject;
+
const netsurf = @import("../netsurf.zig");
// https://dom.spec.whatwg.org/#interface-customevent
@@ -27,13 +28,13 @@ pub const CustomEvent = struct {
pub const union_make_copy = true;
proto: parser.Event,
- detail: ?JsObject,
+ detail: ?js.JsObject,
const CustomEventInit = struct {
bubbles: bool = false,
cancelable: bool = false,
composed: bool = false,
- detail: ?JsObject = null,
+ detail: ?js.JsObject = null,
};
pub fn constructor(event_type: []const u8, opts_: ?CustomEventInit) !CustomEvent {
@@ -53,7 +54,7 @@ pub const CustomEvent = struct {
};
}
- pub fn get_detail(self: *CustomEvent) ?JsObject {
+ pub fn get_detail(self: *CustomEvent) ?js.JsObject {
return self.detail;
}
@@ -64,7 +65,7 @@ pub const CustomEvent = struct {
event_type: []const u8,
can_bubble: bool,
cancelable: bool,
- maybe_detail: ?JsObject,
+ maybe_detail: ?js.JsObject,
) !void {
// This function can only be called after the constructor has called.
// So we assume proto is initialized already by constructor.
diff --git a/src/browser/events/event.zig b/src/browser/events/event.zig
index ef039fac0..7e399849a 100644
--- a/src/browser/events/event.zig
+++ b/src/browser/events/event.zig
@@ -21,7 +21,7 @@ const Allocator = std.mem.Allocator;
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
-const generate = @import("../../runtime/generate.zig");
+const generate = @import("../js/generate.zig");
const Page = @import("../page.zig").Page;
const Node = @import("../dom/node.zig").Node;
@@ -219,18 +219,17 @@ pub const Event = struct {
pub const EventHandler = struct {
once: bool,
capture: bool,
- callback: Function,
+ callback: js.Function,
node: parser.EventNode,
listener: *parser.EventListener,
- const Env = @import("../env.zig").Env;
- const Function = Env.Function;
+ const js = @import("../js/js.zig");
pub const Listener = union(enum) {
- function: Function,
- object: Env.JsObject,
+ function: js.Function,
+ object: js.JsObject,
- pub fn callback(self: Listener, target: *parser.EventTarget) !?Function {
+ pub fn callback(self: Listener, target: *parser.EventTarget) !?js.Function {
return switch (self) {
.function => |func| try func.withThis(target),
.object => |obj| blk: {
@@ -331,7 +330,7 @@ pub const EventHandler = struct {
fn handle(node: *parser.EventNode, event: *parser.Event) void {
const ievent = Event.toInterface(event);
const self: *EventHandler = @fieldParentPtr("node", node);
- var result: Function.Result = undefined;
+ var result: js.Function.Result = undefined;
self.callback.tryCall(void, .{ievent}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,
diff --git a/src/browser/fetch/Headers.zig b/src/browser/fetch/Headers.zig
index f7d83c3fd..9c78046e2 100644
--- a/src/browser/fetch/Headers.zig
+++ b/src/browser/fetch/Headers.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const URL = @import("../../url.zig").URL;
const Page = @import("../page.zig").Page;
@@ -24,8 +25,6 @@ const Page = @import("../page.zig").Page;
const iterator = @import("../iterator/iterator.zig");
const v8 = @import("v8");
-const Env = @import("../env.zig").Env;
-
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
const Headers = @This();
@@ -69,7 +68,7 @@ pub const HeadersInit = union(enum) {
// Headers
headers: *Headers,
// Mappings
- object: Env.JsObject,
+ object: js.JsObject,
};
pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers {
@@ -159,7 +158,7 @@ pub fn _entries(self: *const Headers) HeadersEntryIterable {
};
}
-pub fn _forEach(self: *Headers, callback_fn: Env.Function, this_arg: ?Env.JsObject) !void {
+pub fn _forEach(self: *Headers, callback_fn: js.Function, this_arg: ?js.JsObject) !void {
var iter = self.headers.iterator();
const cb = if (this_arg) |this| try callback_fn.withThis(this) else callback_fn;
diff --git a/src/browser/fetch/Request.zig b/src/browser/fetch/Request.zig
index 5881b427f..172c89765 100644
--- a/src/browser/fetch/Request.zig
+++ b/src/browser/fetch/Request.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const URL = @import("../../url.zig").URL;
@@ -27,7 +28,6 @@ const Http = @import("../../http/Http.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const v8 = @import("v8");
-const Env = @import("../env.zig").Env;
const Headers = @import("Headers.zig");
const HeadersInit = @import("Headers.zig").HeadersInit;
@@ -241,7 +241,7 @@ pub fn _clone(self: *Request) !Request {
};
}
-pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
+pub fn _bytes(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
@@ -253,7 +253,7 @@ pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
-pub fn _json(self: *Response, page: *Page) !Env.Promise {
+pub fn _json(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
@@ -280,7 +280,7 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
-pub fn _text(self: *Response, page: *Page) !Env.Promise {
+pub fn _text(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
diff --git a/src/browser/fetch/Response.zig b/src/browser/fetch/Response.zig
index d9530fb14..6320c5577 100644
--- a/src/browser/fetch/Response.zig
+++ b/src/browser/fetch/Response.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const v8 = @import("v8");
@@ -29,7 +30,6 @@ const ReadableStream = @import("../streams/ReadableStream.zig");
const Headers = @import("Headers.zig");
const HeadersInit = @import("Headers.zig").HeadersInit;
-const Env = @import("../env.zig").Env;
const Mime = @import("../mime.zig").Mime;
const Page = @import("../page.zig").Page;
@@ -165,12 +165,12 @@ pub fn _clone(self: *const Response) !Response {
};
}
-pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
+pub fn _bytes(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
- const resolver = Env.PromiseResolver{
+ const resolver = js.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
@@ -180,7 +180,7 @@ pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
-pub fn _json(self: *Response, page: *Page) !Env.Promise {
+pub fn _json(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
@@ -207,7 +207,7 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
return resolver.promise();
}
-pub fn _text(self: *Response, page: *Page) !Env.Promise {
+pub fn _text(self: *Response, page: *Page) !js.Promise {
if (self.body_used) {
return error.TypeError;
}
diff --git a/src/browser/fetch/fetch.zig b/src/browser/fetch/fetch.zig
index e1e29a819..3b538fe29 100644
--- a/src/browser/fetch/fetch.zig
+++ b/src/browser/fetch/fetch.zig
@@ -19,7 +19,7 @@
const std = @import("std");
const log = @import("../../log.zig");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
const Http = @import("../../http/Http.zig");
@@ -45,7 +45,7 @@ pub const Interfaces = .{
pub const FetchContext = struct {
page: *Page,
arena: std.mem.Allocator,
- promise_resolver: Env.PersistentPromiseResolver,
+ promise_resolver: js.PersistentPromiseResolver,
method: Http.Method,
url: []const u8,
@@ -111,7 +111,7 @@ pub const FetchContext = struct {
};
// https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
-pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promise {
+pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !js.Promise {
const arena = page.arena;
const req = try Request.constructor(input, options, page);
diff --git a/src/browser/html/AbortController.zig b/src/browser/html/AbortController.zig
index fba46e9d7..0f5b0f36f 100644
--- a/src/browser/html/AbortController.zig
+++ b/src/browser/html/AbortController.zig
@@ -17,9 +17,9 @@
// along with this program. If not, see .
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
@@ -113,7 +113,7 @@ pub const AbortSignal = struct {
}
const ThrowIfAborted = union(enum) {
- exception: Env.Exception,
+ exception: js.Exception,
undefined: void,
};
pub fn _throwIfAborted(self: *const AbortSignal, page: *Page) ThrowIfAborted {
diff --git a/src/browser/html/DataSet.zig b/src/browser/html/DataSet.zig
index e53657332..e234f9c43 100644
--- a/src/browser/html/DataSet.zig
+++ b/src/browser/html/DataSet.zig
@@ -17,7 +17,8 @@
// along with this program. If not, see .
const std = @import("std");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
+
const Page = @import("../page.zig").Page;
const Allocator = std.mem.Allocator;
@@ -26,7 +27,7 @@ const DataSet = @This();
element: *parser.Element,
-pub fn named_get(self: *const DataSet, name: []const u8, _: *bool, page: *Page) !Env.UndefinedOr([]const u8) {
+pub fn named_get(self: *const DataSet, name: []const u8, _: *bool, page: *Page) !js.UndefinedOr([]const u8) {
const normalized_name = try normalize(page.call_arena, name);
if (try parser.elementGetAttribute(self.element, normalized_name)) |value| {
return .{ .value = value };
diff --git a/src/browser/html/History.zig b/src/browser/html/History.zig
index 308bfb1fa..8111ba44a 100644
--- a/src/browser/html/History.zig
+++ b/src/browser/html/History.zig
@@ -19,7 +19,7 @@
const std = @import("std");
const log = @import("../../log.zig");
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const Page = @import("../page.zig").Page;
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-history-interface
@@ -67,11 +67,11 @@ pub fn set_scrollRestoration(self: *History, mode: []const u8) void {
self.scroll_restoration = ScrollRestorationMode.fromString(mode) orelse self.scroll_restoration;
}
-pub fn get_state(self: *History, page: *Page) !?Env.Value {
+pub fn get_state(self: *History, page: *Page) !?js.Value {
if (self.current) |curr| {
const entry = self.stack.items[curr];
if (entry.state) |state| {
- const value = try Env.Value.fromJson(page.main_context, state);
+ const value = try js.Value.fromJson(page.main_context, state);
return value;
} else {
return null;
@@ -113,7 +113,7 @@ fn _dispatchPopStateEvent(state: ?[]const u8, page: *Page) !void {
);
}
-pub fn _pushState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
+pub fn _pushState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
const arena = page.session.arena;
const json = try state.toJson(arena);
@@ -123,7 +123,7 @@ pub fn _pushState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]
self.current = self.stack.items.len - 1;
}
-pub fn _replaceState(self: *History, state: Env.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
+pub fn _replaceState(self: *History, state: js.JsObject, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
const arena = page.session.arena;
if (self.current) |curr| {
@@ -199,9 +199,9 @@ pub const PopStateEvent = struct {
// `hasUAVisualTransition` is not implemented. It isn't baseline so this is okay.
- pub fn get_state(self: *const PopStateEvent, page: *Page) !?Env.Value {
+ pub fn get_state(self: *const PopStateEvent, page: *Page) !?js.Value {
if (self.state) |state| {
- const value = try Env.Value.fromJson(page.main_context, state);
+ const value = try js.Value.fromJson(page.main_context, state);
return value;
} else {
return null;
diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig
index b74d7ca26..e34915c27 100644
--- a/src/browser/html/elements.zig
+++ b/src/browser/html/elements.zig
@@ -18,9 +18,9 @@
const std = @import("std");
const log = @import("../../log.zig");
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
-const generate = @import("../../runtime/generate.zig");
-const Env = @import("../env.zig").Env;
+const generate = @import("../js/generate.zig");
const Page = @import("../page.zig").Page;
const urlStitch = @import("../../url.zig").URL.stitch;
@@ -1000,22 +1000,22 @@ pub const HTMLScriptElement = struct {
);
}
- pub fn get_onload(self: *parser.Script, page: *Page) !?Env.Function {
+ pub fn get_onload(self: *parser.Script, page: *Page) !?js.Function {
const state = page.getNodeState(@ptrCast(@alignCast(self))) orelse return null;
return state.onload;
}
- pub fn set_onload(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
+ pub fn set_onload(self: *parser.Script, function: ?js.Function, page: *Page) !void {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
state.onload = function;
}
- pub fn get_onerror(self: *parser.Script, page: *Page) !?Env.Function {
+ pub fn get_onerror(self: *parser.Script, page: *Page) !?js.Function {
const state = page.getNodeState(@ptrCast(@alignCast(self))) orelse return null;
return state.onerror;
}
- pub fn set_onerror(self: *parser.Script, function: ?Env.Function, page: *Page) !void {
+ pub fn set_onerror(self: *parser.Script, function: ?js.Function, page: *Page) !void {
const state = try page.getOrCreateNodeState(@ptrCast(@alignCast(self)));
state.onerror = function;
}
diff --git a/src/browser/html/error_event.zig b/src/browser/html/error_event.zig
index ee006e225..315584181 100644
--- a/src/browser/html/error_event.zig
+++ b/src/browser/html/error_event.zig
@@ -15,7 +15,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-const Env = @import("../env.zig").Env;
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
@@ -28,14 +28,14 @@ pub const ErrorEvent = struct {
filename: []const u8,
lineno: i32,
colno: i32,
- @"error": ?Env.JsObject,
+ @"error": ?js.JsObject,
const ErrorEventInit = struct {
message: []const u8 = "",
filename: []const u8 = "",
lineno: i32 = 0,
colno: i32 = 0,
- @"error": ?Env.JsObject = null,
+ @"error": ?js.JsObject = null,
};
pub fn constructor(event_type: []const u8, opts: ?ErrorEventInit) !ErrorEvent {
@@ -72,7 +72,7 @@ pub const ErrorEvent = struct {
return self.colno;
}
- pub fn get_error(self: *const ErrorEvent) Env.UndefinedOr(Env.JsObject) {
+ pub fn get_error(self: *const ErrorEvent) js.UndefinedOr(js.JsObject) {
if (self.@"error") |e| {
return .{ .value = e };
}
diff --git a/src/browser/html/media_query_list.zig b/src/browser/html/media_query_list.zig
index 28216db35..4d05b7c9e 100644
--- a/src/browser/html/media_query_list.zig
+++ b/src/browser/html/media_query_list.zig
@@ -16,8 +16,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
+const js = @import("../js/js.zig");
const parser = @import("../netsurf.zig");
-const Function = @import("../env.zig").Function;
const EventTarget = @import("../dom/event_target.zig").EventTarget;
// https://drafts.csswg.org/cssom-view/#the-mediaquerylist-interface
@@ -39,7 +39,7 @@ pub const MediaQueryList = struct {
return self.media;
}
- pub fn _addListener(_: *const MediaQueryList, _: Function) void {}
+ pub fn _addListener(_: *const MediaQueryList, _: js.Function) void {}
- pub fn _removeListener(_: *const MediaQueryList, _: Function) void {}
+ pub fn _removeListener(_: *const MediaQueryList, _: js.Function) void {}
};
diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig
index 19e21c8e1..1ca681b3e 100644
--- a/src/browser/html/window.zig
+++ b/src/browser/html/window.zig
@@ -18,9 +18,9 @@
const std = @import("std");
+const js = @import("../js/js.zig");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
-const Env = @import("../env.zig").Env;
const Page = @import("../page.zig").Page;
const Navigator = @import("navigator.zig").Navigator;
@@ -37,8 +37,6 @@ const domcss = @import("../dom/css.zig");
const Css = @import("../css/css.zig").Css;
const EventHandler = @import("../events/event.zig").EventHandler;
-const Function = Env.Function;
-
const v8 = @import("v8");
const Request = @import("../fetch/Request.zig");
const fetchFn = @import("../fetch/fetch.zig").fetch;
@@ -70,7 +68,7 @@ pub const Window = struct {
css: Css = .{},
scroll_x: u32 = 0,
scroll_y: u32 = 0,
- onload_callback: ?Function = null,
+ onload_callback: ?js.Function = null,
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
var fbs = std.io.fixedBufferStream("");
@@ -101,12 +99,12 @@ pub const Window = struct {
self.storage_shelf = shelf;
}
- pub fn _fetch(_: *Window, input: Request.RequestInput, options: ?Request.RequestInit, page: *Page) !Env.Promise {
+ pub fn _fetch(_: *Window, input: Request.RequestInput, options: ?Request.RequestInit, page: *Page) !js.Promise {
return fetchFn(input, options, page);
}
/// Returns `onload_callback`.
- pub fn get_onload(self: *const Window) ?Function {
+ pub fn get_onload(self: *const Window) ?js.Function {
return self.onload_callback;
}
@@ -258,7 +256,7 @@ pub const Window = struct {
return &self.css;
}
- pub fn _requestAnimationFrame(self: *Window, cbk: Function, page: *Page) !u32 {
+ pub fn _requestAnimationFrame(self: *Window, cbk: js.Function, page: *Page) !u32 {
return self.createTimeout(cbk, 5, page, .{
.animation_frame = true,
.name = "animationFrame",
@@ -270,11 +268,11 @@ pub const Window = struct {
_ = self.timers.remove(id);
}
- pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, params: []Env.JsObject, page: *Page) !u32 {
+ pub fn _setTimeout(self: *Window, cbk: js.Function, delay: ?u32, params: []js.JsObject, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .args = params, .name = "setTimeout" });
}
- pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, params: []Env.JsObject, page: *Page) !u32 {
+ pub fn _setInterval(self: *Window, cbk: js.Function, delay: ?u32, params: []js.JsObject, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, .{ .repeat = true, .args = params, .name = "setInterval" });
}
@@ -286,11 +284,11 @@ pub const Window = struct {
_ = self.timers.remove(id);
}
- pub fn _queueMicrotask(self: *Window, cbk: Function, page: *Page) !u32 {
+ pub fn _queueMicrotask(self: *Window, cbk: js.Function, page: *Page) !u32 {
return self.createTimeout(cbk, 0, page, .{ .name = "queueMicrotask" });
}
- pub fn _setImmediate(self: *Window, cbk: Function, page: *Page) !u32 {
+ pub fn _setImmediate(self: *Window, cbk: js.Function, page: *Page) !u32 {
return self.createTimeout(cbk, 0, page, .{ .name = "setImmediate" });
}
@@ -298,7 +296,7 @@ pub const Window = struct {
_ = self.timers.remove(id);
}
- pub fn _matchMedia(_: *const Window, media: Env.String) !MediaQueryList {
+ pub fn _matchMedia(_: *const Window, media: js.String) !MediaQueryList {
return .{
.matches = false, // TODO?
.media = media.string,
@@ -322,12 +320,12 @@ pub const Window = struct {
const CreateTimeoutOpts = struct {
name: []const u8,
- args: []Env.JsObject = &.{},
+ args: []js.JsObject = &.{},
repeat: bool = false,
animation_frame: bool = false,
low_priority: bool = false,
};
- fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, page: *Page, opts: CreateTimeoutOpts) !u32 {
+ fn createTimeout(self: *Window, cbk: js.Function, delay_: ?u32, page: *Page, opts: CreateTimeoutOpts) !u32 {
const delay = delay_ orelse 0;
if (self.timers.count() > 512) {
return error.TooManyTimeout;
@@ -347,9 +345,9 @@ pub const Window = struct {
errdefer _ = self.timers.remove(timer_id);
const args = opts.args;
- var persisted_args: []Env.JsObject = &.{};
+ var persisted_args: []js.JsObject = &.{};
if (args.len > 0) {
- persisted_args = try page.arena.alloc(Env.JsObject, args.len);
+ persisted_args = try page.arena.alloc(js.JsObject, args.len);
for (args, persisted_args) |a, *ca| {
ca.* = try a.persist();
}
@@ -476,13 +474,13 @@ const TimerCallback = struct {
repeat: ?u32,
// The JavaScript callback to execute
- cbk: Function,
+ cbk: js.Function,
animation_frame: bool = false,
window: *Window,
- args: []Env.JsObject = &.{},
+ args: []js.JsObject = &.{},
fn run(ctx: *anyopaque) ?u32 {
const self: *TimerCallback = @ptrCast(@alignCast(ctx));
@@ -496,7 +494,7 @@ const TimerCallback = struct {
return null;
}
- var result: Function.Result = undefined;
+ var result: js.Function.Result = undefined;
var call: anyerror!void = undefined;
if (self.animation_frame) {
diff --git a/src/runtime/generate.zig b/src/browser/js/generate.zig
similarity index 98%
rename from src/runtime/generate.zig
rename to src/browser/js/generate.zig
index c5000cba5..e2cf010a8 100644
--- a/src/runtime/generate.zig
+++ b/src/browser/js/generate.zig
@@ -190,7 +190,7 @@ test "generate: Union" {
const value = Union(.{ Astruct, Bstruct, .{Cstruct} });
const ti = @typeInfo(value).@"union";
try std.testing.expectEqual(3, ti.fields.len);
- try std.testing.expectEqualStrings("*runtime.generate.test.generate: Union.Astruct.Other", @typeName(ti.fields[0].type));
+ try std.testing.expectEqualStrings("*browser.js.generate.test.generate: Union.Astruct.Other", @typeName(ti.fields[0].type));
try std.testing.expectEqualStrings(ti.fields[0].name, "Astruct");
try std.testing.expectEqual(*Bstruct, ti.fields[1].type);
try std.testing.expectEqualStrings(ti.fields[1].name, "Bstruct");
diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig
new file mode 100644
index 000000000..d59823916
--- /dev/null
+++ b/src/browser/js/js.zig
@@ -0,0 +1,4331 @@
+// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
+//
+// Francis Bouvier
+// Pierre Tachoire
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+const std = @import("std");
+const builtin = @import("builtin");
+const v8 = @import("v8");
+
+const log = @import("../../log.zig");
+const generate = @import("generate.zig");
+
+const SubType = @import("subtype.zig").SubType;
+const Page = @import("../page.zig").Page;
+const ScriptManager = @import("../ScriptManager.zig");
+
+const Allocator = std.mem.Allocator;
+const ArenaAllocator = std.heap.ArenaAllocator;
+
+const CALL_ARENA_RETAIN = 1024 * 16;
+const CONTEXT_ARENA_RETAIN = 1024 * 64;
+
+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,
+});
+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`? `TypeLookup` is a struct
+// that looks like:
+//
+// const TypeLookup = 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));
+//
+const TypeLookup = 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
+ // TypeLookup. 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,
+ } });
+};
+
+const TYPE_LOOKUP = TypeLookup{};
+
+// 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.
+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(TYPE_LOOKUP, proto_name);
+ }
+ table[i] = prototype_index;
+ }
+ break :blk table;
+};
+
+// Global, should only be initialized once.
+pub const Platform = struct {
+ 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();
+ }
+};
+
+// 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.
+pub const Env = struct {
+ 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]TypeMeta,
+
+ 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(JsContext.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 js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
+ js_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(TYPE_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(Receiver(TI.pointer.child));
+ if (@hasField(TypeLookup, 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 TYPE_LOOKUP.
+ // Just like we said above, given a type, we can get its
+ // template index.
+
+ const proto_index = @field(TYPE_LOOKUP, proto_name);
+ templates[i].inherit(templates[proto_index]);
+ }
+
+ // while we're here, let's populate our meta lookup
+ const subtype: ?SubType = 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,
+ .js_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: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
+
+ const value =
+ if (msg.getValue()) |v8_value| valueToString(context.call_arena, v8_value, isolate, v8_context) catch |err| @errorName(err) else "no value";
+
+ log.debug(.js, "unhandled rejection", .{ .value = value });
+ }
+};
+
+// 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
+pub const ExecutionWorld = struct {
+ 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 JsContext, 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 JsContext, 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.
+ js_context: ?JsContext = null,
+
+ // no init, must be initialized via env.newExecutionWorld()
+
+ pub fn deinit(self: *ExecutionWorld) void {
+ if (self.js_context != null) {
+ self.removeJsContext();
+ }
+
+ self.call_arena.deinit();
+ self.context_arena.deinit();
+ }
+
+ // Only the top JsContext 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 createJsContext(self: *ExecutionWorld, global: anytype, page: *Page, script_manager: ?*ScriptManager, enter: bool, global_callback: ?GlobalMissingCallback) !*JsContext {
+ std.debug.assert(self.js_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);
+ 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 js_context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
+
+ const property = valueToString(js_context.call_arena, .{ .handle = c_name.? }, _isolate, v8_context) catch "???";
+ if (js_context.global_callback.?.missing(property, js_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 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 = Receiver(@typeInfo(Global.prototype).pointer.child);
+ const proto_name = @typeName(proto_type);
+ const proto_index = @field(TYPE_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 = Receiver(@typeInfo(Struct.prototype).pointer.child);
+ const proto_name = @typeName(proto_type);
+ if (@hasField(TypeLookup, proto_name) == false) {
+ @compileError("Type '" ++ @typeName(Struct) ++ "' defines an unknown prototype: " ++ proto_name);
+ }
+
+ const proto_index = @field(TYPE_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.js_context = JsContext{
+ .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 js_context = &self.js_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(js_context)));
+ v8_context.setEmbedderData(1, data);
+ }
+
+ page.call_arena = js_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 classNameForStruct(Struct) ++ ".prototype.__proto__ = Error.prototype";
+ _ = try js_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 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 js_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 js_context._mapZigInstanceToJs(v8_context.getGlobal(), global);
+ return js_context;
+ }
+
+ pub fn removeJsContext(self: *ExecutionWorld) void {
+ self.js_context.?.deinit();
+ self.js_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();
+ }
+};
+
+const PersistentObject = v8.Persistent(v8.Object);
+const PersistentModule = v8.Persistent(v8.Module);
+const PersistentPromise = v8.Persistent(v8.Promise);
+const PersistentFunction = v8.Persistent(v8.Function);
+
+// Loosely maps to a Browser Page.
+pub const JsContext = struct {
+ id: usize,
+ page: *Page,
+ isolate: v8.Isolate,
+ // This context is a persistent object. The persistent needs to be recovered and reset.
+ v8_context: v8.Context,
+ handle_scope: ?v8.HandleScope,
+
+ // references Env.templates
+ templates: []v8.FunctionTemplate,
+
+ // references the Env.meta_lookup
+ meta_lookup: []TypeMeta,
+
+ // An arena for the lifetime of a call-group. Gets reset whenever
+ // call_depth reaches 0.
+ call_arena: Allocator,
+
+ // An arena for the lifetime of the context
+ context_arena: Allocator,
+
+ // Because calls can be nested (i.e.a function calling a callback),
+ // we can only reset the call_arena when call_depth == 0. If we were
+ // to reset it within a callback, it would invalidate the data of
+ // the call which is calling the callback.
+ call_depth: usize = 0,
+
+ // Callbacks are PesistendObjects. When the context ends, we need
+ // to free every callback we created.
+ callbacks: std.ArrayListUnmanaged(v8.Persistent(v8.Function)) = .empty,
+
+ // Serves two purposes. Like `callbacks` above, this is used to free
+ // every PeristentObjet we've created during the lifetime of the context.
+ // More importantly, it serves as an identity map - for a given Zig
+ // instance, we map it to the same PersistentObject.
+ // The key is the @intFromPtr of the Zig value
+ identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
+
+ // Some web APIs have to manage opaque values. Ideally, they use an
+ // JsObject, but the JsObject has no lifetime guarantee beyond the
+ // current call. They can call .persist() on their JsObject to get
+ // a `*PersistentObject()`. We need to track these to free them.
+ // This used to be a map and acted like identity_map; the key was
+ // the @intFromPtr(js_obj.handle). But v8 can re-use address. Without
+ // a reliable way to know if an object has already been persisted,
+ // we now simply persist every time persist() is called.
+ js_object_list: std.ArrayListUnmanaged(PersistentObject) = .empty,
+
+ // Various web APIs depend on having a persistent promise resolver. They
+ // require for this PromiseResolver to be valid for a lifetime longer than
+ // the function that resolves/rejects them.
+ persisted_promise_resolvers: std.ArrayListUnmanaged(v8.Persistent(v8.PromiseResolver)) = .empty,
+
+ // Some Zig types have code to execute to cleanup
+ destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty,
+
+ // Our module cache: normalized module specifier => module.
+ module_cache: std.StringHashMapUnmanaged(ModuleEntry) = .empty,
+
+ // Module => Path. The key is the module hashcode (module.getIdentityHash)
+ // and the value is the full path to the module. We need to capture this
+ // so that when we're asked to resolve a dependent module, and all we're
+ // given is the specifier, we can form the full path. The full path is
+ // necessary to lookup/store the dependent module in the module_cache.
+ module_identifier: std.AutoHashMapUnmanaged(u32, []const u8) = .empty,
+
+ // the page's script manager
+ script_manager: ?*ScriptManager,
+
+ // Global callback is called on missing property.
+ global_callback: ?GlobalMissingCallback = null,
+
+ const ModuleEntry = struct {
+ // Can be null if we're asynchrously loading the module, in
+ // which case resolver_promise cannot be null.
+ module: ?PersistentModule = null,
+
+ // The promise of the evaluating module. The resolved value is
+ // meaningless to us, but the resolver promise needs to chain
+ // to this, since we need to know when it's complete.
+ module_promise: ?PersistentPromise = null,
+
+ // The promise for the resolver which is loading the module.
+ // (AKA, the first time we try to load it). This resolver will
+ // chain to the module_promise and, when it's done evaluating
+ // will resolve its namespace. Any other attempt to load the
+ // module willchain to this.
+ resolver_promise: ?PersistentPromise = null,
+ };
+
+ // no init, started with executor.createJsContext()
+
+ fn deinit(self: *JsContext) void {
+ {
+ // reverse order, as this has more chance of respecting any
+ // dependencies objects might have with each other.
+ const items = self.destructor_callbacks.items;
+ var i = items.len;
+ while (i > 0) {
+ i -= 1;
+ items[i].destructor();
+ }
+ }
+
+ {
+ var it = self.identity_map.valueIterator();
+ while (it.next()) |p| {
+ p.deinit();
+ }
+ }
+
+ for (self.js_object_list.items) |*p| {
+ p.deinit();
+ }
+
+ for (self.persisted_promise_resolvers.items) |*p| {
+ p.deinit();
+ }
+
+ {
+ var it = self.module_cache.valueIterator();
+ while (it.next()) |entry| {
+ if (entry.module) |*mod| {
+ mod.deinit();
+ }
+ if (entry.module_promise) |*p| {
+ p.deinit();
+ }
+ if (entry.resolver_promise) |*p| {
+ p.deinit();
+ }
+ }
+ }
+
+ for (self.callbacks.items) |*cb| {
+ cb.deinit();
+ }
+ if (self.handle_scope) |*scope| {
+ scope.deinit();
+ self.v8_context.exit();
+ }
+ var presistent_context = v8.Persistent(v8.Context).recoverCast(self.v8_context);
+ presistent_context.deinit();
+ }
+
+ fn trackCallback(self: *JsContext, pf: PersistentFunction) !void {
+ return self.callbacks.append(self.context_arena, pf);
+ }
+
+ // Given an anytype, turns it into a v8.Object. The anytype could be:
+ // 1 - A V8.object already
+ // 2 - Our JsObject wrapper around a V8.Object
+ // 3 - A zig instance that has previously been given to V8
+ // (i.e., the value has to be known to the executor)
+ fn valueToExistingObject(self: *const JsContext, value: anytype) !v8.Object {
+ if (@TypeOf(value) == v8.Object) {
+ return value;
+ }
+
+ if (@TypeOf(value) == JsObject) {
+ return value.js_obj;
+ }
+
+ const persistent_object = self.identity_map.get(@intFromPtr(value)) orelse {
+ return error.InvalidThisForCallback;
+ };
+
+ return persistent_object.castToObject();
+ }
+
+ pub fn stackTrace(self: *const JsContext) !?[]const u8 {
+ return stackForLogs(self.call_arena, self.isolate);
+ }
+
+ // Executes the src
+ pub fn eval(self: *JsContext, src: []const u8, name: ?[]const u8) !void {
+ _ = try self.exec(src, name);
+ }
+
+ pub fn exec(self: *JsContext, src: []const u8, name: ?[]const u8) !Value {
+ const v8_context = self.v8_context;
+
+ const scr = try compileScript(self.isolate, v8_context, src, name);
+
+ const value = scr.run(v8_context) catch {
+ return error.ExecutionError;
+ };
+
+ return self.createValue(value);
+ }
+
+ pub fn module(self: *JsContext, comptime want_result: bool, src: []const u8, url: []const u8, cacheable: bool) !(if (want_result) ModuleEntry else void) {
+ if (cacheable) {
+ if (self.module_cache.get(url)) |entry| {
+ // The dynamic import will create an entry without the
+ // module to prevent multiple calls from asynchronously
+ // loading the same module. If we're here, without the
+ // module, then it's time to load it.
+ if (entry.module != null) {
+ return if (comptime want_result) entry else {};
+ }
+ }
+ }
+ errdefer _ = self.module_cache.remove(url);
+
+ const m = try compileModule(self.isolate, src, url);
+
+ const arena = self.context_arena;
+ const owned_url = try arena.dupe(u8, url);
+
+ try self.module_identifier.putNoClobber(arena, m.getIdentityHash(), owned_url);
+ errdefer _ = self.module_identifier.remove(m.getIdentityHash());
+
+ const v8_context = self.v8_context;
+ {
+ // Non-async modules are blocking. We can download them in
+ // parallel, but they need to be processed serially. So we
+ // want to get the list of dependent modules this module has
+ // and start downloading them asap.
+ const requests = m.getModuleRequests();
+ const isolate = self.isolate;
+ for (0..requests.length()) |i| {
+ const req = requests.get(v8_context, @intCast(i)).castTo(v8.ModuleRequest);
+ const specifier = try jsStringToZig(self.call_arena, req.getSpecifier(), isolate);
+ const normalized_specifier = try @import("../../url.zig").stitch(
+ self.call_arena,
+ specifier,
+ owned_url,
+ .{ .alloc = .if_needed, .null_terminated = true },
+ );
+ const gop = try self.module_cache.getOrPut(self.context_arena, normalized_specifier);
+ if (!gop.found_existing) {
+ const owned_specifier = try self.context_arena.dupeZ(u8, normalized_specifier);
+ gop.key_ptr.* = owned_specifier;
+ gop.value_ptr.* = .{};
+ try self.script_manager.?.getModule(owned_specifier);
+ }
+ }
+ }
+
+ if (try m.instantiate(v8_context, resolveModuleCallback) == false) {
+ return error.ModuleInstantiationError;
+ }
+
+ const evaluated = try m.evaluate(v8_context);
+ // https://v8.github.io/api/head/classv8_1_1Module.html#a1f1758265a4082595757c3251bb40e0f
+ // Must be a promise that gets returned here.
+ std.debug.assert(evaluated.isPromise());
+
+ if (comptime !want_result) {
+ // avoid creating a bunch of persisted objects if it isn't
+ // cacheable and the caller doesn't care about results.
+ // This is pretty common, i.e. every