From 19df73729ab9b16510c8eac2d9c6c97a294571ce Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 4 Jun 2025 18:04:39 +0800 Subject: [PATCH 1/2] Improve usability of NodeWrapper The NodeWrapper pattern attaches a Zig instance to a libdom Node. That works in isolation, but for 1 given node, we might want to attach different instances. For example, for an HTMLScriptElement we want to attach an `onError`, but for that same node viewed as an HTMLElement we want to a `CSSStyleDeclaration`. We can only have one. Currently, this code will crash if, for example, we create the embedded data as an HTMLScriptElement, then try to read the embedded data as an HTMLElement. This PR introduces dedicated state class. So if you want the onError property, you no longer ask the NodeWrapper for an HTMLSCriptElement. Instead, you ask for a storage/HTMLElement. Nothing fancy here, just memory-inefficient optional fields. If it gets out of hand, we'll think of something more clever. --- src/browser/dom/document.zig | 20 +++++++++----- src/browser/html/document.zig | 29 ++++++++------------ src/browser/html/elements.zig | 45 ++++++++++++------------------- src/browser/page.zig | 4 +-- src/browser/state/Document.zig | 31 +++++++++++++++++++++ src/browser/state/HTMLElement.zig | 24 +++++++++++++++++ 6 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 src/browser/state/Document.zig create mode 100644 src/browser/state/HTMLElement.zig diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index 4d6832e3..f96e20bb 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -28,6 +28,7 @@ const NodeUnion = @import("node.zig").Union; const collection = @import("html_collection.zig"); const css = @import("css.zig"); +const State = @import("../state/Document.zig"); const Element = @import("element.zig").Element; const ElementUnion = @import("element.zig").Union; const TreeWalker = @import("tree_walker.zig").TreeWalker; @@ -42,8 +43,6 @@ pub const Document = struct { pub const prototype = *Node; pub const subtype = .node; - active_element: ?*parser.Element = null, - pub fn constructor(page: *const Page) !*parser.DocumentHTML { const doc = try parser.documentCreateDocument( try parser.documentHTMLGetTitle(page.window.document), @@ -245,9 +244,9 @@ pub const Document = struct { return try TreeWalker.init(root, what_to_show, filter); } - pub fn get_activeElement(doc: *parser.Document, page: *Page) !?ElementUnion { - const self = try page.getOrCreateNodeWrapper(Document, @ptrCast(doc)); - if (self.active_element) |ae| { + pub fn get_activeElement(self: *parser.Document, page: *Page) !?ElementUnion { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + if (state.active_element) |ae| { return try Element.toInterface(ae); } @@ -255,7 +254,16 @@ pub const Document = struct { return try Element.toInterface(@ptrCast(body)); } - return get_documentElement(doc); + return get_documentElement(self); + } + + // TODO: some elements can't be focused, like if they're disabled + // but there doesn't seem to be a generic way to check this. For example + // we could look for the "disabled" attribute, but that's only meaningful + // on certain types, and libdom's vtable doesn't seem to expose this. + pub fn setFocus(self: *parser.Document, e: *parser.ElementHTML, page: *Page) !void { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + state.active_element = @ptrCast(e); } }; diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index 5c74fe7e..384bc9d7 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -30,6 +30,7 @@ const NodeList = @import("../dom/nodelist.zig").NodeList; const Location = @import("location.zig").Location; const collection = @import("../dom/html_collection.zig"); +const State = @import("../state/Document.zig"); const Walker = @import("../dom/walker.zig").WalkerDepthFirst; const Cookie = @import("../storage/cookie.zig").Cookie; @@ -39,14 +40,6 @@ pub const HTMLDocument = struct { pub const prototype = *Document; pub const subtype = .node; - ready_state: ReadyState = .loading, - - const ReadyState = enum { - loading, - interactive, - complete, - }; - // JS funcs // -------- @@ -191,9 +184,9 @@ pub const HTMLDocument = struct { return &page.window; } - pub fn get_readyState(node: *parser.DocumentHTML, page: *Page) ![]const u8 { - const self = try page.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(node)); - return @tagName(self.ready_state); + pub fn get_readyState(self: *parser.DocumentHTML, page: *Page) ![]const u8 { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + return @tagName(state.ready_state); } // noop legacy functions @@ -270,9 +263,9 @@ pub const HTMLDocument = struct { return list.items; } - pub fn documentIsLoaded(html_doc: *parser.DocumentHTML, page: *Page) !void { - const self = try page.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc)); - self.ready_state = .interactive; + pub fn documentIsLoaded(self: *parser.DocumentHTML, page: *Page) !void { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + state.ready_state = .interactive; const evt = try parser.eventCreate(); defer parser.eventDestroy(evt); @@ -282,12 +275,12 @@ pub const HTMLDocument = struct { .source = "document", }); try parser.eventInit(evt, "DOMContentLoaded", .{ .bubbles = true, .cancelable = true }); - _ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, html_doc), evt); + _ = try parser.eventTargetDispatchEvent(parser.toEventTarget(parser.DocumentHTML, self), evt); } - pub fn documentIsComplete(html_doc: *parser.DocumentHTML, page: *Page) !void { - const self = try page.getOrCreateNodeWrapper(HTMLDocument, @ptrCast(html_doc)); - self.ready_state = .complete; + pub fn documentIsComplete(self: *parser.DocumentHTML, page: *Page) !void { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + state.ready_state = .complete; } }; diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index d0bb23b4..5dae5c2a 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -25,6 +25,7 @@ const Page = @import("../page.zig").Page; const urlStitch = @import("../../url.zig").URL.stitch; const URL = @import("../url/url.zig").URL; const Node = @import("../dom/node.zig").Node; +const State = @import("../state/HTMLElement.zig"); const Element = @import("../dom/element.zig").Element; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; @@ -112,11 +113,9 @@ pub const HTMLElement = struct { pub const prototype = *Element; pub const subtype = .node; - style: CSSStyleDeclaration = .empty, - pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration { - const self = try page.getOrCreateNodeWrapper(HTMLElement, @ptrCast(e)); - return &self.style; + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(e)); + return &state.style; } pub fn get_innerText(e: *parser.ElementHTML) ![]const u8 { @@ -159,16 +158,9 @@ pub const HTMLElement = struct { return; } - const root_node = try parser.nodeGetRootNode(@ptrCast(e)); - const Document = @import("../dom/document.zig").Document; - const document = try page.getOrCreateNodeWrapper(Document, @ptrCast(root_node)); - - // TODO: some elements can't be focused, like if they're disabled - // but there doesn't seem to be a generic way to check this. For example - // we could look for the "disabled" attribute, but that's only meaningful - // on certain types, and libdom's vtable doesn't seem to expose this. - document.active_element = @ptrCast(e); + const root_node = try parser.nodeGetRootNode(@ptrCast(e)); + try Document.setFocus(@ptrCast(root_node), e, page); } }; @@ -852,9 +844,6 @@ pub const HTMLScriptElement = struct { pub const prototype = *HTMLElement; pub const subtype = .node; - onload: ?Env.Function = null, - onerror: ?Env.Function = null, - pub fn get_src(self: *parser.Script) !?[]const u8 { return try parser.elementGetAttribute( parser.scriptToElt(self), @@ -964,24 +953,24 @@ pub const HTMLScriptElement = struct { return try parser.elementRemoveAttribute(parser.scriptToElt(self), "nomodule"); } - pub fn get_onload(script: *parser.Script, page: *Page) !?Env.Function { - const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null; - return self.onload; + pub fn get_onload(self: *parser.Script, page: *Page) !?Env.Function { + const state = page.getNodeWrapper(State, @ptrCast(self)) orelse return null; + return state.onload; } - pub fn set_onload(script: *parser.Script, function: ?Env.Function, page: *Page) !void { - const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script)); - self.onload = function; + pub fn set_onload(self: *parser.Script, function: ?Env.Function, page: *Page) !void { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + state.onload = function; } - pub fn get_onerror(script: *parser.Script, page: *Page) !?Env.Function { - const self = page.getNodeWrapper(HTMLScriptElement, @ptrCast(script)) orelse return null; - return self.onerror; + pub fn get_onerror(self: *parser.Script, page: *Page) !?Env.Function { + const state = page.getNodeWrapper(State, @ptrCast(self)) orelse return null; + return state.onerror; } - pub fn set_onerror(script: *parser.Script, function: ?Env.Function, page: *Page) !void { - const self = try page.getOrCreateNodeWrapper(HTMLScriptElement, @ptrCast(script)); - self.onerror = function; + pub fn set_onerror(self: *parser.Script, function: ?Env.Function, page: *Page) !void { + const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + state.onerror = function; } }; diff --git a/src/browser/page.zig b/src/browser/page.zig index cd633a3d..fa8088a9 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -743,8 +743,8 @@ const Script = struct { // attached to it. But this seems quite unlikely and it does help // optimize loading scripts, of which there can be hundreds for a // page. - const HTMLScriptElement = @import("html/elements.zig").HTMLScriptElement; - if (page.getNodeWrapper(HTMLScriptElement, @ptrCast(e))) |se| { + const State = @import("state/HTMLElement.zig"); + if (page.getNodeWrapper(State, @ptrCast(e))) |se| { if (se.onload) |function| { onload = .{ .function = function }; } diff --git a/src/browser/state/Document.zig b/src/browser/state/Document.zig new file mode 100644 index 00000000..dc2b6050 --- /dev/null +++ b/src/browser/state/Document.zig @@ -0,0 +1,31 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const parser = @import("../netsurf.zig"); + +// proxy-owner for html/document +ready_state: ReadyState = .loading, + +// proxy-owner for dom/document +active_element: ?*parser.Element = null, + +const ReadyState = enum { + loading, + interactive, + complete, +}; diff --git a/src/browser/state/HTMLElement.zig b/src/browser/state/HTMLElement.zig new file mode 100644 index 00000000..0a41b207 --- /dev/null +++ b/src/browser/state/HTMLElement.zig @@ -0,0 +1,24 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const Env = @import("../env.zig").Env; +const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; + +onload: ?Env.Function = null, +onerror: ?Env.Function = null, +style: CSSStyleDeclaration = .empty, From 4165f47a648060c93be4d2b061a22304c6885ba7 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 4 Jun 2025 19:52:23 +0800 Subject: [PATCH 2/2] merge all states --- src/browser/State.zig | 65 +++++++++++++++++++++++++++++++ src/browser/browser.zig | 4 ++ src/browser/dom/document.zig | 5 +-- src/browser/html/document.zig | 7 ++-- src/browser/html/elements.zig | 11 +++--- src/browser/html/select.zig | 22 ++--------- src/browser/page.zig | 25 ++++++------ src/browser/session.zig | 1 + src/browser/state/Document.zig | 31 --------------- src/browser/state/HTMLElement.zig | 24 ------------ 10 files changed, 98 insertions(+), 97 deletions(-) create mode 100644 src/browser/State.zig delete mode 100644 src/browser/state/Document.zig delete mode 100644 src/browser/state/HTMLElement.zig diff --git a/src/browser/State.zig b/src/browser/State.zig new file mode 100644 index 00000000..55447923 --- /dev/null +++ b/src/browser/State.zig @@ -0,0 +1,65 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Sometimes we need to extend libdom. For example, its HTMLDocument doesn't +// have a readyState. We have a couple different options, such as making the +// correction in libdom directly. Another option stems from the fact that every +// libdom node has an opaque embedder_data field. This is the struct that we +// lazily load into that field. +// +// It didn't originally start off as a collection of every single extension, but +// this quickly proved necessary, since different fields are needed on the same +// data at different levels of the prototype chain. This isn't memory efficient. + +const Env = @import("env.zig").Env; +const parser = @import("netsurf.zig"); +const CSSStyleDeclaration = @import("cssom/css_style_declaration.zig").CSSStyleDeclaration; + +// for HTMLScript (but probably needs to be added to more) +onload: ?Env.Function = null, +onerror: ?Env.Function = null, + +// for HTMLElement +style: CSSStyleDeclaration = .empty, + +// for html/document +ready_state: ReadyState = .loading, + +// for dom/document +active_element: ?*parser.Element = null, + +// for HTMLSelectElement +// By default, if no option is explicitly selected, the first option should +// be selected. However, libdom doesn't do this, and it sets the +// selectedIndex to -1, which is a valid value for "nothing selected". +// Therefore, when libdom says the selectedIndex == -1, we don't know if +// it means that nothing is selected, or if the first option is selected by +// default. +// There are cases where this won't work, but when selectedIndex is +// explicitly set, we set this boolean flag. Then, when we're getting then +// selectedIndex, if this flag is == false, which is to say that if +// selectedIndex hasn't been explicitly set AND if we have at least 1 option +// AND if it isn't a multi select, we can make the 1st item selected by +// default (by returning selectedIndex == 0). +explicit_index_set: bool = false, + +const ReadyState = enum { + loading, + interactive, + complete, +}; diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 0a4db620..8bf24e36 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -21,6 +21,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; +const State = @import("State.zig"); const Env = @import("env.zig").Env; const App = @import("../app.zig").App; const Session = @import("session.zig").Session; @@ -41,6 +42,7 @@ pub const Browser = struct { session_arena: ArenaAllocator, transfer_arena: ArenaAllocator, notification: *Notification, + state_pool: std.heap.MemoryPool(State), pub fn init(app: *App) !Browser { const allocator = app.allocator; @@ -61,6 +63,7 @@ pub const Browser = struct { .page_arena = ArenaAllocator.init(allocator), .session_arena = ArenaAllocator.init(allocator), .transfer_arena = ArenaAllocator.init(allocator), + .state_pool = std.heap.MemoryPool(State).init(allocator), }; } @@ -71,6 +74,7 @@ pub const Browser = struct { self.session_arena.deinit(); self.transfer_arena.deinit(); self.notification.deinit(); + self.state_pool.deinit(); } pub fn newSession(self: *Browser) !*Session { diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index f96e20bb..633f069f 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -28,7 +28,6 @@ const NodeUnion = @import("node.zig").Union; const collection = @import("html_collection.zig"); const css = @import("css.zig"); -const State = @import("../state/Document.zig"); const Element = @import("element.zig").Element; const ElementUnion = @import("element.zig").Union; const TreeWalker = @import("tree_walker.zig").TreeWalker; @@ -245,7 +244,7 @@ pub const Document = struct { } pub fn get_activeElement(self: *parser.Document, page: *Page) !?ElementUnion { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); if (state.active_element) |ae| { return try Element.toInterface(ae); } @@ -262,7 +261,7 @@ pub const Document = struct { // we could look for the "disabled" attribute, but that's only meaningful // on certain types, and libdom's vtable doesn't seem to expose this. pub fn setFocus(self: *parser.Document, e: *parser.ElementHTML, page: *Page) !void { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); state.active_element = @ptrCast(e); } }; diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index 384bc9d7..240df53c 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -30,7 +30,6 @@ const NodeList = @import("../dom/nodelist.zig").NodeList; const Location = @import("location.zig").Location; const collection = @import("../dom/html_collection.zig"); -const State = @import("../state/Document.zig"); const Walker = @import("../dom/walker.zig").WalkerDepthFirst; const Cookie = @import("../storage/cookie.zig").Cookie; @@ -185,7 +184,7 @@ pub const HTMLDocument = struct { } pub fn get_readyState(self: *parser.DocumentHTML, page: *Page) ![]const u8 { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); return @tagName(state.ready_state); } @@ -264,7 +263,7 @@ pub const HTMLDocument = struct { } pub fn documentIsLoaded(self: *parser.DocumentHTML, page: *Page) !void { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); state.ready_state = .interactive; const evt = try parser.eventCreate(); @@ -279,7 +278,7 @@ pub const HTMLDocument = struct { } pub fn documentIsComplete(self: *parser.DocumentHTML, page: *Page) !void { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); state.ready_state = .complete; } }; diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index 5dae5c2a..15006bc5 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -25,7 +25,6 @@ const Page = @import("../page.zig").Page; const urlStitch = @import("../../url.zig").URL.stitch; const URL = @import("../url/url.zig").URL; const Node = @import("../dom/node.zig").Node; -const State = @import("../state/HTMLElement.zig"); const Element = @import("../dom/element.zig").Element; const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; @@ -114,7 +113,7 @@ pub const HTMLElement = struct { pub const subtype = .node; pub fn get_style(e: *parser.ElementHTML, page: *Page) !*CSSStyleDeclaration { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(e)); + const state = try page.getOrCreateNodeState(@ptrCast(e)); return &state.style; } @@ -954,22 +953,22 @@ pub const HTMLScriptElement = struct { } pub fn get_onload(self: *parser.Script, page: *Page) !?Env.Function { - const state = page.getNodeWrapper(State, @ptrCast(self)) orelse return null; + const state = page.getNodeState(@ptrCast(self)) orelse return null; return state.onload; } pub fn set_onload(self: *parser.Script, function: ?Env.Function, page: *Page) !void { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); state.onload = function; } pub fn get_onerror(self: *parser.Script, page: *Page) !?Env.Function { - const state = page.getNodeWrapper(State, @ptrCast(self)) orelse return null; + const state = page.getNodeState(@ptrCast(self)) orelse return null; return state.onerror; } pub fn set_onerror(self: *parser.Script, function: ?Env.Function, page: *Page) !void { - const state = try page.getOrCreateNodeWrapper(State, @ptrCast(self)); + const state = try page.getOrCreateNodeState(@ptrCast(self)); state.onerror = function; } }; diff --git a/src/browser/html/select.zig b/src/browser/html/select.zig index 5a73138f..ce078630 100644 --- a/src/browser/html/select.zig +++ b/src/browser/html/select.zig @@ -26,20 +26,6 @@ pub const HTMLSelectElement = struct { pub const prototype = *HTMLElement; pub const subtype = .node; - // By default, if no option is explicitly selected, the first option should - // be selected. However, libdom doesn't do this, and it sets the - // selectedIndex to -1, which is a valid value for "nothing selected". - // Therefore, when libdom says the selectedIndex == -1, we don't know if - // it means that nothing is selected, or if the first option is selected by - // default. - // There are cases where this won't work, but when selectedIndex is - // explicitly set, we set this boolean flag. Then, when we're getting then - // selectedIndex, if this flag is == false, which is to say that if - // selectedIndex hasn't been explicitly set AND if we have at least 1 option - // AND if it isn't a multi select, we can make the 1st item selected by - // default (by returning selectedIndex == 0). - explicit_index_set: bool = false, - pub fn get_length(select: *parser.Select) !u32 { return parser.selectGetLength(select); } @@ -70,11 +56,11 @@ pub const HTMLSelectElement = struct { } pub fn get_selectedIndex(select: *parser.Select, page: *Page) !i32 { - const self = try page.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select)); + const state = try page.getOrCreateNodeState(@ptrCast(select)); const selected_index = try parser.selectGetSelectedIndex(select); // See the explicit_index_set field documentation - if (!self.explicit_index_set) { + if (!state.explicit_index_set) { if (selected_index == -1) { if (try parser.selectGetMultiple(select) == false) { if (try get_length(select) > 0) { @@ -89,8 +75,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, page: *Page) !void { - var self = try page.getOrCreateNodeWrapper(HTMLSelectElement, @ptrCast(select)); - self.explicit_index_set = true; + var state = try page.getOrCreateNodeState(@ptrCast(select)); + state.explicit_index_set = true; const options = try parser.selectGetOptions(select); const len = try parser.optionCollectionGetLength(options); diff --git a/src/browser/page.zig b/src/browser/page.zig index fa8088a9..83f1136b 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -22,6 +22,7 @@ const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const Dump = @import("dump.zig"); +const State = @import("State.zig"); const Env = @import("env.zig").Env; const Mime = @import("mime.zig").Mime; const DataURI = @import("datauri.zig").DataURI; @@ -95,6 +96,8 @@ pub const Page = struct { // indicates intention to navigate to another page on the next loop execution. delayed_navigation: bool = false, + state_pool: *std.heap.MemoryPool(State), + pub fn init(self: *Page, arena: Allocator, session: *Session) !void { const browser = session.browser; self.* = .{ @@ -106,6 +109,7 @@ pub const Page = struct { .call_arena = undefined, .loop = browser.app.loop, .renderer = Renderer.init(arena), + .state_pool = &browser.state_pool, .cookie_jar = &session.cookie_jar, .microtask_node = .{ .func = microtaskCallback }, .window_clicked_event_node = .{ .func = windowClicked }, @@ -597,21 +601,21 @@ pub const Page = struct { _ = try self.loop.timeout(0, &navi.navigate_node); } - pub fn getOrCreateNodeWrapper(self: *Page, comptime T: type, node: *parser.Node) !*T { - if (self.getNodeWrapper(T, node)) |wrap| { + pub fn getOrCreateNodeState(self: *Page, node: *parser.Node) !*State { + if (self.getNodeState(node)) |wrap| { return wrap; } - const wrap = try self.arena.create(T); - wrap.* = T{}; + const state = try self.state_pool.create(); + state.* = .{}; - parser.nodeSetEmbedderData(node, wrap); - return wrap; + parser.nodeSetEmbedderData(node, state); + return state; } - pub fn getNodeWrapper(_: *const Page, comptime T: type, node: *parser.Node) ?*T { - if (parser.nodeGetEmbedderData(node)) |wrap| { - return @alignCast(@ptrCast(wrap)); + pub fn getNodeState(_: *const Page, node: *parser.Node) ?*State { + if (parser.nodeGetEmbedderData(node)) |state| { + return @alignCast(@ptrCast(state)); } return null; } @@ -743,8 +747,7 @@ const Script = struct { // attached to it. But this seems quite unlikely and it does help // optimize loading scripts, of which there can be hundreds for a // page. - const State = @import("state/HTMLElement.zig"); - if (page.getNodeWrapper(State, @ptrCast(e))) |se| { + if (page.getNodeState(@ptrCast(e))) |se| { if (se.onload) |function| { onload = .{ .function = function }; } diff --git a/src/browser/session.zig b/src/browser/session.zig index b128d14b..44fce9d3 100644 --- a/src/browser/session.zig +++ b/src/browser/session.zig @@ -90,6 +90,7 @@ pub const Session = struct { const page_arena = &self.browser.page_arena; _ = page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 }); + _ = self.browser.state_pool.reset(.{ .retain_with_limit = 4 * 1024 }); self.page = @as(Page, undefined); const page = &self.page.?; diff --git a/src/browser/state/Document.zig b/src/browser/state/Document.zig deleted file mode 100644 index dc2b6050..00000000 --- a/src/browser/state/Document.zig +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) -// -// Francis Bouvier -// Pierre Tachoire -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -const parser = @import("../netsurf.zig"); - -// proxy-owner for html/document -ready_state: ReadyState = .loading, - -// proxy-owner for dom/document -active_element: ?*parser.Element = null, - -const ReadyState = enum { - loading, - interactive, - complete, -}; diff --git a/src/browser/state/HTMLElement.zig b/src/browser/state/HTMLElement.zig deleted file mode 100644 index 0a41b207..00000000 --- a/src/browser/state/HTMLElement.zig +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) -// -// Francis Bouvier -// Pierre Tachoire -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -const Env = @import("../env.zig").Env; -const CSSStyleDeclaration = @import("../cssom/css_style_declaration.zig").CSSStyleDeclaration; - -onload: ?Env.Function = null, -onerror: ?Env.Function = null, -style: CSSStyleDeclaration = .empty,