Merge pull request #705 from lightpanda-io/page_as_state

Replace SessionState directly with the Page.
This commit is contained in:
Karl Seguin
2025-05-27 20:55:01 +08:00
committed by GitHub
33 changed files with 549 additions and 590 deletions

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const builtin = @import("builtin");
const JsObject = @import("../env.zig").Env.JsObject;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const log = if (builtin.is_test) &test_capture else @import("../../log.zig");
@@ -29,49 +29,49 @@ pub const Console = struct {
timers: std.StringHashMapUnmanaged(u32) = .{},
counts: std.StringHashMapUnmanaged(u32) = .{},
pub fn _log(_: *const Console, values: []JsObject, state: *SessionState) !void {
pub fn _log(_: *const Console, values: []JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.info(.console, "info", .{ .args = try serializeValues(values, state) });
log.info(.console, "info", .{ .args = try serializeValues(values, page) });
}
pub fn _info(console: *const Console, values: []JsObject, state: *SessionState) !void {
return console._log(values, state);
pub fn _info(console: *const Console, values: []JsObject, page: *Page) !void {
return console._log(values, page);
}
pub fn _debug(_: *const Console, values: []JsObject, state: *SessionState) !void {
pub fn _debug(_: *const Console, values: []JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.debug(.console, "debug", .{ .args = try serializeValues(values, state) });
log.debug(.console, "debug", .{ .args = try serializeValues(values, page) });
}
pub fn _warn(_: *const Console, values: []JsObject, state: *SessionState) !void {
pub fn _warn(_: *const Console, values: []JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.warn(.console, "warn", .{ .args = try serializeValues(values, state) });
log.warn(.console, "warn", .{ .args = try serializeValues(values, page) });
}
pub fn _error(_: *const Console, values: []JsObject, state: *SessionState) !void {
pub fn _error(_: *const Console, values: []JsObject, page: *Page) !void {
if (values.len == 0) {
return;
}
log.info(.console, "error", .{ .args = try serializeValues(values, state) });
log.info(.console, "error", .{ .args = try serializeValues(values, page) });
}
pub fn _clear(_: *const Console) void {}
pub fn _count(self: *Console, label_: ?[]const u8, state: *SessionState) !void {
pub fn _count(self: *Console, label_: ?[]const u8, page: *Page) !void {
const label = label_ orelse "default";
const gop = try self.counts.getOrPut(state.arena, label);
const gop = try self.counts.getOrPut(page.arena, label);
var current: u32 = 0;
if (gop.found_existing) {
current = gop.value_ptr.*;
} else {
gop.key_ptr.* = try state.arena.dupe(u8, label);
gop.key_ptr.* = try page.arena.dupe(u8, label);
}
const count = current + 1;
@@ -89,15 +89,15 @@ pub const Console = struct {
log.info(.console, "count reset", .{ .label = label, .count = kv.value });
}
pub fn _time(self: *Console, label_: ?[]const u8, state: *SessionState) !void {
pub fn _time(self: *Console, label_: ?[]const u8, page: *Page) !void {
const label = label_ orelse "default";
const gop = try self.timers.getOrPut(state.arena, label);
const gop = try self.timers.getOrPut(page.arena, label);
if (gop.found_existing) {
log.info(.console, "duplicate timer", .{ .label = label });
return;
}
gop.key_ptr.* = try state.arena.dupe(u8, label);
gop.key_ptr.* = try page.arena.dupe(u8, label);
gop.value_ptr.* = timestamp();
}
@@ -122,19 +122,19 @@ pub const Console = struct {
log.warn(.console, "timer stop", .{ .label = label, .elapsed = elapsed - kv.value });
}
pub fn _assert(_: *Console, assertion: JsObject, values: []JsObject, state: *SessionState) !void {
pub fn _assert(_: *Console, assertion: JsObject, values: []JsObject, page: *Page) !void {
if (assertion.isTruthy()) {
return;
}
var serialized_values: []const u8 = "";
if (values.len > 0) {
serialized_values = try serializeValues(values, state);
serialized_values = try serializeValues(values, page);
}
log.info(.console, "assertion failed", .{ .values = serialized_values });
}
fn serializeValues(values: []JsObject, state: *SessionState) ![]const u8 {
const arena = state.call_arena;
fn serializeValues(values: []JsObject, page: *Page) ![]const u8 {
const arena = page.call_arena;
var arr: std.ArrayListUnmanaged(u8) = .{};
try arr.appendSlice(arena, try values[0].toString());
for (values[1..]) |value| {

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const CSSParser = @import("./css_parser.zig").CSSParser;
const CSSValueAnalyzer = @import("./css_value_analyzer.zig").CSSValueAnalyzer;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
pub const Interfaces = .{
CSSStyleDeclaration,
@@ -47,17 +47,17 @@ pub const CSSStyleDeclaration = struct {
return self._getPropertyValue("float");
}
pub fn set_cssFloat(self: *CSSStyleDeclaration, value: ?[]const u8, state: *SessionState) !void {
pub fn set_cssFloat(self: *CSSStyleDeclaration, value: ?[]const u8, page: *Page) !void {
const final_value = value orelse "";
return self._setProperty("float", final_value, null, state);
return self._setProperty("float", final_value, null, page);
}
pub fn get_cssText(self: *const CSSStyleDeclaration, state: *SessionState) ![]const u8 {
pub fn get_cssText(self: *const CSSStyleDeclaration, page: *Page) ![]const u8 {
var buffer: std.ArrayListUnmanaged(u8) = .empty;
const writer = buffer.writer(state.call_arena);
const writer = buffer.writer(page.call_arena);
for (self.order.items) |name| {
const prop = self.store.get(name).?;
const escaped = try CSSValueAnalyzer.escapeCSSValue(state.call_arena, prop.value);
const escaped = try CSSValueAnalyzer.escapeCSSValue(page.call_arena, prop.value);
try writer.print("{s}: {s}", .{ name, escaped });
if (prop.priority) try writer.writeAll(" !important");
try writer.writeAll("; ");
@@ -66,18 +66,18 @@ pub const CSSStyleDeclaration = struct {
}
// TODO Propagate also upward to parent node
pub fn set_cssText(self: *CSSStyleDeclaration, text: []const u8, state: *SessionState) !void {
pub fn set_cssText(self: *CSSStyleDeclaration, text: []const u8, page: *Page) !void {
self.store.clearRetainingCapacity();
self.order.clearRetainingCapacity();
// call_arena is safe here, because _setProperty will dupe the name
// using the state's longer-living arena.
const declarations = try CSSParser.parseDeclarations(state.call_arena, text);
// using the page's longer-living arena.
const declarations = try CSSParser.parseDeclarations(page.call_arena, text);
for (declarations) |decl| {
if (!CSSValueAnalyzer.isValidPropertyName(decl.name)) continue;
const priority: ?[]const u8 = if (decl.is_important) "important" else null;
try self._setProperty(decl.name, decl.value, priority, state);
try self._setProperty(decl.name, decl.value, priority, page);
}
}
@@ -119,19 +119,19 @@ pub const CSSStyleDeclaration = struct {
break;
}
}
// safe to return, since it's in our state.arena
// safe to return, since it's in our page.arena
return prop.value.value;
}
pub fn _setProperty(self: *CSSStyleDeclaration, name: []const u8, value: []const u8, priority: ?[]const u8, state: *SessionState) !void {
const owned_value = try state.arena.dupe(u8, value);
pub fn _setProperty(self: *CSSStyleDeclaration, name: []const u8, value: []const u8, priority: ?[]const u8, page: *Page) !void {
const owned_value = try page.arena.dupe(u8, value);
const is_important = priority != null and std.ascii.eqlIgnoreCase(priority.?, "important");
const gop = try self.store.getOrPut(state.arena, name);
const gop = try self.store.getOrPut(page.arena, name);
if (!gop.found_existing) {
const owned_name = try state.arena.dupe(u8, name);
const owned_name = try page.arena.dupe(u8, name);
gop.key_ptr.* = owned_name;
try self.order.append(state.arena, owned_name);
try self.order.append(page.arena, owned_name);
}
gop.value_ptr.* = .{ .value = owned_value, .priority = is_important };
@@ -177,7 +177,7 @@ test "CSSOM.CSSStyleDeclaration" {
.{ "style.setProperty('color', 'green')", "undefined" },
.{ "style.getPropertyValue('color')", "green" },
.{ "style.length", "4" },
.{ "style.color", "green"},
.{ "style.color", "green" },
.{ "style.setProperty('padding', '10px', 'important')", "undefined" },
.{ "style.getPropertyValue('padding')", "10px" },

View File

@@ -20,7 +20,7 @@ const parser = @import("../netsurf.zig");
const CharacterData = @import("character_data.zig").CharacterData;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
// https://dom.spec.whatwg.org/#interface-comment
pub const Comment = struct {
@@ -28,9 +28,9 @@ pub const Comment = struct {
pub const prototype = *CharacterData;
pub const subtype = .node;
pub fn constructor(data: ?[]const u8, state: *const SessionState) !*parser.Comment {
pub fn constructor(data: ?[]const u8, page: *const Page) !*parser.Comment {
return parser.documentCreateComment(
parser.documentHTMLToDocument(state.window.document),
parser.documentHTMLToDocument(page.window.document),
data orelse "",
);
}

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const Node = @import("node.zig").Node;
const NodeList = @import("nodelist.zig").NodeList;
@@ -42,14 +42,14 @@ pub const Document = struct {
pub const prototype = *Node;
pub const subtype = .node;
pub fn constructor(state: *const SessionState) !*parser.DocumentHTML {
pub fn constructor(page: *const Page) !*parser.DocumentHTML {
const doc = try parser.documentCreateDocument(
try parser.documentHTMLGetTitle(state.window.document),
try parser.documentHTMLGetTitle(page.window.document),
);
// we have to work w/ document instead of html document.
const ddoc = parser.documentHTMLToDocument(doc);
const ccur = parser.documentHTMLToDocument(state.window.document);
const ccur = parser.documentHTMLToDocument(page.window.document);
try parser.documentSetDocumentURI(ddoc, try parser.documentGetDocumentURI(ccur));
try parser.documentSetInputEncoding(ddoc, try parser.documentGetInputEncoding(ccur));
@@ -141,18 +141,17 @@ pub const Document = struct {
pub fn _getElementsByTagName(
self: *parser.Document,
tag_name: []const u8,
state: *SessionState,
page: *Page,
) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(state.arena, parser.documentToNode(self), tag_name, true);
return try collection.HTMLCollectionByTagName(page.arena, parser.documentToNode(self), tag_name, true);
}
pub fn _getElementsByClassName(
self: *parser.Document,
classNames: []const u8,
state: *SessionState,
page: *Page,
) !collection.HTMLCollection {
const allocator = state.arena;
return try collection.HTMLCollectionByClassName(allocator, parser.documentToNode(self), classNames, true);
return try collection.HTMLCollectionByClassName(page.arena, parser.documentToNode(self), classNames, true);
}
pub fn _createDocumentFragment(self: *parser.Document) !*parser.DocumentFragment {
@@ -214,20 +213,18 @@ pub const Document = struct {
return 1;
}
pub fn _querySelector(self: *parser.Document, selector: []const u8, state: *SessionState) !?ElementUnion {
pub fn _querySelector(self: *parser.Document, selector: []const u8, page: *Page) !?ElementUnion {
if (selector.len == 0) return null;
const allocator = state.arena;
const n = try css.querySelector(allocator, parser.documentToNode(self), selector);
const n = try css.querySelector(page.arena, parser.documentToNode(self), selector);
if (n == null) return null;
return try Element.toInterface(parser.nodeToElement(n.?));
}
pub fn _querySelectorAll(self: *parser.Document, selector: []const u8, state: *SessionState) !NodeList {
const allocator = state.arena;
return css.querySelectorAll(allocator, parser.documentToNode(self), selector);
pub fn _querySelectorAll(self: *parser.Document, selector: []const u8, page: *Page) !NodeList {
return css.querySelectorAll(page.arena, parser.documentToNode(self), selector);
}
pub fn _prepend(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
@@ -249,7 +246,9 @@ pub const Document = struct {
const testing = @import("../../testing.zig");
test "Browser.DOM.Document" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
var runner = try testing.jsRunner(testing.tracking_allocator, .{
.url = "about:blank",
});
defer runner.deinit();
try runner.testCases(&.{

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const Node = @import("node.zig").Node;
@@ -27,9 +27,9 @@ pub const DocumentFragment = struct {
pub const prototype = *Node;
pub const subtype = .node;
pub fn constructor(state: *const SessionState) !*parser.DocumentFragment {
pub fn constructor(page: *const Page) !*parser.DocumentFragment {
return parser.documentCreateDocumentFragment(
parser.documentHTMLToDocument(state.window.document),
parser.documentHTMLToDocument(page.window.document),
);
}

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const css = @import("css.zig");
const log = @import("../../log.zig");
@@ -102,14 +102,14 @@ pub const Element = struct {
return try parser.nodeGetAttributes(parser.elementToNode(self)) orelse unreachable;
}
pub fn get_innerHTML(self: *parser.Element, state: *SessionState) ![]const u8 {
var buf = std.ArrayList(u8).init(state.arena);
pub fn get_innerHTML(self: *parser.Element, page: *Page) ![]const u8 {
var buf = std.ArrayList(u8).init(page.arena);
try dump.writeChildren(parser.elementToNode(self), buf.writer());
return buf.items;
}
pub fn get_outerHTML(self: *parser.Element, state: *SessionState) ![]const u8 {
var buf = std.ArrayList(u8).init(state.arena);
pub fn get_outerHTML(self: *parser.Element, page: *Page) ![]const u8 {
var buf = std.ArrayList(u8).init(page.arena);
try dump.writeNode(parser.elementToNode(self), buf.writer());
return buf.items;
}
@@ -138,10 +138,10 @@ pub const Element = struct {
// The closest() method of the Element interface traverses the element and its parents (heading toward the document root) until it finds a node that matches the specified CSS selector.
// Returns the closest ancestor Element or itself, which matches the selectors. If there are no such element, null.
pub fn _closest(self: *parser.Element, selector: []const u8, state: *SessionState) !?*parser.Element {
pub fn _closest(self: *parser.Element, selector: []const u8, page: *Page) !?*parser.Element {
const cssParse = @import("../css/css.zig").parse;
const CssNodeWrap = @import("../css/libdom.zig").Node;
const select = try cssParse(state.call_arena, selector, .{});
const select = try cssParse(page.call_arena, selector, .{});
var current: CssNodeWrap = .{ .node = parser.elementToNode(self) };
while (true) {
@@ -249,10 +249,10 @@ pub const Element = struct {
pub fn _getElementsByTagName(
self: *parser.Element,
tag_name: []const u8,
state: *SessionState,
page: *Page,
) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(
state.arena,
page.arena,
parser.elementToNode(self),
tag_name,
false,
@@ -262,10 +262,10 @@ pub const Element = struct {
pub fn _getElementsByClassName(
self: *parser.Element,
classNames: []const u8,
state: *SessionState,
page: *Page,
) !collection.HTMLCollection {
return try collection.HTMLCollectionByClassName(
state.arena,
page.arena,
parser.elementToNode(self),
classNames,
false,
@@ -328,18 +328,18 @@ pub const Element = struct {
}
}
pub fn _querySelector(self: *parser.Element, selector: []const u8, state: *SessionState) !?Union {
pub fn _querySelector(self: *parser.Element, selector: []const u8, page: *Page) !?Union {
if (selector.len == 0) return null;
const n = try css.querySelector(state.arena, parser.elementToNode(self), selector);
const n = try css.querySelector(page.arena, parser.elementToNode(self), selector);
if (n == null) return null;
return try toInterface(parser.nodeToElement(n.?));
}
pub fn _querySelectorAll(self: *parser.Element, selector: []const u8, state: *SessionState) !NodeList {
return css.querySelectorAll(state.arena, parser.elementToNode(self), selector);
pub fn _querySelectorAll(self: *parser.Element, selector: []const u8, page: *Page) !NodeList {
return css.querySelectorAll(page.arena, parser.elementToNode(self), selector);
}
pub fn _prepend(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
@@ -366,40 +366,40 @@ pub const Element = struct {
// A DOMRect object providing information about the size of an element and its position relative to the viewport.
// Returns a 0 DOMRect object if the element is eventually detached from the main window
pub fn _getBoundingClientRect(self: *parser.Element, state: *SessionState) !DOMRect {
pub fn _getBoundingClientRect(self: *parser.Element, page: *Page) !DOMRect {
// Since we are lazy rendering we need to do this check. We could store the renderer in a viewport such that it could cache these, but it would require tracking changes.
const root = try parser.nodeGetRootNode(parser.elementToNode(self));
if (root != parser.documentToNode(parser.documentHTMLToDocument(state.window.document))) {
if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) {
return DOMRect{ .x = 0, .y = 0, .width = 0, .height = 0 };
}
return state.renderer.getRect(self);
return page.renderer.getRect(self);
}
// Returns a collection of DOMRect objects that indicate the bounding rectangles for each CSS border box in a client.
// We do not render so it only always return the element's bounding rect.
// Returns an empty array if the element is eventually detached from the main window
pub fn _getClientRects(self: *parser.Element, state: *SessionState) ![]DOMRect {
pub fn _getClientRects(self: *parser.Element, page: *Page) ![]DOMRect {
const root = try parser.nodeGetRootNode(parser.elementToNode(self));
if (root != parser.documentToNode(parser.documentHTMLToDocument(state.window.document))) {
if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) {
return &.{};
}
const heap_ptr = try state.call_arena.create(DOMRect);
heap_ptr.* = try state.renderer.getRect(self);
const heap_ptr = try page.call_arena.create(DOMRect);
heap_ptr.* = try page.renderer.getRect(self);
return heap_ptr[0..1];
}
pub fn get_clientWidth(_: *parser.Element, state: *SessionState) u32 {
return state.renderer.width();
pub fn get_clientWidth(_: *parser.Element, page: *Page) u32 {
return page.renderer.width();
}
pub fn get_clientHeight(_: *parser.Element, state: *SessionState) u32 {
return state.renderer.height();
pub fn get_clientHeight(_: *parser.Element, page: *Page) u32 {
return page.renderer.height();
}
pub fn _matches(self: *parser.Element, selectors: []const u8, state: *SessionState) !bool {
pub fn _matches(self: *parser.Element, selectors: []const u8, page: *Page) !bool {
const cssParse = @import("../css/css.zig").parse;
const CssNodeWrap = @import("../css/libdom.zig").Node;
const s = try cssParse(state.call_arena, selectors, .{});
const s = try cssParse(page.call_arena, selectors, .{});
return s.match(CssNodeWrap{ .node = parser.elementToNode(self) });
}

View File

@@ -18,7 +18,7 @@
const Env = @import("../env.zig").Env;
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const EventHandler = @import("../events/event.zig").EventHandler;
@@ -59,7 +59,7 @@ pub const EventTarget = struct {
typ: []const u8,
cbk: Env.Function,
opts_: ?AddEventListenerOpts,
state: *SessionState,
page: *Page,
) !void {
var capture = false;
if (opts_) |opts| {
@@ -91,7 +91,7 @@ pub const EventTarget = struct {
return;
}
const eh = try EventHandler.init(state.arena, try cbk.withThis(self));
const eh = try EventHandler.init(page.arena, try cbk.withThis(self));
try parser.eventTargetAddEventListener(
self,
@@ -132,10 +132,6 @@ pub const EventTarget = struct {
pub fn _dispatchEvent(self: *parser.EventTarget, event: *parser.Event) !bool {
return try parser.eventTargetDispatchEvent(self, event);
}
pub fn deinit(self: *parser.EventTarget, state: *SessionState) void {
parser.eventTargetRemoveAllEventListeners(self, state.arena) catch unreachable;
}
};
const testing = @import("../../testing.zig");

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const Element = @import("element.zig").Element;
@@ -39,17 +39,17 @@ pub const Interfaces = .{
// The returned Entries are phony, they always indicate full intersection.
// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
pub const IntersectionObserver = struct {
page: *Page,
callback: Env.Function,
options: IntersectionObserverOptions,
state: *SessionState,
observed_entries: std.ArrayListUnmanaged(IntersectionObserverEntry),
// new IntersectionObserver(callback)
// new IntersectionObserver(callback, options) [not supported yet]
pub fn constructor(callback: Env.Function, options_: ?IntersectionObserverOptions, state: *SessionState) !IntersectionObserver {
pub fn constructor(callback: Env.Function, options_: ?IntersectionObserverOptions, page: *Page) !IntersectionObserver {
var options = IntersectionObserverOptions{
.root = parser.documentToNode(parser.documentHTMLToDocument(state.window.document)),
.root = parser.documentToNode(parser.documentHTMLToDocument(page.window.document)),
.rootMargin = "0px 0px 0px 0px",
.threshold = &.{0.0},
};
@@ -60,9 +60,9 @@ pub const IntersectionObserver = struct {
}
return .{
.page = page,
.callback = callback,
.options = options,
.state = state,
.observed_entries = .{},
};
}
@@ -78,8 +78,8 @@ pub const IntersectionObserver = struct {
}
}
try self.observed_entries.append(self.state.arena, .{
.state = self.state,
try self.observed_entries.append(self.page.arena, .{
.page = self.page,
.target = target_element,
.options = &self.options,
});
@@ -113,13 +113,13 @@ const IntersectionObserverOptions = struct {
// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry
// https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
pub const IntersectionObserverEntry = struct {
state: *SessionState,
page: *Page,
target: *parser.Element,
options: *IntersectionObserverOptions,
// Returns the bounds rectangle of the target element as a DOMRectReadOnly. The bounds are computed as described in the documentation for Element.getBoundingClientRect().
pub fn get_boundingClientRect(self: *const IntersectionObserverEntry) !Element.DOMRect {
return Element._getBoundingClientRect(self.target, self.state);
return Element._getBoundingClientRect(self.target, self.page);
}
// Returns the ratio of the intersectionRect to the boundingClientRect.
@@ -129,10 +129,14 @@ pub const IntersectionObserverEntry = struct {
// Returns a DOMRectReadOnly representing the target's visible area.
pub fn get_intersectionRect(self: *const IntersectionObserverEntry) !Element.DOMRect {
return Element._getBoundingClientRect(self.target, self.state);
return Element._getBoundingClientRect(self.target, self.page);
}
// A Boolean value which is true if the target element intersects with the intersection observer's root. If this is true, then, the IntersectionObserverEntry describes a transition into a state of intersection; if it's false, then you know the transition is from intersecting to not-intersecting.
// A Boolean value which is true if the target element intersects with the
// intersection observer's root. If this is true, then, the
// IntersectionObserverEntry describes a transition into a state of
// intersection; if it's false, then you know the transition is from
// intersecting to not-intersecting.
pub fn get_isIntersecting(_: *const IntersectionObserverEntry) bool {
return true;
}
@@ -140,8 +144,8 @@ pub const IntersectionObserverEntry = struct {
// Returns a DOMRectReadOnly for the intersection observer's root.
pub fn get_rootBounds(self: *const IntersectionObserverEntry) !Element.DOMRect {
const root = self.options.root.?;
if (@intFromPtr(root) == @intFromPtr(self.state.window.document)) {
return self.state.renderer.boundingRect();
if (@intFromPtr(root) == @intFromPtr(self.page.window.document)) {
return self.page.renderer.boundingRect();
}
const root_type = try parser.nodeType(root);
@@ -156,7 +160,7 @@ pub const IntersectionObserverEntry = struct {
else => return error.InvalidState,
}
return Element._getBoundingClientRect(element, self.state);
return Element._getBoundingClientRect(element, self.page);
}
// The Element whose intersection with the root changed.

View File

@@ -21,7 +21,7 @@ const Allocator = std.mem.Allocator;
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const Env = @import("../env.zig").Env;
const NodeList = @import("nodelist.zig").NodeList;
@@ -42,11 +42,11 @@ pub const MutationObserver = struct {
// execute our callback with it.
observed: std.ArrayListUnmanaged(*MutationRecord),
pub fn constructor(cbk: Env.Function, state: *SessionState) !MutationObserver {
pub fn constructor(cbk: Env.Function, page: *Page) !MutationObserver {
return .{
.cbk = cbk,
.observed = .{},
.arena = state.arena,
.arena = page.arena,
};
}

View File

@@ -22,7 +22,7 @@ const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const EventTarget = @import("event_target.zig").EventTarget;
// DOM
@@ -278,8 +278,8 @@ pub const Node = struct {
return try parser.nodeHasChildNodes(self);
}
pub fn get_childNodes(self: *parser.Node, state: *SessionState) !NodeList {
const allocator = state.arena;
pub fn get_childNodes(self: *parser.Node, page: *Page) !NodeList {
const allocator = page.arena;
var list: NodeList = .{};
var n = try parser.nodeFirstChild(self) orelse return list;

View File

@@ -20,7 +20,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const Node = @import("node.zig").Node;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
// https://dom.spec.whatwg.org/#processinginstruction
pub const ProcessingInstruction = struct {
@@ -39,9 +39,9 @@ pub const ProcessingInstruction = struct {
// There's something wrong when we try to clone a ProcessInstruction normally.
// The resulting object can't be cast back into a node (it crashes). This is
// a simple workaround.
pub fn _cloneNode(self: *parser.ProcessingInstruction, _: ?bool, state: *SessionState) !*parser.ProcessingInstruction {
pub fn _cloneNode(self: *parser.ProcessingInstruction, _: ?bool, page: *Page) !*parser.ProcessingInstruction {
return try parser.documentCreateProcessingInstruction(
@ptrCast(state.window.document),
@ptrCast(page.window.document),
try get_target(self),
(try get_data(self)) orelse "",
);

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const CharacterData = @import("character_data.zig").CharacterData;
const CDATASection = @import("cdata_section.zig").CDATASection;
@@ -32,9 +32,9 @@ pub const Text = struct {
pub const prototype = *CharacterData;
pub const subtype = .node;
pub fn constructor(data: ?[]const u8, state: *const SessionState) !*parser.Text {
pub fn constructor(data: ?[]const u8, page: *const Page) !*parser.Text {
return parser.documentCreateTextNode(
parser.documentHTMLToDocument(state.window.document),
parser.documentHTMLToDocument(page.window.document),
data orelse "",
);
}

View File

@@ -21,7 +21,7 @@ const parser = @import("../netsurf.zig");
const NodeFilter = @import("node_filter.zig").NodeFilter;
const Env = @import("../env.zig").Env;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
// https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
pub const TreeWalker = struct {

View File

@@ -156,6 +156,9 @@ fn writeEscapedAttributeValue(writer: anytype, value: []const u8) !void {
const testing = std.testing;
test "dump.writeHTML" {
try parser.init();
defer parser.deinit();
try testWriteHTML(
"<div id=\"content\">Over 9000!</div>",
"<div id=\"content\">Over 9000!</div>",

View File

@@ -1,29 +1,24 @@
const std = @import("std");
const parser = @import("netsurf.zig");
const URL = @import("../url.zig").URL;
const Page = @import("page.zig").Page;
const js = @import("../runtime/js.zig");
const storage = @import("storage/storage.zig");
const generate = @import("../runtime/generate.zig");
const Renderer = @import("renderer.zig").Renderer;
const Loop = @import("../runtime/loop.zig").Loop;
const RequestFactory = @import("../http/client.zig").RequestFactory;
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(*SessionState, WebApis);
// 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.SessionState, browser.env.WebApis)
// 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(*SessionState, Interfaces);
// 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.env.SessionState, .{...A HUNDRED TYPES...})
// runtime.js.Env(*browser.Page, .{...A HUNDRED TYPES...})
pub const Interfaces = generate.Tuple(.{
@import("crypto/crypto.zig").Crypto,
@import("console/console.zig").Console,
@@ -44,41 +39,5 @@ const WebApis = struct {
pub const JsThis = Env.JsThis;
pub const JsObject = Env.JsObject;
pub const Function = Env.Function;
pub const Env = js.Env(*SessionState, WebApis);
const Window = @import("html/window.zig").Window;
pub const Global = Window;
pub const SessionState = struct {
loop: *Loop,
url: *const URL,
window: *Window,
renderer: *Renderer,
arena: std.mem.Allocator,
cookie_jar: *storage.CookieJar,
request_factory: RequestFactory,
// dangerous, but set by the JS framework
// shorter-lived than the arena above, which
// exists for the entire rendering of the page
call_arena: std.mem.Allocator = undefined,
pub fn getOrCreateNodeWrapper(self: *SessionState, comptime T: type, node: *parser.Node) !*T {
if (try self.getNodeWrapper(T, node)) |wrap| {
return wrap;
}
const wrap = try self.arena.create(T);
wrap.* = T{};
parser.nodeSetEmbedderData(node, wrap);
return wrap;
}
pub fn getNodeWrapper(_: *SessionState, comptime T: type, node: *parser.Node) !?*T {
if (parser.nodeGetEmbedderData(node)) |wrap| {
return @alignCast(@ptrCast(wrap));
}
return null;
}
};
pub const Env = js.Env(*Page, WebApis);
pub const Global = @import("html/window.zig").Window;

View File

@@ -19,7 +19,7 @@
const std = @import("std");
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const Window = @import("window.zig").Window;
const Element = @import("../dom/element.zig").Element;
@@ -86,18 +86,18 @@ pub const HTMLDocument = struct {
}
}
pub fn get_cookie(_: *parser.DocumentHTML, state: *SessionState) ![]const u8 {
pub fn get_cookie(_: *parser.DocumentHTML, page: *Page) ![]const u8 {
var buf: std.ArrayListUnmanaged(u8) = .{};
try state.cookie_jar.forRequest(&state.url.uri, buf.writer(state.arena), .{ .navigation = true });
try page.cookie_jar.forRequest(&page.url.uri, buf.writer(page.arena), .{ .navigation = true });
return buf.items;
}
pub fn set_cookie(_: *parser.DocumentHTML, cookie_str: []const u8, state: *SessionState) ![]const u8 {
pub fn set_cookie(_: *parser.DocumentHTML, cookie_str: []const u8, page: *Page) ![]const u8 {
// we use the cookie jar's allocator to parse the cookie because it
// outlives the page's arena.
const c = try Cookie.parse(state.cookie_jar.allocator, &state.url.uri, cookie_str);
const c = try Cookie.parse(page.cookie_jar.allocator, &page.url.uri, cookie_str);
errdefer c.deinit();
try state.cookie_jar.add(c, std.time.timestamp());
try page.cookie_jar.add(c, std.time.timestamp());
return cookie_str;
}
@@ -110,8 +110,8 @@ pub const HTMLDocument = struct {
return v;
}
pub fn _getElementsByName(self: *parser.DocumentHTML, name: []const u8, state: *SessionState) !NodeList {
const arena = state.arena;
pub fn _getElementsByName(self: *parser.DocumentHTML, name: []const u8, page: *Page) !NodeList {
const arena = page.arena;
var list: NodeList = .{};
if (name.len == 0) return list;
@@ -130,24 +130,24 @@ pub const HTMLDocument = struct {
return list;
}
pub fn get_images(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "img", false);
pub fn get_images(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "img", false);
}
pub fn get_embeds(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "embed", false);
pub fn get_embeds(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "embed", false);
}
pub fn get_plugins(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
return get_embeds(self, state);
pub fn get_plugins(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection {
return get_embeds(self, page);
}
pub fn get_forms(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "form", false);
pub fn get_forms(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "form", false);
}
pub fn get_scripts(self: *parser.DocumentHTML, state: *SessionState) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(state.arena, parser.documentHTMLToNode(self), "script", false);
pub fn get_scripts(self: *parser.DocumentHTML, page: *Page) !collection.HTMLCollection {
return try collection.HTMLCollectionByTagName(page.arena, parser.documentHTMLToNode(self), "script", false);
}
pub fn get_applets(_: *parser.DocumentHTML) !collection.HTMLCollection {
@@ -182,12 +182,12 @@ pub const HTMLDocument = struct {
return "off";
}
pub fn get_defaultView(_: *parser.DocumentHTML, state: *const SessionState) *Window {
return state.window;
pub fn get_defaultView(_: *parser.DocumentHTML, page: *Page) *Window {
return &page.window;
}
pub fn get_readyState(node: *parser.DocumentHTML, state: *SessionState) ![]const u8 {
const self = try state.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(node));
pub fn get_readyState(node: *parser.DocumentHTML, page: *Page) ![]const u8 {
const self = try page.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(node));
return @tagName(self.ready_state);
}
@@ -232,41 +232,41 @@ pub const HTMLDocument = struct {
// Since LightPanda requires the client to know what they are clicking on we do not return the underlying element at this moment
// This can currenty only happen if the first pixel is clicked without having rendered any element. This will change when css properties are supported.
// This returns an ElementUnion instead of a *Parser.Element in case the element somehow hasn't passed through the js runtime yet.
pub fn _elementFromPoint(_: *parser.DocumentHTML, x: f32, y: f32, state: *SessionState) !?ElementUnion {
pub fn _elementFromPoint(_: *parser.DocumentHTML, x: f32, y: f32, page: *Page) !?ElementUnion {
const ix: i32 = @intFromFloat(@floor(x));
const iy: i32 = @intFromFloat(@floor(y));
const element = state.renderer.getElementAtPosition(ix, iy) orelse return null;
const element = page.renderer.getElementAtPosition(ix, iy) orelse return null;
// TODO if pointer-events set to none the underlying element should be returned (parser.documentGetDocumentElement(self.document);?)
return try Element.toInterface(element);
}
// Returns an array of all elements at the specified coordinates (relative to the viewport). The elements are ordered from the topmost to the bottommost box of the viewport.
pub fn _elementsFromPoint(_: *parser.DocumentHTML, x: f32, y: f32, state: *SessionState) ![]ElementUnion {
pub fn _elementsFromPoint(_: *parser.DocumentHTML, x: f32, y: f32, page: *Page) ![]ElementUnion {
const ix: i32 = @intFromFloat(@floor(x));
const iy: i32 = @intFromFloat(@floor(y));
const element = state.renderer.getElementAtPosition(ix, iy) orelse return &.{};
const element = page.renderer.getElementAtPosition(ix, iy) orelse return &.{};
// TODO if pointer-events set to none the underlying element should be returned (parser.documentGetDocumentElement(self.document);?)
var list: std.ArrayListUnmanaged(ElementUnion) = .empty;
try list.ensureTotalCapacity(state.call_arena, 3);
try list.ensureTotalCapacity(page.call_arena, 3);
list.appendAssumeCapacity(try Element.toInterface(element));
// Since we are using a flat renderer there is no hierarchy of elements. What we do know is that the element is part of the main document.
// Thus we can add the HtmlHtmlElement and it's child HTMLBodyElement to the returned list.
// TBD Should we instead return every parent that is an element? Note that a child does not physically need to be overlapping the parent.
// Should we do a render pass on demand?
const doc_elem = try parser.documentGetDocumentElement(parser.documentHTMLToDocument(state.window.document)) orelse {
const doc_elem = try parser.documentGetDocumentElement(parser.documentHTMLToDocument(page.window.document)) orelse {
return list.items;
};
if (try parser.documentHTMLBody(state.window.document)) |body| {
if (try parser.documentHTMLBody(page.window.document)) |body| {
list.appendAssumeCapacity(try Element.toInterface(parser.bodyToElement(body)));
}
list.appendAssumeCapacity(try Element.toInterface(doc_elem));
return list.items;
}
pub fn documentIsLoaded(html_doc: *parser.DocumentHTML, state: *SessionState) !void {
const self = try state.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc));
pub fn documentIsLoaded(html_doc: *parser.DocumentHTML, page: *Page) !void {
const self = try page.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc));
self.ready_state = .interactive;
const evt = try parser.eventCreate();
@@ -276,8 +276,8 @@ pub const HTMLDocument = struct {
_ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, html_doc), evt);
}
pub fn documentIsComplete(html_doc: *parser.DocumentHTML, state: *SessionState) !void {
const self = try state.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc));
pub fn documentIsComplete(html_doc: *parser.DocumentHTML, page: *Page) !void {
const self = try page.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc));
self.ready_state = .complete;
}
};
@@ -383,12 +383,12 @@ test "Browser.HTML.Document" {
.{ "document.readyState", "loading" },
}, .{});
try HTMLDocument.documentIsLoaded(runner.window.document, &runner.state);
try HTMLDocument.documentIsLoaded(runner.page.window.document, runner.page);
try runner.testCases(&.{
.{ "document.readyState", "interactive" },
}, .{});
try HTMLDocument.documentIsComplete(runner.window.document, &runner.state);
try HTMLDocument.documentIsComplete(runner.page.window.document, runner.page);
try runner.testCases(&.{
.{ "document.readyState", "complete" },
}, .{});

View File

@@ -19,7 +19,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const generate = @import("../../runtime/generate.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const URL = @import("../url/url.zig").URL;
const Node = @import("../dom/node.zig").Node;
@@ -111,8 +111,8 @@ pub const HTMLElement = struct {
style: CSSStyleDeclaration = .empty,
pub fn get_style(e: *parser.ElementHTML, state: *SessionState) !*CSSStyleDeclaration {
const self = try state.getOrCreateNodeWrapper(HTMLElement, @ptrCast(e));
pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration {
const self = try page.getOrCreateNodeWrapper(HTMLElement, @ptrCast(e));
return &self.style;
}
@@ -228,26 +228,26 @@ pub const HTMLAnchorElement = struct {
return try parser.nodeSetTextContent(parser.anchorToNode(self), v);
}
inline fn url(self: *parser.Anchor, state: *SessionState) !URL {
inline fn url(self: *parser.Anchor, page: *Page) !URL {
const href = try parser.anchorGetHref(self);
return URL.constructor(href, null, state); // TODO inject base url
return URL.constructor(href, null, page); // TODO inject base url
}
// TODO return a disposable string
pub fn get_origin(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try u.get_origin(state);
pub fn get_origin(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try u.get_origin(page);
}
// TODO return a disposable string
pub fn get_protocol(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return u.get_protocol(state);
pub fn get_protocol(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return u.get_protocol(page);
}
pub fn set_protocol(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_protocol(self: *parser.Anchor, v: []const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
u.uri.scheme = v;
const href = try u.toString(arena);
@@ -255,12 +255,12 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_host(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try u.get_host(state);
pub fn get_host(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try u.get_host(page);
}
pub fn set_host(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
pub fn set_host(self: *parser.Anchor, v: []const u8, page: *Page) !void {
// search : separator
var p: ?u16 = null;
var h: []const u8 = undefined;
@@ -272,8 +272,8 @@ pub const HTMLAnchorElement = struct {
}
}
const arena = state.arena;
var u = try url(self, state);
const arena = page.arena;
var u = try url(self, page);
if (p) |pp| {
u.uri.host = .{ .raw = h };
@@ -288,28 +288,28 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_hostname(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try state.arena.dupe(u8, u.get_hostname());
pub fn get_hostname(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_hostname());
}
pub fn set_hostname(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_hostname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
u.uri.host = .{ .raw = v };
const href = try u.toString(arena);
try parser.anchorSetHref(self, href);
}
// TODO return a disposable string
pub fn get_port(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try u.get_port(state);
pub fn get_port(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try u.get_port(page);
}
pub fn set_port(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_port(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
if (v != null and v.?.len > 0) {
u.uri.port = try std.fmt.parseInt(u16, v.?, 10);
@@ -322,14 +322,14 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_username(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try state.arena.dupe(u8, u.get_username());
pub fn get_username(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_username());
}
pub fn set_username(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_username(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
if (v) |vv| {
u.uri.user = .{ .raw = vv };
@@ -342,14 +342,14 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_password(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try state.arena.dupe(u8, u.get_password());
pub fn get_password(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_password());
}
pub fn set_password(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_password(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
if (v) |vv| {
u.uri.password = .{ .raw = vv };
@@ -362,14 +362,14 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_pathname(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try state.arena.dupe(u8, u.get_pathname());
pub fn get_pathname(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_pathname());
}
pub fn set_pathname(self: *parser.Anchor, v: []const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_pathname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
u.uri.path = .{ .raw = v };
const href = try u.toString(arena);
@@ -377,14 +377,14 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_search(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try u.get_search(state);
pub fn get_search(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try u.get_search(page);
}
pub fn set_search(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_search(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
if (v) |vv| {
u.uri.query = .{ .raw = vv };
@@ -397,14 +397,14 @@ pub const HTMLAnchorElement = struct {
}
// TODO return a disposable string
pub fn get_hash(self: *parser.Anchor, state: *SessionState) ![]const u8 {
var u = try url(self, state);
return try u.get_hash(state);
pub fn get_hash(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try u.get_hash(page);
}
pub fn set_hash(self: *parser.Anchor, v: ?[]const u8, state: *SessionState) !void {
const arena = state.arena;
var u = try url(self, state);
pub fn set_hash(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
const arena = page.arena;
var u = try url(self, page);
if (v) |vv| {
u.uri.fragment = .{ .raw = vv };
@@ -567,8 +567,8 @@ pub const HTMLImageElement = struct {
pub const subtype = .node;
pub const js_name = "Image";
pub fn constructor(width: ?u32, height: ?u32, state: *const SessionState) !*parser.Image {
const element = try parser.documentCreateElement(parser.documentHTMLToDocument(state.window.document), "img");
pub fn constructor(width: ?u32, height: ?u32, page: *const Page) !*parser.Image {
const element = try parser.documentCreateElement(parser.documentHTMLToDocument(page.window.document), "img");
const image: *parser.Image = @ptrCast(element);
if (width) |width_| try parser.imageSetWidth(image, width_);
if (height) |height_| try parser.imageSetHeight(image, height_);

View File

@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const URL = @import("../url/url.zig").URL;
@@ -24,18 +24,18 @@ const URL = @import("../url/url.zig").URL;
pub const Location = struct {
url: ?URL = null,
pub fn get_href(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_href(state);
pub fn get_href(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_href(page);
return "";
}
pub fn get_protocol(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_protocol(state);
pub fn get_protocol(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_protocol(page);
return "";
}
pub fn get_host(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_host(state);
pub fn get_host(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_host(page);
return "";
}
@@ -44,8 +44,8 @@ pub const Location = struct {
return "";
}
pub fn get_port(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_port(state);
pub fn get_port(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_port(page);
return "";
}
@@ -54,18 +54,18 @@ pub const Location = struct {
return "";
}
pub fn get_search(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_search(state);
pub fn get_search(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_search(page);
return "";
}
pub fn get_hash(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_hash(state);
pub fn get_hash(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_hash(page);
return "";
}
pub fn get_origin(self: *Location, state: *SessionState) ![]const u8 {
if (self.url) |*u| return u.get_origin(state);
pub fn get_origin(self: *Location, page: *Page) ![]const u8 {
if (self.url) |*u| return u.get_origin(page);
return "";
}
@@ -82,8 +82,8 @@ pub const Location = struct {
// TODO
pub fn _reload(_: *Location) !void {}
pub fn _toString(self: *Location, state: *SessionState) ![]const u8 {
return try self.get_href(state);
pub fn _toString(self: *Location, page: *Page) ![]const u8 {
return try self.get_href(page);
}
};

View File

@@ -19,7 +19,7 @@ const std = @import("std");
const parser = @import("../netsurf.zig");
const HTMLElement = @import("elements.zig").HTMLElement;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
pub const HTMLSelectElement = struct {
pub const Self = parser.Select;
@@ -69,8 +69,8 @@ pub const HTMLSelectElement = struct {
return parser.selectSetMultiple(select, multiple);
}
pub fn get_selectedIndex(select: *parser.Select, state: *SessionState) !i32 {
const self = try state.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select));
pub fn get_selectedIndex(select: *parser.Select, page: *Page) !i32 {
const self = try page.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select));
const selected_index = try parser.selectGetSelectedIndex(select);
// See the explicit_index_set field documentation
@@ -88,8 +88,8 @@ pub const HTMLSelectElement = struct {
// Libdom's dom_html_select_select_set_selected_index will crash if index
// is out of range, and it doesn't properly unset options
pub fn set_selectedIndex(select: *parser.Select, index: i32, state: *SessionState) !void {
var self = try state.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select));
pub fn set_selectedIndex(select: *parser.Select, index: i32, page: *Page) !void {
var self = try page.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select));
self.explicit_index_set = true;
const options = try parser.selectGetOptions(select);

View File

@@ -21,7 +21,7 @@ const std = @import("std");
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const Function = @import("../env.zig").Function;
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const Loop = @import("../../runtime/loop.zig").Loop;
const Navigator = @import("navigator.zig").Navigator;
@@ -130,15 +130,15 @@ pub const Window = struct {
}
// The interior height of the window in pixels, including the height of the horizontal scroll bar, if present.
pub fn get_innerHeight(_: *Window, state: *SessionState) u32 {
pub fn get_innerHeight(_: *Window, page: *Page) u32 {
// We do not have scrollbars or padding so this is the same as Element.clientHeight
return state.renderer.height();
return page.renderer.height();
}
// The interior width of the window in pixels. That includes the width of the vertical scroll bar, if one is present.
pub fn get_innerWidth(_: *Window, state: *SessionState) u32 {
pub fn get_innerWidth(_: *Window, page: *Page) u32 {
// We do not have scrollbars or padding so this is the same as Element.clientWidth
return state.renderer.width();
return page.renderer.width();
}
pub fn get_name(self: *Window) []const u8 {
@@ -182,40 +182,40 @@ pub const Window = struct {
}
// TODO handle callback arguments.
pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, state: *SessionState) !u32 {
return self.createTimeout(cbk, delay, state, false);
pub fn _setTimeout(self: *Window, cbk: Function, delay: ?u32, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, false);
}
// TODO handle callback arguments.
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, state: *SessionState) !u32 {
return self.createTimeout(cbk, delay, state, true);
pub fn _setInterval(self: *Window, cbk: Function, delay: ?u32, page: *Page) !u32 {
return self.createTimeout(cbk, delay, page, true);
}
pub fn _clearTimeout(self: *Window, id: u32, state: *SessionState) !void {
pub fn _clearTimeout(self: *Window, id: u32, page: *Page) !void {
const kv = self.timers.fetchRemove(id) orelse return;
try state.loop.cancel(kv.value.loop_id);
try page.loop.cancel(kv.value.loop_id);
}
pub fn _clearInterval(self: *Window, id: u32, state: *SessionState) !void {
pub fn _clearInterval(self: *Window, id: u32, page: *Page) !void {
const kv = self.timers.fetchRemove(id) orelse return;
try state.loop.cancel(kv.value.loop_id);
try page.loop.cancel(kv.value.loop_id);
}
pub fn _matchMedia(_: *const Window, media: []const u8, state: *SessionState) !MediaQueryList {
pub fn _matchMedia(_: *const Window, media: []const u8, page: *Page) !MediaQueryList {
return .{
.matches = false, // TODO?
.media = try state.arena.dupe(u8, media),
.media = try page.arena.dupe(u8, media),
};
}
fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, state: *SessionState, comptime repeat: bool) !u32 {
fn createTimeout(self: *Window, cbk: Function, delay_: ?u32, page: *Page, comptime repeat: bool) !u32 {
if (self.timers.count() > 512) {
return error.TooManyTimeout;
}
const timer_id = self.timer_id +% 1;
self.timer_id = timer_id;
const arena = state.arena;
const arena = page.arena;
const gop = try self.timers.getOrPut(arena, timer_id);
if (gop.found_existing) {
@@ -235,7 +235,7 @@ pub const Window = struct {
.node = .{ .func = TimerCallback.run },
.repeat = if (repeat) delay else null,
};
callback.loop_id = try state.loop.timeout(delay, &callback.node);
callback.loop_id = try page.loop.timeout(delay, &callback.node);
gop.value_ptr.* = callback;
return timer_id;

View File

@@ -22,16 +22,16 @@ const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const Dump = @import("dump.zig");
const Env = @import("env.zig").Env;
const Mime = @import("mime.zig").Mime;
const DataURI = @import("datauri.zig").DataURI;
const Session = @import("session.zig").Session;
const Renderer = @import("renderer.zig").Renderer;
const SessionState = @import("env.zig").SessionState;
const Window = @import("html/window.zig").Window;
const Walker = @import("dom/walker.zig").WalkerDepthFirst;
const Env = @import("env.zig").Env;
const Loop = @import("../runtime/loop.zig").Loop;
const HTMLDocument = @import("html/document.zig").HTMLDocument;
const RequestFactory = @import("../http/client.zig").RequestFactory;
const URL = @import("../url.zig").URL;
@@ -48,13 +48,22 @@ const polyfill = @import("polyfill/polyfill.zig");
// The page handle all its memory in an arena allocator. The arena is reseted
// when end() is called.
pub const Page = struct {
// Our event loop
loop: *Loop,
cookie_jar: *storage.CookieJar,
// Pre-configured http/cilent.zig used to make HTTP requests.
request_factory: RequestFactory,
session: *Session,
// an arena with a lifetime for the entire duration of the page
// An arena with a lifetime for the entire duration of the page
arena: Allocator,
// Gets injected into any WebAPI method that needs it
state: SessionState,
// Managed by the JS runtime, meant to have a much shorter life than the
// above arena. It should only be used by WebAPIs.
call_arena: Allocator,
// Serves are the root object of our JavaScript environment
window: Window,
@@ -62,6 +71,8 @@ pub const Page = struct {
// The URL of the page
url: URL,
// If the body of the main page isn't HTML, we capture its raw bytes here
// (currently, this is only useful in fetch mode with the --dump option)
raw_data: ?[]const u8,
renderer: Renderer,
@@ -70,6 +81,8 @@ pub const Page = struct {
window_clicked_event_node: parser.EventNode,
// Our JavaScript context for this specific page. This is what we use to
// execute any JavaScript
scope: *Env.Scope,
// List of modules currently fetched/loaded.
@@ -87,21 +100,17 @@ pub const Page = struct {
.raw_data = null,
.url = URL.empty,
.session = session,
.call_arena = undefined,
.loop = browser.app.loop,
.renderer = Renderer.init(arena),
.cookie_jar = &session.cookie_jar,
.microtask_node = .{ .func = microtaskCallback },
.window_clicked_event_node = .{ .func = windowClicked },
.state = .{
.arena = arena,
.url = &self.url,
.window = &self.window,
.renderer = &self.renderer,
.loop = browser.app.loop,
.cookie_jar = &session.cookie_jar,
.request_factory = browser.http_client.requestFactory(browser.notification),
},
.scope = try session.executor.startScope(&self.window, &self.state, self, true),
.request_factory = browser.http_client.requestFactory(browser.notification),
.scope = undefined,
.module_map = .empty,
};
self.scope = try session.executor.startScope(&self.window, self, self, true);
// load polyfills
try polyfill.load(self.arena, self.scope);
@@ -180,7 +189,7 @@ pub const Page = struct {
try self.loadHTMLDoc(fbs.reader(), "utf-8");
// We do not processHTMLDoc here as we know we don't have any scripts
// This assumption may be false when CDP Page.addScriptToEvaluateOnNewDocument is implemented
try HTMLDocument.documentIsComplete(self.window.document, &self.state);
try HTMLDocument.documentIsComplete(self.window.document, self);
return;
}
@@ -243,7 +252,7 @@ pub const Page = struct {
}
// https://html.spec.whatwg.org/#read-html
fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void {
pub fn loadHTMLDoc(self: *Page, reader: anytype, charset: []const u8) !void {
const ccharset = try self.arena.dupeZ(u8, charset);
const html_doc = try parser.documentHTMLParse(reader, ccharset);
@@ -352,7 +361,7 @@ pub const Page = struct {
// at the point where all subresources apart from async script elements
// have loaded.
// https://html.spec.whatwg.org/#reporting-document-loading-status
try HTMLDocument.documentIsLoaded(html_doc, &self.state);
try HTMLDocument.documentIsLoaded(html_doc, self);
// eval async scripts.
for (async_scripts.items) |script| {
@@ -363,7 +372,7 @@ pub const Page = struct {
try parser.documentHTMLSetCurrentScript(html_doc, null);
}
try HTMLDocument.documentIsComplete(html_doc, &self.state);
try HTMLDocument.documentIsComplete(html_doc, self);
// dispatch window.load event
const loadevt = try parser.eventCreate();
@@ -470,7 +479,7 @@ pub const Page = struct {
errdefer request.deinit();
var arr: std.ArrayListUnmanaged(u8) = .{};
try self.state.cookie_jar.forRequest(&url.uri, arr.writer(self.arena), opts);
try self.cookie_jar.forRequest(&url.uri, arr.writer(self.arena), opts);
if (arr.items.len > 0) {
try request.addHeader("Cookie", arr.items, .{});
@@ -534,126 +543,145 @@ pub const Page = struct {
.session = self.session,
.href = try arena.dupe(u8, href),
};
_ = try self.state.loop.timeout(0, &navi.navigate_node);
_ = try self.loop.timeout(0, &navi.navigate_node);
},
else => {},
}
}
const DelayedNavigation = struct {
navigate_node: Loop.CallbackNode = .{ .func = DelayedNavigation.delayNavigate },
session: *Session,
href: []const u8,
fn delayNavigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
_ = repeat_delay;
const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node);
self.session.pageNavigate(self.href) catch |err| {
// TODO: should we trigger a specific event here?
log.err(.page, "delayed navigation error", .{ .err = err });
};
pub fn getOrCreateNodeWrapper(self: *Page, comptime T: type, node: *parser.Node) !*T {
if (try self.getNodeWrapper(T, node)) |wrap| {
return wrap;
}
const wrap = try self.arena.create(T);
wrap.* = T{};
parser.nodeSetEmbedderData(node, wrap);
return wrap;
}
pub fn getNodeWrapper(_: *Page, comptime T: type, node: *parser.Node) !?*T {
if (parser.nodeGetEmbedderData(node)) |wrap| {
return @alignCast(@ptrCast(wrap));
}
return null;
}
};
const DelayedNavigation = struct {
navigate_node: Loop.CallbackNode = .{ .func = DelayedNavigation.delay_navigate },
session: *Session,
href: []const u8,
fn delay_navigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
_ = repeat_delay;
const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node);
self.session.pageNavigate(self.href) catch |err| {
// TODO: should we trigger a specific event here?
log.err(.page, "delayed navigation error", .{ .err = err });
};
}
};
const Script = struct {
kind: Kind,
is_async: bool,
is_defer: bool,
src: ?[]const u8,
element: *parser.Element,
// The javascript to load after we successfully load the script
onload: ?[]const u8,
// The javascript to load if we have an error executing the script
// For now, we ignore this, since we still have a lot of errors that we
// shouldn't
//onerror: ?[]const u8,
const Kind = enum {
module,
javascript,
};
const Script = struct {
kind: Kind,
is_async: bool,
is_defer: bool,
src: ?[]const u8,
element: *parser.Element,
// The javascript to load after we successfully load the script
onload: ?[]const u8,
// The javascript to load if we have an error executing the script
// For now, we ignore this, since we still have a lot of errors that we
// shouldn't
//onerror: ?[]const u8,
const Kind = enum {
module,
javascript,
};
fn init(e: *parser.Element) !?Script {
// ignore non-script tags
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e)));
if (tag != .script) {
return null;
}
if (try parser.elementGetAttribute(e, "nomodule") != null) {
// these scripts should only be loaded if we don't support modules
// but since we do support modules, we can just skip them.
return null;
}
const kind = parseKind(try parser.elementGetAttribute(e, "type")) orelse {
return null;
};
return .{
.kind = kind,
.element = e,
.src = try parser.elementGetAttribute(e, "src"),
.onload = try parser.elementGetAttribute(e, "onload"),
.is_async = try parser.elementGetAttribute(e, "async") != null,
.is_defer = try parser.elementGetAttribute(e, "defer") != null,
};
}
// > type
// > Attribute is not set (default), an empty string, or a JavaScript MIME
// > type indicates that the script is a "classic script", containing
// > JavaScript code.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
fn parseKind(script_type_: ?[]const u8) ?Kind {
const script_type = script_type_ orelse return .javascript;
if (script_type.len == 0) {
return .javascript;
}
if (std.mem.eql(u8, script_type, "application/javascript")) return .javascript;
if (std.mem.eql(u8, script_type, "text/javascript")) return .javascript;
if (std.mem.eql(u8, script_type, "module")) return .module;
fn init(e: *parser.Element) !?Script {
// ignore non-script tags
const tag = try parser.elementHTMLGetTagType(@as(*parser.ElementHTML, @ptrCast(e)));
if (tag != .script) {
return null;
}
fn eval(self: *const Script, page: *Page, body: []const u8) !void {
var try_catch: Env.TryCatch = undefined;
try_catch.init(page.scope);
defer try_catch.deinit();
if (try parser.elementGetAttribute(e, "nomodule") != null) {
// these scripts should only be loaded if we don't support modules
// but since we do support modules, we can just skip them.
return null;
}
const src = self.src orelse "inline";
const res = switch (self.kind) {
.javascript => page.scope.exec(body, src),
.module => blk: {
switch (try page.scope.module(body, src)) {
.value => |v| break :blk v,
.exception => |e| {
log.warn(.page, "eval module", .{ .src = src, .err = try e.exception(page.arena) });
return error.JsErr;
},
}
},
} catch {
const kind = parseKind(try parser.elementGetAttribute(e, "type")) orelse {
return null;
};
return .{
.kind = kind,
.element = e,
.src = try parser.elementGetAttribute(e, "src"),
.onload = try parser.elementGetAttribute(e, "onload"),
.is_async = try parser.elementGetAttribute(e, "async") != null,
.is_defer = try parser.elementGetAttribute(e, "defer") != null,
};
}
// > type
// > Attribute is not set (default), an empty string, or a JavaScript MIME
// > type indicates that the script is a "classic script", containing
// > JavaScript code.
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attribute_is_not_set_default_an_empty_string_or_a_javascript_mime_type
fn parseKind(script_type_: ?[]const u8) ?Kind {
const script_type = script_type_ orelse return .javascript;
if (script_type.len == 0) {
return .javascript;
}
if (std.mem.eql(u8, script_type, "application/javascript")) return .javascript;
if (std.mem.eql(u8, script_type, "text/javascript")) return .javascript;
if (std.mem.eql(u8, script_type, "module")) return .module;
return null;
}
fn eval(self: *const Script, page: *Page, body: []const u8) !void {
var try_catch: Env.TryCatch = undefined;
try_catch.init(page.scope);
defer try_catch.deinit();
const src = self.src orelse "inline";
const res = switch (self.kind) {
.javascript => page.scope.exec(body, src),
.module => blk: {
switch (try page.scope.module(body, src)) {
.value => |v| break :blk v,
.exception => |e| {
log.warn(.page, "eval module", .{ .src = src, .err = try e.exception(page.arena) });
return error.JsErr;
},
}
},
} catch {
if (try try_catch.err(page.arena)) |msg| {
log.warn(.page, "eval script", .{ .src = src, .err = msg });
}
return error.JsErr;
};
_ = res;
if (self.onload) |onload| {
_ = page.scope.exec(onload, "script_on_load") catch {
if (try try_catch.err(page.arena)) |msg| {
log.warn(.page, "eval script", .{ .src = src, .err = msg });
log.warn(.page, "eval onload", .{ .src = src, .err = msg });
}
return error.JsErr;
};
_ = res;
if (self.onload) |onload| {
_ = page.scope.exec(onload, "script_on_load") catch {
if (try try_catch.err(page.arena)) |msg| {
log.warn(.page, "eval onload", .{ .src = src, .err = msg });
}
return error.JsErr;
};
}
}
};
}
};
pub const NavigateReason = enum {

View File

@@ -16,7 +16,7 @@ test "Browser.fetch" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit();
try @import("polyfill.zig").load(testing.allocator, runner.scope);
try @import("polyfill.zig").load(testing.allocator, runner.page.scope);
try runner.testCases(&.{
.{

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const query = @import("query.zig");
@@ -47,9 +47,9 @@ pub const URL = struct {
pub fn constructor(
url: []const u8,
base: ?[]const u8,
state: *SessionState,
page: *Page,
) !URL {
const arena = state.arena;
const arena = page.arena;
const raw = try std.mem.concat(arena, u8, &[_][]const u8{ url, base orelse "" });
const uri = std.Uri.parse(raw) catch return error.TypeError;
@@ -66,8 +66,8 @@ pub const URL = struct {
};
}
pub fn get_origin(self: *URL, state: *SessionState) ![]const u8 {
var buf = std.ArrayList(u8).init(state.arena);
pub fn get_origin(self: *URL, page: *Page) ![]const u8 {
var buf = std.ArrayList(u8).init(page.arena);
try self.uri.writeToStream(.{
.scheme = true,
.authentication = false,
@@ -82,8 +82,8 @@ pub const URL = struct {
// get_href returns the URL by writing all its components.
// The query is replaced by a dump of search params.
//
pub fn get_href(self: *URL, state: *SessionState) ![]const u8 {
const arena = state.arena;
pub fn get_href(self: *URL, page: *Page) ![]const u8 {
const arena = page.arena;
// retrieve the query search from search_params.
const cur = self.uri.query;
defer self.uri.query = cur;
@@ -109,8 +109,8 @@ pub const URL = struct {
return buf.items;
}
pub fn get_protocol(self: *URL, state: *SessionState) ![]const u8 {
return try std.mem.concat(state.arena, u8, &[_][]const u8{ self.uri.scheme, ":" });
pub fn get_protocol(self: *URL, page: *Page) ![]const u8 {
return try std.mem.concat(page.arena, u8, &[_][]const u8{ self.uri.scheme, ":" });
}
pub fn get_username(self: *URL) []const u8 {
@@ -121,8 +121,8 @@ pub const URL = struct {
return uriComponentNullStr(self.uri.password);
}
pub fn get_host(self: *URL, state: *SessionState) ![]const u8 {
var buf = std.ArrayList(u8).init(state.arena);
pub fn get_host(self: *URL, page: *Page) ![]const u8 {
var buf = std.ArrayList(u8).init(page.arena);
try self.uri.writeToStream(.{
.scheme = false,
@@ -139,8 +139,8 @@ pub const URL = struct {
return uriComponentNullStr(self.uri.host);
}
pub fn get_port(self: *URL, state: *SessionState) ![]const u8 {
const arena = state.arena;
pub fn get_port(self: *URL, page: *Page) ![]const u8 {
const arena = page.arena;
if (self.uri.port == null) return try arena.dupe(u8, "");
var buf = std.ArrayList(u8).init(arena);
@@ -153,8 +153,8 @@ pub const URL = struct {
return uriComponentStr(self.uri.path);
}
pub fn get_search(self: *URL, state: *SessionState) ![]const u8 {
const arena = state.arena;
pub fn get_search(self: *URL, page: *Page) ![]const u8 {
const arena = page.arena;
if (self.search_params.get_size() == 0) return try arena.dupe(u8, "");
var buf: std.ArrayListUnmanaged(u8) = .{};
@@ -164,8 +164,8 @@ pub const URL = struct {
return buf.items;
}
pub fn get_hash(self: *URL, state: *SessionState) ![]const u8 {
const arena = state.arena;
pub fn get_hash(self: *URL, page: *Page) ![]const u8 {
const arena = page.arena;
if (self.uri.fragment == null) return try arena.dupe(u8, "");
return try std.mem.concat(arena, u8, &[_][]const u8{ "#", uriComponentNullStr(self.uri.fragment) });
@@ -175,8 +175,8 @@ pub const URL = struct {
return &self.search_params;
}
pub fn _toJSON(self: *URL, state: *SessionState) ![]const u8 {
return try self.get_href(state);
pub fn _toJSON(self: *URL, page: *Page) ![]const u8 {
return try self.get_href(page);
}
};
@@ -200,8 +200,8 @@ fn uriComponentStr(c: std.Uri.Component) []const u8 {
pub const URLSearchParams = struct {
values: query.Values,
pub fn constructor(qs: ?[]const u8, state: *SessionState) !URLSearchParams {
return init(state.arena, qs);
pub fn constructor(qs: ?[]const u8, page: *Page) !URLSearchParams {
return init(page.arena, qs);
}
pub fn init(arena: std.mem.Allocator, qs: ?[]const u8) !URLSearchParams {

View File

@@ -25,7 +25,7 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget;
const EventHandler = @import("../events/event.zig").EventHandler;
const parser = @import("../netsurf.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
pub const XMLHttpRequestEventTarget = struct {
pub const prototype = *EventTarget;
@@ -86,34 +86,34 @@ pub const XMLHttpRequestEventTarget = struct {
return self.onloadend_cbk;
}
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
pub fn set_onloadstart(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
if (self.onloadstart_cbk) |cbk| try self.unregister("loadstart", cbk.id);
try self.register(state.arena, "loadstart", handler);
try self.register(page.arena, "loadstart", handler);
self.onloadstart_cbk = handler;
}
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
pub fn set_onprogress(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
if (self.onprogress_cbk) |cbk| try self.unregister("progress", cbk.id);
try self.register(state.arena, "progress", handler);
try self.register(page.arena, "progress", handler);
self.onprogress_cbk = handler;
}
pub fn set_onabort(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
pub fn set_onabort(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
if (self.onabort_cbk) |cbk| try self.unregister("abort", cbk.id);
try self.register(state.arena, "abort", handler);
try self.register(page.arena, "abort", handler);
self.onabort_cbk = handler;
}
pub fn set_onload(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
pub fn set_onload(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
if (self.onload_cbk) |cbk| try self.unregister("load", cbk.id);
try self.register(state.arena, "load", handler);
try self.register(page.arena, "load", handler);
self.onload_cbk = handler;
}
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
pub fn set_ontimeout(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
if (self.ontimeout_cbk) |cbk| try self.unregister("timeout", cbk.id);
try self.register(state.arena, "timeout", handler);
try self.register(page.arena, "timeout", handler);
self.ontimeout_cbk = handler;
}
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, handler: Function, state: *SessionState) !void {
pub fn set_onloadend(self: *XMLHttpRequestEventTarget, handler: Function, page: *Page) !void {
if (self.onloadend_cbk) |cbk| try self.unregister("loadend", cbk.id);
try self.register(state.arena, "loadend", handler);
try self.register(page.arena, "loadend", handler);
self.onloadend_cbk = handler;
}
};

View File

@@ -23,7 +23,7 @@ const Allocator = std.mem.Allocator;
const log = @import("../../log.zig");
const parser = @import("../netsurf.zig");
const iterator = @import("../iterator/iterator.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
pub const Interfaces = .{
FormData,
@@ -53,19 +53,19 @@ pub const Interfaces = .{
pub const FormData = struct {
entries: std.ArrayListUnmanaged(Entry),
pub fn constructor(form_: ?*parser.Form, submitter_: ?*parser.ElementHTML, state: *SessionState) !FormData {
pub fn constructor(form_: ?*parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !FormData {
const form = form_ orelse return .{ .entries = .empty };
return fromForm(form, submitter_, state, .{});
return fromForm(form, submitter_, page, .{});
}
const FromFormOpts = struct {
// Uses the state.arena if null. This is needed for when we're handling
// Uses the page.arena if null. This is needed for when we're handling
// form submission from the Page, and we want to capture the form within
// the session's transfer_arena.
allocator: ?Allocator = null,
};
pub fn fromForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, state: *SessionState, opts: FromFormOpts) !FormData {
const entries = try collectForm(opts.allocator orelse state.arena, form, submitter_, state);
pub fn fromForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page, opts: FromFormOpts) !FormData {
const entries = try collectForm(opts.allocator orelse page.arena, form, submitter_, page);
return .{ .entries = entries };
}
@@ -74,8 +74,8 @@ pub const FormData = struct {
return result.entry.value;
}
pub fn _getAll(self: *const FormData, key: []const u8, state: *SessionState) ![][]const u8 {
const arena = state.call_arena;
pub fn _getAll(self: *const FormData, key: []const u8, page: *Page) ![][]const u8 {
const arena = page.call_arena;
var arr: std.ArrayListUnmanaged([]const u8) = .empty;
for (self.entries.items) |entry| {
if (std.mem.eql(u8, key, entry.key)) {
@@ -91,15 +91,15 @@ pub const FormData = struct {
// TODO: value should be a string or blog
// TODO: another optional parameter for the filename
pub fn _set(self: *FormData, key: []const u8, value: []const u8, state: *SessionState) !void {
pub fn _set(self: *FormData, key: []const u8, value: []const u8, page: *Page) !void {
self._delete(key);
return self._append(key, value, state);
return self._append(key, value, page);
}
// TODO: value should be a string or blog
// TODO: another optional parameter for the filename
pub fn _append(self: *FormData, key: []const u8, value: []const u8, state: *SessionState) !void {
const arena = state.arena;
pub fn _append(self: *FormData, key: []const u8, value: []const u8, page: *Page) !void {
const arena = page.arena;
return self.entries.append(arena, .{ .key = try arena.dupe(u8, key), .value = try arena.dupe(u8, value) });
}
@@ -198,7 +198,7 @@ const EntryIterator = struct {
}
};
fn collectForm(arena: Allocator, form: *parser.Form, submitter_: ?*parser.ElementHTML, state: *SessionState) !std.ArrayListUnmanaged(Entry) {
fn collectForm(arena: Allocator, form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !std.ArrayListUnmanaged(Entry) {
const collection = try parser.formGetCollection(form);
const len = try parser.htmlCollectionGetLength(collection);
@@ -252,7 +252,7 @@ fn collectForm(arena: Allocator, form: *parser.Form, submitter_: ?*parser.Elemen
},
.select => {
const select: *parser.Select = @ptrCast(node);
try collectSelectValues(arena, select, name, &entries, state);
try collectSelectValues(arena, select, name, &entries, page);
},
.textarea => {
const textarea: *parser.TextArea = @ptrCast(node);
@@ -275,12 +275,12 @@ fn collectForm(arena: Allocator, form: *parser.Form, submitter_: ?*parser.Elemen
return entries;
}
fn collectSelectValues(arena: Allocator, select: *parser.Select, name: []const u8, entries: *std.ArrayListUnmanaged(Entry), state: *SessionState) !void {
fn collectSelectValues(arena: Allocator, select: *parser.Select, name: []const u8, entries: *std.ArrayListUnmanaged(Entry), page: *Page) !void {
const HTMLSelectElement = @import("../html/select.zig").HTMLSelectElement;
// Go through the HTMLSelectElement because it has specific logic for handling
// the default selected option, which libdom doesn't properly handle
const selected_index = try HTMLSelectElement.get_selectedIndex(select, state);
const selected_index = try HTMLSelectElement.get_selectedIndex(select, page);
if (selected_index == -1) {
return;
}

View File

@@ -29,7 +29,7 @@ const URL = @import("../../url.zig").URL;
const Mime = @import("../mime.zig").Mime;
const parser = @import("../netsurf.zig");
const http = @import("../../http/client.zig");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const CookieJar = @import("../storage/storage.zig").CookieJar;
// XHR interfaces
@@ -238,8 +238,8 @@ pub const XMLHttpRequest = struct {
const min_delay: u64 = 50000000; // 50ms
pub fn constructor(session_state: *SessionState) !XMLHttpRequest {
const arena = session_state.arena;
pub fn constructor(page: *Page) !XMLHttpRequest {
const arena = page.arena;
return .{
.arena = arena,
.headers = Headers.init(arena),
@@ -247,8 +247,8 @@ pub const XMLHttpRequest = struct {
.method = undefined,
.state = .unsent,
.url = null,
.origin_url = session_state.url,
.cookie_jar = session_state.cookie_jar,
.origin_url = &page.url,
.cookie_jar = page.cookie_jar,
};
}
@@ -408,7 +408,7 @@ pub const XMLHttpRequest = struct {
}
// TODO body can be either a XMLHttpRequestBodyInit or a document
pub fn _send(self: *XMLHttpRequest, body: ?[]const u8, session_state: *SessionState) !void {
pub fn _send(self: *XMLHttpRequest, body: ?[]const u8, page: *Page) !void {
if (self.state != .opened) return DOMError.InvalidState;
if (self.send_flag) return DOMError.InvalidState;
@@ -416,7 +416,7 @@ pub const XMLHttpRequest = struct {
self.send_flag = true;
self.request = try session_state.request_factory.create(self.method, &self.url.?.uri);
self.request = try page.request_factory.create(self.method, &self.url.?.uri);
var request = &self.request.?;
errdefer request.deinit();
@@ -426,7 +426,7 @@ pub const XMLHttpRequest = struct {
{
var arr: std.ArrayListUnmanaged(u8) = .{};
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(session_state.arena), .{
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(page.arena), .{
.navigation = false,
.origin_uri = &self.origin_url.uri,
});
@@ -442,12 +442,12 @@ pub const XMLHttpRequest = struct {
// var used_body: ?XMLHttpRequestBodyInit = null;
if (body) |b| {
if (self.method != .GET and self.method != .HEAD) {
request.body = try session_state.arena.dupe(u8, b);
request.body = try page.arena.dupe(u8, b);
try request.addHeader("Content-Type", "text/plain; charset=UTF-8", .{});
}
}
try request.sendAsync(session_state.loop, self, .{});
try request.sendAsync(page.loop, self, .{});
}
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: anyerror!http.Progress) !void {

View File

@@ -18,7 +18,7 @@
//
const std = @import("std");
const SessionState = @import("../env.zig").SessionState;
const Page = @import("../page.zig").Page;
const dump = @import("../dump.zig");
const parser = @import("../netsurf.zig");
@@ -33,8 +33,8 @@ pub const XMLSerializer = struct {
return .{};
}
pub fn _serializeToString(_: *const XMLSerializer, root: *parser.Node, state: *SessionState) ![]const u8 {
var buf = std.ArrayList(u8).init(state.arena);
pub fn _serializeToString(_: *const XMLSerializer, root: *parser.Node, page: *Page) ![]const u8 {
var buf = std.ArrayList(u8).init(page.arena);
if (try parser.nodeType(root) == .document) {
try dump.writeHTML(@as(*parser.Document, @ptrCast(root)), buf.writer());
} else {

View File

@@ -555,7 +555,7 @@ const IsolatedWorld = struct {
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
if (self.executor.scope != null) return error.Only1IsolatedContextSupported;
_ = try self.executor.startScope(&page.window, &page.state, {}, false);
_ = try self.executor.startScope(&page.window, page, {}, false);
}
};

View File

@@ -383,6 +383,7 @@ fn getContentQuads(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
@@ -397,7 +398,7 @@ fn getContentQuads(cmd: anytype) !void {
// Elements like SVGElement may have multiple quads.
const element = parser.nodeToElement(node._node);
const rect = try Element._getBoundingClientRect(element, &bc.session.page.?.state);
const rect = try Element._getBoundingClientRect(element, page);
const quad = rectToQuad(rect);
return cmd.sendResult(.{ .quads = &.{quad} }, .{});
@@ -411,6 +412,7 @@ fn getBoxModel(cmd: anytype) !void {
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);
@@ -418,7 +420,7 @@ fn getBoxModel(cmd: anytype) !void {
if (try parser.nodeType(node._node) != .element) return error.NodeIsNotAnElement;
const element = parser.nodeToElement(node._node);
const rect = try Element._getBoundingClientRect(element, &bc.session.page.?.state);
const rect = try Element._getBoundingClientRect(element, page);
const quad = rectToQuad(rect);
return cmd.sendResult(.{ .model = BoxModel{

View File

@@ -113,10 +113,10 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
});
defer runner.deinit();
try polyfill.load(arena, runner.scope);
try polyfill.load(arena, runner.page.scope);
// loop over the scripts.
const doc = parser.documentHTMLToDocument(runner.state.window.document);
const doc = parser.documentHTMLToDocument(runner.page.window.document);
const scripts = try parser.documentGetElementsByTagName(doc, "script");
const script_count = try parser.nodeListLength(scripts);
for (0..script_count) |i| {
@@ -147,7 +147,7 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
try parser.eventInit(loadevt, "load", .{});
_ = try parser.eventTargetDispatchEvent(
parser.toEventTarget(@TypeOf(runner.window), &runner.window),
parser.toEventTarget(@TypeOf(runner.page.window), &runner.page.window),
loadevt,
);
}
@@ -155,9 +155,9 @@ fn run(arena: Allocator, test_file: []const u8, loader: *FileLoader, err_out: *?
{
// wait for all async executions
var try_catch: Env.TryCatch = undefined;
try_catch.init(runner.scope);
try_catch.init(runner.page.scope);
defer try_catch.deinit();
try runner.loop.run();
try runner.page.loop.run();
if (try_catch.hasCaught()) {
err_out.* = (try try_catch.err(arena)) orelse "unknwon error";

View File

@@ -23,7 +23,7 @@ const generate = @import("generate.zig");
pub const allocator = std.testing.allocator;
// Very similar to the JSRunner in src/testing.zig, but it isn't tied to the
// browser.Env or the browser.SessionState
// browser.Env or the *Page state
pub fn Runner(comptime State: type, comptime Global: type, comptime types: anytype) type {
const AdjustedTypes = if (Global == void) generate.Tuple(.{ types, DefaultGlobal }) else types;

View File

@@ -183,7 +183,7 @@ test "telemetry: getOrCreateId" {
}
test "telemetry: sends event to provider" {
var app = testing.app(.{});
var app = testing.createApp(.{});
defer app.deinit();
var telemetry = TelemetryT(MockProvider).init(app, .serve);

View File

@@ -171,7 +171,7 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
}
// dummy opts incase we want to add something, and not have to break all the callers
pub fn app(_: anytype) *App {
pub fn createApp(_: anytype) *App {
return App.init(allocator, .{ .run_mode = .serve }) catch unreachable;
}
@@ -367,114 +367,79 @@ pub const tracking_allocator = @import("root").tracking_allocator.allocator();
pub const JsRunner = struct {
const URL = @import("url.zig").URL;
const Env = @import("browser/env.zig").Env;
const Loop = @import("runtime/loop.zig").Loop;
const HttpClient = @import("http/client.zig").Client;
const storage = @import("browser/storage/storage.zig");
const Window = @import("browser/html/window.zig").Window;
const Renderer = @import("browser/renderer.zig").Renderer;
const SessionState = @import("browser/env.zig").SessionState;
const Page = @import("browser/page.zig").Page;
const Browser = @import("browser/browser.zig").Browser;
url: URL,
env: *Env,
loop: Loop,
window: Window,
state: SessionState,
arena: Allocator,
renderer: Renderer,
http_client: HttpClient,
scope: *Env.Scope,
executor: Env.ExecutionWorld,
storage_shelf: storage.Shelf,
cookie_jar: storage.CookieJar,
app: *App,
page: *Page,
browser: *Browser,
fn init(parent_allocator: Allocator, opts: RunnerOpts) !*JsRunner {
fn init(alloc: Allocator, opts: RunnerOpts) !JsRunner {
parser.deinit();
try parser.init();
const aa = try parent_allocator.create(std.heap.ArenaAllocator);
aa.* = std.heap.ArenaAllocator.init(parent_allocator);
errdefer aa.deinit();
const arena = aa.allocator();
const self = try arena.create(JsRunner);
self.arena = arena;
self.env = try Env.init(arena, .{});
errdefer self.env.deinit();
self.url = try URL.parse(opts.url, null);
self.renderer = Renderer.init(arena);
self.cookie_jar = storage.CookieJar.init(arena);
self.loop = try Loop.init(arena);
errdefer self.loop.deinit();
var html = std.io.fixedBufferStream(opts.html);
const document = try parser.documentHTMLParse(html.reader(), "UTF-8");
self.window = try Window.create(null, null);
try self.window.replaceDocument(document);
try self.window.replaceLocation(.{
.url = try self.url.toWebApi(arena),
});
self.http_client = try HttpClient.init(arena, 1, .{
var app = try App.init(alloc, .{
.run_mode = .serve,
.tls_verify_host = false,
});
errdefer app.deinit();
self.state = .{
.arena = arena,
.loop = &self.loop,
.url = &self.url,
.window = &self.window,
.renderer = &self.renderer,
.cookie_jar = &self.cookie_jar,
.request_factory = self.http_client.requestFactory(null),
const browser = try alloc.create(Browser);
errdefer alloc.destroy(browser);
browser.* = try Browser.init(app);
errdefer browser.deinit();
var session = try browser.newSession();
var page = try session.createPage();
// a bit hacky, but since we aren't going through page.navigate, there's
// some minimum setup we need to do
page.url = try URL.parse(opts.url, null);
try page.window.replaceLocation(.{
.url = try page.url.toWebApi(page.arena),
});
var html = std.io.fixedBufferStream(opts.html);
try page.loadHTMLDoc(html.reader(), "UTF-8");
return .{
.app = app,
.page = page,
.browser = browser,
};
self.storage_shelf = storage.Shelf.init(arena);
self.window.setStorageShelf(&self.storage_shelf);
self.executor = try self.env.newExecutionWorld();
errdefer self.executor.deinit();
self.scope = try self.executor.startScope(&self.window, &self.state, {}, true);
return self;
}
pub fn deinit(self: *JsRunner) void {
self.loop.deinit();
self.executor.deinit();
self.env.deinit();
self.http_client.deinit();
self.storage_shelf.deinit();
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(self.arena.ptr));
arena.deinit();
arena.child_allocator.destroy(arena);
self.browser.deinit();
self.app.allocator.destroy(self.browser);
self.app.deinit();
}
const RunOpts = struct {};
pub const Case = std.meta.Tuple(&.{ []const u8, ?[]const u8 });
pub fn testCases(self: *JsRunner, cases: []const Case, _: RunOpts) !void {
const scope = self.page.scope;
const arena = self.page.arena;
const start = try std.time.Instant.now();
for (cases, 0..) |case, i| {
var try_catch: Env.TryCatch = undefined;
try_catch.init(self.scope);
try_catch.init(scope);
defer try_catch.deinit();
const value = self.scope.exec(case.@"0", null) catch |err| {
if (try try_catch.err(self.arena)) |msg| {
const value = scope.exec(case.@"0", null) catch |err| {
if (try try_catch.err(arena)) |msg| {
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
}
return err;
};
try self.loop.run();
try self.page.loop.run();
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
if (case.@"1") |expected| {
const actual = try value.toString(self.arena);
const actual = try value.toString(arena);
if (std.mem.eql(u8, expected, actual) == false) {
std.debug.print("Expected:\n{s}\n\nGot:\n{s}\n\nCase: {d}\n{s}\n", .{ expected, actual, i + 1, case.@"0" });
return error.UnexpectedResult;
@@ -488,12 +453,15 @@ pub const JsRunner = struct {
}
pub fn eval(self: *JsRunner, src: []const u8, name: ?[]const u8, err_msg: *?[]const u8) !Env.Value {
const scope = self.page.scope;
const arena = self.page.arena;
var try_catch: Env.TryCatch = undefined;
try_catch.init(self.scope);
try_catch.init(scope);
defer try_catch.deinit();
return self.scope.exec(src, name) catch |err| {
if (try try_catch.err(self.arena)) |msg| {
return scope.exec(src, name) catch |err| {
if (try try_catch.err(arena)) |msg| {
err_msg.* = msg;
std.debug.print("Error running script: {s}\n", .{msg});
}
@@ -517,6 +485,6 @@ const RunnerOpts = struct {
,
};
pub fn jsRunner(alloc: Allocator, opts: RunnerOpts) !*JsRunner {
pub fn jsRunner(alloc: Allocator, opts: RunnerOpts) !JsRunner {
return JsRunner.init(alloc, opts);
}