diff --git a/src/browser/console/console.zig b/src/browser/console/console.zig
index 3e3fd02b..1edbc612 100644
--- a/src/browser/console/console.zig
+++ b/src/browser/console/console.zig
@@ -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| {
diff --git a/src/browser/cssom/css_style_declaration.zig b/src/browser/cssom/css_style_declaration.zig
index 62779f5f..1e980fb2 100644
--- a/src/browser/cssom/css_style_declaration.zig
+++ b/src/browser/cssom/css_style_declaration.zig
@@ -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" },
diff --git a/src/browser/dom/comment.zig b/src/browser/dom/comment.zig
index 19aa9781..62c6bd4a 100644
--- a/src/browser/dom/comment.zig
+++ b/src/browser/dom/comment.zig
@@ -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 "",
);
}
diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig
index 03d15616..a987717b 100644
--- a/src/browser/dom/document.zig
+++ b/src/browser/dom/document.zig
@@ -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(&.{
diff --git a/src/browser/dom/document_fragment.zig b/src/browser/dom/document_fragment.zig
index 7bb0e5c1..0085ec33 100644
--- a/src/browser/dom/document_fragment.zig
+++ b/src/browser/dom/document_fragment.zig
@@ -17,7 +17,7 @@
// along with this program. If not, see .
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),
);
}
diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig
index 976025d6..12e5f39d 100644
--- a/src/browser/dom/element.zig
+++ b/src/browser/dom/element.zig
@@ -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) });
}
diff --git a/src/browser/dom/event_target.zig b/src/browser/dom/event_target.zig
index f25c7e76..bd320385 100644
--- a/src/browser/dom/event_target.zig
+++ b/src/browser/dom/event_target.zig
@@ -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");
diff --git a/src/browser/dom/intersection_observer.zig b/src/browser/dom/intersection_observer.zig
index 18b02508..17e29787 100644
--- a/src/browser/dom/intersection_observer.zig
+++ b/src/browser/dom/intersection_observer.zig
@@ -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.
diff --git a/src/browser/dom/mutation_observer.zig b/src/browser/dom/mutation_observer.zig
index e838394b..c4deff88 100644
--- a/src/browser/dom/mutation_observer.zig
+++ b/src/browser/dom/mutation_observer.zig
@@ -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,
};
}
diff --git a/src/browser/dom/node.zig b/src/browser/dom/node.zig
index ea55fa5f..61cd1301 100644
--- a/src/browser/dom/node.zig
+++ b/src/browser/dom/node.zig
@@ -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;
diff --git a/src/browser/dom/processing_instruction.zig b/src/browser/dom/processing_instruction.zig
index ae4d93f6..8165e69d 100644
--- a/src/browser/dom/processing_instruction.zig
+++ b/src/browser/dom/processing_instruction.zig
@@ -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 "",
);
diff --git a/src/browser/dom/text.zig b/src/browser/dom/text.zig
index c12d3402..b1334db5 100644
--- a/src/browser/dom/text.zig
+++ b/src/browser/dom/text.zig
@@ -17,7 +17,7 @@
// along with this program. If not, see .
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 "",
);
}
diff --git a/src/browser/dom/tree_walker.zig b/src/browser/dom/tree_walker.zig
index 826bb062..a79567b3 100644
--- a/src/browser/dom/tree_walker.zig
+++ b/src/browser/dom/tree_walker.zig
@@ -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 {
diff --git a/src/browser/dump.zig b/src/browser/dump.zig
index 57084dec..18923ed1 100644
--- a/src/browser/dump.zig
+++ b/src/browser/dump.zig
@@ -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(
"
Over 9000!
",
"Over 9000!
",
diff --git a/src/browser/env.zig b/src/browser/env.zig
index b4b8d866..7d6627ec 100644
--- a/src/browser/env.zig
+++ b/src/browser/env.zig
@@ -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;
diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig
index a677bd01..f0b533d4 100644
--- a/src/browser/html/document.zig
+++ b/src/browser/html/document.zig
@@ -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" },
}, .{});
diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig
index d93ba2b4..0f4b16aa 100644
--- a/src/browser/html/elements.zig
+++ b/src/browser/html/elements.zig
@@ -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_);
diff --git a/src/browser/html/location.zig b/src/browser/html/location.zig
index 52968d13..604b9622 100644
--- a/src/browser/html/location.zig
+++ b/src/browser/html/location.zig
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-const 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);
}
};
diff --git a/src/browser/html/select.zig b/src/browser/html/select.zig
index f07e5d64..5a73138f 100644
--- a/src/browser/html/select.zig
+++ b/src/browser/html/select.zig
@@ -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);
diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig
index 164e220a..b58f7cbc 100644
--- a/src/browser/html/window.zig
+++ b/src/browser/html/window.zig
@@ -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;
diff --git a/src/browser/page.zig b/src/browser/page.zig
index ed2a2cbd..e5ac0d38 100644
--- a/src/browser/page.zig
+++ b/src/browser/page.zig
@@ -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 {
diff --git a/src/browser/polyfill/fetch.zig b/src/browser/polyfill/fetch.zig
index a0166bdd..d062a2cb 100644
--- a/src/browser/polyfill/fetch.zig
+++ b/src/browser/polyfill/fetch.zig
@@ -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(&.{
.{
diff --git a/src/browser/url/url.zig b/src/browser/url/url.zig
index 3bf496bd..fd790380 100644
--- a/src/browser/url/url.zig
+++ b/src/browser/url/url.zig
@@ -17,7 +17,7 @@
// along with this program. If not, see .
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 {
diff --git a/src/browser/xhr/event_target.zig b/src/browser/xhr/event_target.zig
index cb6e36e0..fe686973 100644
--- a/src/browser/xhr/event_target.zig
+++ b/src/browser/xhr/event_target.zig
@@ -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;
}
};
diff --git a/src/browser/xhr/form_data.zig b/src/browser/xhr/form_data.zig
index ae8a3023..afa2e699 100644
--- a/src/browser/xhr/form_data.zig
+++ b/src/browser/xhr/form_data.zig
@@ -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;
}
diff --git a/src/browser/xhr/xhr.zig b/src/browser/xhr/xhr.zig
index 9dc71036..eb71b5fa 100644
--- a/src/browser/xhr/xhr.zig
+++ b/src/browser/xhr/xhr.zig
@@ -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 {
diff --git a/src/browser/xmlserializer/xmlserializer.zig b/src/browser/xmlserializer/xmlserializer.zig
index 207db972..17c362f8 100644
--- a/src/browser/xmlserializer/xmlserializer.zig
+++ b/src/browser/xmlserializer/xmlserializer.zig
@@ -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 {
diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig
index 53644410..19ba3c71 100644
--- a/src/cdp/cdp.zig
+++ b/src/cdp/cdp.zig
@@ -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);
}
};
diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig
index 53d8e19a..9af5c89e 100644
--- a/src/cdp/domains/dom.zig
+++ b/src/cdp/domains/dom.zig
@@ -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{
diff --git a/src/main_wpt.zig b/src/main_wpt.zig
index cd2b5536..614b4968 100644
--- a/src/main_wpt.zig
+++ b/src/main_wpt.zig
@@ -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";
diff --git a/src/runtime/testing.zig b/src/runtime/testing.zig
index c51298c0..8cfc5c44 100644
--- a/src/runtime/testing.zig
+++ b/src/runtime/testing.zig
@@ -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;
diff --git a/src/telemetry/telemetry.zig b/src/telemetry/telemetry.zig
index b3bc6b06..93b0e4e1 100644
--- a/src/telemetry/telemetry.zig
+++ b/src/telemetry/telemetry.zig
@@ -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);
diff --git a/src/testing.zig b/src/testing.zig
index d800afb5..faf3455d 100644
--- a/src/testing.zig
+++ b/src/testing.zig
@@ -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);
}