mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1522 from lightpanda-io/remove_page_reset
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Remove Page.reset
This commit is contained in:
@@ -66,13 +66,13 @@ lookup: std.HashMapUnmanaged(
|
||||
dispatch_depth: usize,
|
||||
deferred_removals: std.ArrayList(struct { list: *std.DoublyLinkedList, listener: *Listener }),
|
||||
|
||||
pub fn init(page: *Page) EventManager {
|
||||
pub fn init(arena: Allocator, page: *Page) EventManager {
|
||||
return .{
|
||||
.page = page,
|
||||
.lookup = .{},
|
||||
.arena = page.arena,
|
||||
.list_pool = std.heap.MemoryPool(std.DoublyLinkedList).init(page.arena),
|
||||
.listener_pool = std.heap.MemoryPool(Listener).init(page.arena),
|
||||
.arena = arena,
|
||||
.list_pool = .init(arena),
|
||||
.listener_pool = .init(arena),
|
||||
.dispatch_depth = 0,
|
||||
.deferred_removals = .{},
|
||||
};
|
||||
|
||||
@@ -43,7 +43,9 @@ const IS_DEBUG = builtin.mode == .Debug;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Factory = @This();
|
||||
|
||||
_page: *Page,
|
||||
_arena: Allocator,
|
||||
_slab: SlabAllocator,
|
||||
|
||||
fn PrototypeChain(comptime types: []const type) type {
|
||||
@@ -149,10 +151,11 @@ fn AutoPrototypeChain(comptime types: []const type) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(page: *Page) Factory {
|
||||
pub fn init(arena: Allocator, page: *Page) Factory {
|
||||
return .{
|
||||
._page = page,
|
||||
._slab = SlabAllocator.init(page.arena, 128),
|
||||
._arena = arena,
|
||||
._slab = SlabAllocator.init(arena, 128),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -329,7 +332,7 @@ pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeO
|
||||
chain.setMiddle(2, Element.Type);
|
||||
|
||||
// will never allocate, can't fail
|
||||
const tag_name_str = String.init(self._page.arena, tag_name, .{}) catch unreachable;
|
||||
const tag_name_str = String.init(self._arena, tag_name, .{}) catch unreachable;
|
||||
|
||||
// Manually set Element.Svg with the tag_name
|
||||
chain.set(3, .{
|
||||
|
||||
@@ -83,7 +83,7 @@ _session: *Session,
|
||||
|
||||
_event_manager: EventManager,
|
||||
|
||||
_parse_mode: enum { document, fragment, document_write },
|
||||
_parse_mode: enum { document, fragment, document_write } = .document,
|
||||
|
||||
// See Attribute.List for what this is. TL;DR: proper DOM Attribute Nodes are
|
||||
// fat yet rarely needed. We only create them on-demand, but still need proper
|
||||
@@ -91,21 +91,21 @@ _parse_mode: enum { document, fragment, document_write },
|
||||
// a look here. We don't store this in the Element or Attribute.List.Entry
|
||||
// because that would require additional space per element / Attribute.List.Entry
|
||||
// even thoug we'll create very few (if any) actual *Attributes.
|
||||
_attribute_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute),
|
||||
_attribute_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute) = .empty,
|
||||
|
||||
// Same as _atlribute_lookup, but instead of individual attributes, this is for
|
||||
// the return of elements.attributes.
|
||||
_attribute_named_node_map_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute.NamedNodeMap),
|
||||
_attribute_named_node_map_lookup: std.AutoHashMapUnmanaged(usize, *Element.Attribute.NamedNodeMap) = .empty,
|
||||
|
||||
// Lazily-created style, classList, and dataset objects. Only stored for elements
|
||||
// that actually access these features via JavaScript, saving 24 bytes per element.
|
||||
_element_styles: Element.StyleLookup = .{},
|
||||
_element_datasets: Element.DatasetLookup = .{},
|
||||
_element_class_lists: Element.ClassListLookup = .{},
|
||||
_element_rel_lists: Element.RelListLookup = .{},
|
||||
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
||||
_node_owner_documents: Node.OwnerDocumentLookup = .{},
|
||||
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||
_element_styles: Element.StyleLookup = .empty,
|
||||
_element_datasets: Element.DatasetLookup = .empty,
|
||||
_element_class_lists: Element.ClassListLookup = .empty,
|
||||
_element_rel_lists: Element.RelListLookup = .empty,
|
||||
_element_shadow_roots: Element.ShadowRootLookup = .empty,
|
||||
_node_owner_documents: Node.OwnerDocumentLookup = .empty,
|
||||
_element_assigned_slots: Element.AssignedSlotLookup = .empty,
|
||||
|
||||
/// Lazily-created inline event listeners (or listeners provided as attributes).
|
||||
/// Avoids bloating all elements with extra function fields for rare usage.
|
||||
@@ -125,7 +125,7 @@ _element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||
/// ```js
|
||||
/// img.setAttribute("onload", "(() => { ... })()");
|
||||
/// ```
|
||||
_element_attr_listeners: GlobalEventHandlersLookup = .{},
|
||||
_element_attr_listeners: GlobalEventHandlersLookup = .empty,
|
||||
|
||||
/// `load` events that'll be fired before window's `load` event.
|
||||
/// A call to `documentIsComplete` (which calls `_documentIsComplete`) resets it.
|
||||
@@ -167,9 +167,9 @@ _undefined_custom_elements: std.ArrayList(*Element.Html.Custom) = .{},
|
||||
// for heap allocations and managing WebAPI objects
|
||||
_factory: Factory,
|
||||
|
||||
_load_state: LoadState,
|
||||
_load_state: LoadState = .waiting,
|
||||
|
||||
_parse_state: ParseState,
|
||||
_parse_state: ParseState = .pre,
|
||||
|
||||
_notified_network_idle: IdleNotification = .init,
|
||||
_notified_network_almost_idle: IdleNotification = .init,
|
||||
@@ -179,19 +179,19 @@ _notified_network_almost_idle: IdleNotification = .init,
|
||||
_queued_navigation: ?QueuedNavigation = null,
|
||||
|
||||
// The URL of the current page
|
||||
url: [:0]const u8,
|
||||
url: [:0]const u8 = "about:blank",
|
||||
|
||||
// The base url specifies the base URL used to resolve the relative urls.
|
||||
// It is set by a <base> tag.
|
||||
// If null the url must be used.
|
||||
base_url: ?[:0]const u8,
|
||||
base_url: ?[:0]const u8 = null,
|
||||
|
||||
// referer header cache.
|
||||
referer_header: ?[:0]const u8,
|
||||
referer_header: ?[:0]const u8 = null,
|
||||
|
||||
// Arbitrary buffer. Need to temporarily lowercase a value? Use this. No lifetime
|
||||
// guarantee - it's valid until someone else uses it.
|
||||
buf: [BUF_SIZE]u8,
|
||||
buf: [BUF_SIZE]u8 = undefined,
|
||||
|
||||
// access to the JavaScript engine
|
||||
js: *JS.Context,
|
||||
@@ -209,13 +209,13 @@ arena_pool: *ArenaPool,
|
||||
_arena_pool_leak_track: (if (IS_DEBUG) std.AutoHashMapUnmanaged(usize, struct {
|
||||
owner: []const u8,
|
||||
count: usize,
|
||||
}) else void),
|
||||
}) else void) = if (IS_DEBUG) .empty else {},
|
||||
|
||||
window: *Window,
|
||||
document: *Document,
|
||||
|
||||
// DOM version used to invalidate cached state of "live" collections
|
||||
version: usize,
|
||||
version: usize = 0,
|
||||
|
||||
_req_id: ?usize = null,
|
||||
_navigated_options: ?NavigatedOpts = null,
|
||||
@@ -224,19 +224,59 @@ pub fn init(self: *Page, session: *Session) !void {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.page, "page.init", .{});
|
||||
}
|
||||
|
||||
const browser = session.browser;
|
||||
self._session = session;
|
||||
const page_arena = browser.page_arena.allocator();
|
||||
errdefer _ = browser.page_arena.reset(.free_all);
|
||||
|
||||
self.arena_pool = browser.arena_pool;
|
||||
self.arena = browser.page_arena.allocator();
|
||||
self.call_arena = browser.call_arena.allocator();
|
||||
var factory = Factory.init(page_arena, self);
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
self._arena_pool_leak_track = .empty;
|
||||
const document = (try factory.document(Node.Document.HTMLDocument{
|
||||
._proto = undefined,
|
||||
})).asDocument();
|
||||
|
||||
self.* = .{
|
||||
.js = undefined,
|
||||
.arena = page_arena,
|
||||
.document = document,
|
||||
.window = undefined,
|
||||
.arena_pool = browser.arena_pool,
|
||||
.call_arena = browser.call_arena.allocator(),
|
||||
._session = session,
|
||||
._factory = factory,
|
||||
._script_manager = undefined,
|
||||
._event_manager = EventManager.init(page_arena, self),
|
||||
};
|
||||
|
||||
self.window = try factory.eventTarget(Window{
|
||||
._proto = undefined,
|
||||
._document = self.document,
|
||||
._location = &default_location,
|
||||
._performance = Performance.init(),
|
||||
._screen = try factory.eventTarget(Screen{
|
||||
._proto = undefined,
|
||||
._orientation = null,
|
||||
}),
|
||||
._visual_viewport = try factory.eventTarget(VisualViewport{
|
||||
._proto = undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
self._script_manager = ScriptManager.init(browser.allocator, browser.http_client, self);
|
||||
errdefer self._script_manager.deinit();
|
||||
|
||||
self.js = try browser.env.createContext(self, true);
|
||||
errdefer self.js.deinit();
|
||||
|
||||
if (comptime builtin.is_test == false) {
|
||||
// HTML test runner manually calls these as necessary
|
||||
try self.js.scheduler.add(session.browser, struct {
|
||||
fn runMessageLoop(ctx: *anyopaque) !?u32 {
|
||||
const b: *@import("Browser.zig") = @ptrCast(@alignCast(ctx));
|
||||
b.runMessageLoop();
|
||||
return 250;
|
||||
}
|
||||
}.runMessageLoop, 250, .{ .name = "page.messageLoop" });
|
||||
}
|
||||
|
||||
try self.reset(true);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Page) void {
|
||||
@@ -267,130 +307,10 @@ pub fn deinit(self: *Page) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(self: *Page, comptime initializing: bool) !void {
|
||||
const browser = self._session.browser;
|
||||
|
||||
if (comptime initializing == false) {
|
||||
browser.env.destroyContext(self.js);
|
||||
|
||||
// We force a garbage collection between page navigations to keep v8
|
||||
// memory usage as low as possible.
|
||||
browser.env.memoryPressureNotification(.moderate);
|
||||
self._script_manager.shutdown = true;
|
||||
browser.http_client.abort();
|
||||
self._script_manager.deinit();
|
||||
|
||||
// destroying the context, and aborting the http_client can both cause
|
||||
// resources to be freed. We need to check for a leak after we've finished
|
||||
// all of our cleanup.
|
||||
if (comptime IS_DEBUG) {
|
||||
var it = self._arena_pool_leak_track.valueIterator();
|
||||
while (it.next()) |value_ptr| {
|
||||
if (value_ptr.count > 0) {
|
||||
log.err(.bug, "ArenaPool Leak", .{ .owner = value_ptr.owner });
|
||||
}
|
||||
}
|
||||
self._arena_pool_leak_track = .empty;
|
||||
}
|
||||
|
||||
_ = browser.page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
|
||||
}
|
||||
|
||||
self._factory = Factory.init(self);
|
||||
|
||||
self.version = 0;
|
||||
self.url = "about:blank";
|
||||
self.base_url = null;
|
||||
self.referer_header = null;
|
||||
|
||||
self.document = (try self._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument();
|
||||
|
||||
const storage_bucket = try self._factory.create(storage.Bucket{});
|
||||
const screen = try Screen.init(self);
|
||||
const visual_viewport = try VisualViewport.init(self);
|
||||
self.window = try self._factory.eventTarget(Window{
|
||||
._document = self.document,
|
||||
._storage_bucket = storage_bucket,
|
||||
._performance = Performance.init(),
|
||||
._proto = undefined,
|
||||
._location = &default_location,
|
||||
._screen = screen,
|
||||
._visual_viewport = visual_viewport,
|
||||
});
|
||||
self.window._document = self.document;
|
||||
self.window._location = &default_location;
|
||||
|
||||
self._parse_state = .pre;
|
||||
self._load_state = .waiting;
|
||||
self._queued_navigation = null;
|
||||
self._parse_mode = .document;
|
||||
self._attribute_lookup = .empty;
|
||||
self._attribute_named_node_map_lookup = .empty;
|
||||
self._event_manager = EventManager.init(self);
|
||||
|
||||
self._script_manager = ScriptManager.init(self);
|
||||
errdefer self._script_manager.deinit();
|
||||
|
||||
self.js = try browser.env.createContext(self, true);
|
||||
errdefer self.js.deinit();
|
||||
|
||||
self._element_styles = .{};
|
||||
self._element_datasets = .{};
|
||||
self._element_class_lists = .{};
|
||||
self._element_rel_lists = .{};
|
||||
self._element_shadow_roots = .{};
|
||||
self._node_owner_documents = .{};
|
||||
self._element_assigned_slots = .{};
|
||||
|
||||
self._element_attr_listeners = .{};
|
||||
|
||||
self._to_load = .{};
|
||||
|
||||
self._notified_network_idle = .init;
|
||||
self._notified_network_almost_idle = .init;
|
||||
|
||||
self._performance_observers = .{};
|
||||
self._mutation_observers = .{};
|
||||
self._mutation_delivery_scheduled = false;
|
||||
self._mutation_delivery_depth = 0;
|
||||
self._intersection_observers = .{};
|
||||
self._intersection_check_scheduled = false;
|
||||
self._intersection_delivery_scheduled = false;
|
||||
self._slots_pending_slotchange = .{};
|
||||
self._slotchange_delivery_scheduled = false;
|
||||
self._customized_builtin_definitions = .{};
|
||||
self._customized_builtin_connected_callback_invoked = .{};
|
||||
self._customized_builtin_disconnected_callback_invoked = .{};
|
||||
self._undefined_custom_elements = .{};
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
self._arena_pool_leak_track = .{};
|
||||
}
|
||||
|
||||
try self.registerBackgroundTasks();
|
||||
}
|
||||
|
||||
pub fn base(self: *const Page) [:0]const u8 {
|
||||
return self.base_url orelse self.url;
|
||||
}
|
||||
|
||||
fn registerBackgroundTasks(self: *Page) !void {
|
||||
if (comptime builtin.is_test) {
|
||||
// HTML test runner manually calls these as necessary
|
||||
return;
|
||||
}
|
||||
|
||||
const Browser = @import("Browser.zig");
|
||||
|
||||
try self.js.scheduler.add(self._session.browser, struct {
|
||||
fn runMessageLoop(ctx: *anyopaque) !?u32 {
|
||||
const b: *Browser = @ptrCast(@alignCast(ctx));
|
||||
b.runMessageLoop();
|
||||
return 250;
|
||||
}
|
||||
}.runMessageLoop, 250, .{ .name = "page.messageLoop" });
|
||||
}
|
||||
|
||||
pub fn getTitle(self: *Page) !?[]const u8 {
|
||||
if (self.window._document.is(Document.HTMLDocument)) |html_doc| {
|
||||
return try html_doc.getTitle(self);
|
||||
@@ -461,12 +381,8 @@ pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
|
||||
}
|
||||
|
||||
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
||||
lp.assert(self._load_state == .waiting, "page.renavigate", .{});
|
||||
const session = self._session;
|
||||
if (self._parse_state != .pre or self._load_state != .waiting) {
|
||||
// it's possible for navigate to be called multiple times on the
|
||||
// same page (via CDP). We want to reset the page between each call.
|
||||
try self.reset(false);
|
||||
}
|
||||
self._load_state = .parsing;
|
||||
|
||||
const req_id = self._session.browser.http_client.nextReqId();
|
||||
|
||||
@@ -83,10 +83,7 @@ imported_modules: std.StringHashMapUnmanaged(ImportedModule),
|
||||
// importmap contains resolved urls.
|
||||
importmap: std.StringHashMapUnmanaged([:0]const u8),
|
||||
|
||||
pub fn init(page: *Page) ScriptManager {
|
||||
// page isn't fully initialized, we can setup our reference, but that's it.
|
||||
const browser = page._session.browser;
|
||||
const allocator = browser.allocator;
|
||||
pub fn init(allocator: Allocator, http_client: *Http.Client, page: *Page) ScriptManager {
|
||||
return .{
|
||||
.page = page,
|
||||
.async_scripts = .{},
|
||||
@@ -96,7 +93,7 @@ pub fn init(page: *Page) ScriptManager {
|
||||
.is_evaluating = false,
|
||||
.allocator = allocator,
|
||||
.imported_modules = .empty,
|
||||
.client = browser.http_client,
|
||||
.client = http_client,
|
||||
.static_scripts_done = false,
|
||||
.buffer_pool = BufferPool.init(allocator, 5),
|
||||
.script_pool = std.heap.MemoryPool(Script).init(allocator),
|
||||
|
||||
@@ -127,6 +127,23 @@ pub fn removePage(self: *Session) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replacePage(self: *Session) !*Page {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "replace page", .{});
|
||||
}
|
||||
|
||||
lp.assert(self.page != null, "Session.replacePage null page", .{});
|
||||
self.page.?.deinit();
|
||||
|
||||
_ = self.browser.page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
|
||||
self.browser.env.memoryPressureNotification(.moderate);
|
||||
|
||||
self.page = @as(Page, undefined);
|
||||
const page = &self.page.?;
|
||||
try Page.init(page, self);
|
||||
return page;
|
||||
}
|
||||
|
||||
pub fn currentPage(self: *Session) ?*Page {
|
||||
return &(self.page orelse return null);
|
||||
}
|
||||
|
||||
@@ -32,13 +32,6 @@ const Screen = @This();
|
||||
_proto: *EventTarget,
|
||||
_orientation: ?*Orientation = null,
|
||||
|
||||
pub fn init(page: *Page) !*Screen {
|
||||
return page._factory.eventTarget(Screen{
|
||||
._proto = undefined,
|
||||
._orientation = null,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn asEventTarget(self: *Screen) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,6 @@ const VisualViewport = @This();
|
||||
|
||||
_proto: *EventTarget,
|
||||
|
||||
pub fn init(page: *Page) !*VisualViewport {
|
||||
return page._factory.eventTarget(VisualViewport{
|
||||
._proto = undefined,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn asEventTarget(self: *VisualViewport) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ _navigator: Navigator = .init,
|
||||
_screen: *Screen,
|
||||
_visual_viewport: *VisualViewport,
|
||||
_performance: Performance,
|
||||
_storage_bucket: *storage.Bucket,
|
||||
_storage_bucket: storage.Bucket = .{},
|
||||
_on_load: ?js.Function.Global = null,
|
||||
_on_pageshow: ?js.Function.Global = null,
|
||||
_on_popstate: ?js.Function.Global = null,
|
||||
@@ -128,11 +128,11 @@ pub fn getPerformance(self: *Window) *Performance {
|
||||
return &self._performance;
|
||||
}
|
||||
|
||||
pub fn getLocalStorage(self: *const Window) *storage.Lookup {
|
||||
pub fn getLocalStorage(self: *Window) *storage.Lookup {
|
||||
return &self._storage_bucket.local;
|
||||
}
|
||||
|
||||
pub fn getSessionStorage(self: *const Window) *storage.Lookup {
|
||||
pub fn getSessionStorage(self: *Window) *storage.Lookup {
|
||||
return &self._storage_bucket.session;
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +213,12 @@ fn navigate(cmd: anytype) !void {
|
||||
return error.SessionIdNotLoaded;
|
||||
}
|
||||
|
||||
var page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||
const session = bc.session;
|
||||
var page = session.currentPage() orelse return error.PageNotLoaded;
|
||||
|
||||
if (page._load_state != .waiting) {
|
||||
page = try session.replacePage();
|
||||
}
|
||||
|
||||
try page.navigate(params.url, .{
|
||||
.reason = .address_bar,
|
||||
|
||||
Reference in New Issue
Block a user