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