mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
merge all states
This commit is contained in:
65
src/browser/State.zig
Normal file
65
src/browser/State.zig
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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,
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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.?;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
Reference in New Issue
Block a user