mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Introduce Page (container)
Follow up to https://github.com/lightpanda-io/browser/pull/2200 This change is actually pretty mundane, but a bunch of files that used to take a *Session (e.g. every WebAPI releaseRef and deinit) now take a *Page. This aims to separate the 2 lifetimes currently managed by Session by moving the "Page" lifetime to a dedicated container: Page. Ultimately, the goal is to remove the 1-page-per-session limit of the current design. Not to explicitly support multiple pages per session (though, that's more possible now), but in order to better emulate Chrome where, during a navigation event, the old and new page both exist.
This commit is contained in:
@@ -726,7 +726,7 @@ test "SemanticTree backendDOMNodeId" {
|
||||
|
||||
var frame = try testing.pageTest("cdp/registry1.html", .{});
|
||||
defer testing.reset();
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
|
||||
const st: Self = .{
|
||||
.dom_node = frame.window._document.asNode(),
|
||||
@@ -750,7 +750,7 @@ test "SemanticTree max_depth" {
|
||||
|
||||
var frame = try testing.pageTest("cdp/registry1.html", .{});
|
||||
defer testing.reset();
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
|
||||
const st: Self = .{
|
||||
.dom_node = frame.window._document.asNode(),
|
||||
|
||||
@@ -104,7 +104,7 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) Dispat
|
||||
|
||||
pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, comptime opts: DispatchOpts) DispatchError!void {
|
||||
event.acquireRef();
|
||||
defer _ = event.releaseRef(self.frame._session);
|
||||
defer _ = event.releaseRef(self.frame._page);
|
||||
|
||||
// Increment event count for Event Timing API
|
||||
self.frame.window._performance._event_counts.increment(event._type_string.str());
|
||||
@@ -138,7 +138,7 @@ pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event,
|
||||
window._current_event = event;
|
||||
defer window._current_event = prev_event;
|
||||
|
||||
try self.base.dispatchDirect(frame.call_arena, frame.js, target, event, handler, frame._session, opts);
|
||||
try self.base.dispatchDirect(frame.call_arena, frame.js, target, event, handler, frame._page, opts);
|
||||
}
|
||||
|
||||
/// Check if there are any listeners for a direct dispatch (non-DOM target).
|
||||
@@ -617,7 +617,7 @@ const ActivationState = struct {
|
||||
const event = try Event.initTrusted(comptime .wrap(typ), .{
|
||||
.bubbles = true,
|
||||
.cancelable = false,
|
||||
}, frame._session);
|
||||
}, frame._page);
|
||||
|
||||
const target = input.asElement().asEventTarget();
|
||||
try frame._event_manager.dispatch(target, event);
|
||||
|
||||
@@ -21,7 +21,7 @@ const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
const Session = @import("Session.zig");
|
||||
const Page = @import("Page.zig");
|
||||
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const EventTarget = @import("webapi/EventTarget.zig");
|
||||
@@ -216,7 +216,7 @@ pub fn dispatchDirect(
|
||||
target: *EventTarget,
|
||||
event: *Event,
|
||||
handler: anytype,
|
||||
session: *Session,
|
||||
page: *Page,
|
||||
comptime opts: DispatchDirectOptions,
|
||||
) DispatchError!void {
|
||||
if (comptime IS_DEBUG) {
|
||||
@@ -224,7 +224,7 @@ pub fn dispatchDirect(
|
||||
}
|
||||
|
||||
event.acquireRef();
|
||||
defer _ = event.releaseRef(session);
|
||||
defer _ = event.releaseRef(page);
|
||||
|
||||
if (comptime opts.inject_target) {
|
||||
event._target = target;
|
||||
|
||||
@@ -22,6 +22,7 @@ const builtin = @import("builtin");
|
||||
|
||||
const JS = @import("js/js.zig");
|
||||
const Mime = @import("Mime.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Factory = @import("Factory.zig");
|
||||
const Session = @import("Session.zig");
|
||||
const EventManager = @import("EventManager.zig");
|
||||
@@ -87,6 +88,8 @@ _frame_id: u32,
|
||||
// navigate.
|
||||
_loader_id: u32,
|
||||
|
||||
_page: *Page,
|
||||
|
||||
_session: *Session,
|
||||
|
||||
_event_manager: EventManager,
|
||||
@@ -255,35 +258,39 @@ _type: enum { root, frame }, // only used for logs right now
|
||||
_req_id: u32 = 0,
|
||||
_navigated_options: ?NavigatedOpts = null,
|
||||
|
||||
pub fn init(self: *Frame, frame_id: u32, session: *Session, parent: ?*Frame) !void {
|
||||
pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.frame, "frame.init", .{});
|
||||
}
|
||||
|
||||
const session = page.session;
|
||||
const call_arena = try session.getArena(.medium, "call_arena");
|
||||
errdefer session.releaseArena(call_arena);
|
||||
|
||||
const factory = &session.factory;
|
||||
const factory = &page.factory;
|
||||
const document = (try factory.document(Node.Document.HTMLDocument{
|
||||
._proto = undefined,
|
||||
})).asDocument();
|
||||
|
||||
const arena = page.frame_arena;
|
||||
|
||||
self.* = .{
|
||||
.js = undefined,
|
||||
.arena = arena,
|
||||
.parent = parent,
|
||||
.arena = session.frame_arena,
|
||||
.document = document,
|
||||
.window = undefined,
|
||||
.call_arena = call_arena,
|
||||
._frame_id = frame_id,
|
||||
._loader_id = session.nextLoaderId(),
|
||||
._page = page,
|
||||
._session = session,
|
||||
._loader_id = session.nextLoaderId(),
|
||||
._factory = factory,
|
||||
._pending_loads = 1, // always 1 for the ScriptManager
|
||||
._type = if (parent == null) .root else .frame,
|
||||
._style_manager = undefined,
|
||||
._script_manager = undefined,
|
||||
._event_manager = EventManager.init(session.frame_arena, self),
|
||||
._event_manager = EventManager.init(arena, self),
|
||||
};
|
||||
self._to_load = &self._to_load_1;
|
||||
|
||||
@@ -322,8 +329,8 @@ pub fn init(self: *Frame, frame_id: u32, session: *Session, parent: ?*Frame) !vo
|
||||
errdefer self._script_manager.deinit();
|
||||
|
||||
self.js = try browser.env.createContext(self, .{
|
||||
.identity = &session.identity,
|
||||
.identity_arena = session.frame_arena,
|
||||
.identity = &page.identity,
|
||||
.identity_arena = arena,
|
||||
.call_arena = self.call_arena,
|
||||
});
|
||||
errdefer browser.env.destroyContext(self.js);
|
||||
@@ -363,10 +370,10 @@ pub fn deinit(self: *Frame, abort_http: bool) void {
|
||||
// stats.print(&stream) catch unreachable;
|
||||
}
|
||||
|
||||
const session = self._session;
|
||||
const page = self._page;
|
||||
|
||||
if (self._queued_navigation) |qn| {
|
||||
session.releaseArena(qn.arena);
|
||||
page.releaseArena(qn.arena);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -374,7 +381,7 @@ pub fn deinit(self: *Frame, abort_http: bool) void {
|
||||
{
|
||||
var it = self._blob_urls.valueIterator();
|
||||
while (it.next()) |blob| {
|
||||
blob.*.releaseRef(session);
|
||||
blob.*.releaseRef(page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,39 +390,40 @@ pub fn deinit(self: *Frame, abort_http: bool) void {
|
||||
while (node) |n| {
|
||||
node = n.next; // capture before we potentially delete observer
|
||||
const observer: *MutationObserver = @fieldParentPtr("node", n);
|
||||
observer.releaseRef(session);
|
||||
observer.releaseRef(page);
|
||||
}
|
||||
}
|
||||
|
||||
for (self._intersection_observers.items) |observer| {
|
||||
observer.releaseRef(session);
|
||||
observer.releaseRef(page);
|
||||
}
|
||||
|
||||
var document = self.window._document;
|
||||
document._selection.releaseRef(session);
|
||||
document._selection.releaseRef(page);
|
||||
|
||||
if (document._fonts) |f| {
|
||||
f.releaseRef(session);
|
||||
f.releaseRef(page);
|
||||
}
|
||||
}
|
||||
|
||||
session.browser.env.destroyContext(self.js);
|
||||
const browser = page.session.browser;
|
||||
browser.env.destroyContext(self.js);
|
||||
|
||||
self._script_manager.shutdown = true;
|
||||
|
||||
if (self.parent == null) {
|
||||
session.browser.http_client.abort();
|
||||
browser.http_client.abort();
|
||||
} else if (abort_http) {
|
||||
// a small optimization, it's faster to abort _everything_ on the root
|
||||
// frame, so we prefer that. But if it's just the frame that's going
|
||||
// away (a frame navigation) then we'll abort the frame-related requests
|
||||
session.browser.http_client.abortFrame(self._frame_id);
|
||||
browser.http_client.abortFrame(self._frame_id);
|
||||
}
|
||||
|
||||
self._script_manager.deinit();
|
||||
self._style_manager.deinit();
|
||||
|
||||
session.releaseArena(self.call_arena);
|
||||
page.releaseArena(self.call_arena);
|
||||
}
|
||||
|
||||
pub fn trackWorker(self: *Frame, worker: *Worker) !void {
|
||||
@@ -807,7 +815,7 @@ pub fn documentIsLoaded(self: *Frame) void {
|
||||
}
|
||||
|
||||
pub fn _documentIsLoaded(self: *Frame) !void {
|
||||
const event = try Event.initTrusted(.wrap("DOMContentLoaded"), .{ .bubbles = true }, self._session);
|
||||
const event = try Event.initTrusted(.wrap("DOMContentLoaded"), .{ .bubbles = true }, self._page);
|
||||
try self._event_manager.dispatch(
|
||||
self.document.asEventTarget(),
|
||||
event,
|
||||
@@ -834,7 +842,7 @@ pub fn iframeCompletedLoading(self: *Frame, iframe: *IFrame) void {
|
||||
defer entered.exit();
|
||||
|
||||
blk: {
|
||||
const event = Event.initTrusted(comptime .wrap("load"), .{}, self._session) catch |err| {
|
||||
const event = Event.initTrusted(comptime .wrap("load"), .{}, self._page) catch |err| {
|
||||
log.err(.frame, "iframe event init", .{ .err = err, .url = iframe._src });
|
||||
break :blk;
|
||||
};
|
||||
@@ -888,7 +896,7 @@ fn _documentIsComplete(self: *Frame) !void {
|
||||
// Dispatch window.load event.
|
||||
const window_target = self.window.asEventTarget();
|
||||
if (self._event_manager.hasDirectListeners(window_target, "load", self.window._on_load)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self._page);
|
||||
// This event is weird, it's dispatched directly on the window, but
|
||||
// with the document as the target.
|
||||
event._target = self.document.asEventTarget();
|
||||
@@ -1213,7 +1221,7 @@ pub fn iframeAddedCallback(self: *Frame, iframe: *IFrame) !void {
|
||||
const new_frame = try self.arena.create(Frame);
|
||||
const frame_id = session.nextFrameId();
|
||||
|
||||
try Frame.init(new_frame, frame_id, session, self);
|
||||
try Frame.init(new_frame, frame_id, self._page, self);
|
||||
errdefer new_frame.deinit(true);
|
||||
|
||||
self._pending_loads += 1;
|
||||
@@ -1431,7 +1439,7 @@ pub fn registerMutationObserver(self: *Frame, observer: *MutationObserver) !void
|
||||
}
|
||||
|
||||
pub fn unregisterMutationObserver(self: *Frame, observer: *MutationObserver) void {
|
||||
observer.releaseRef(self._session);
|
||||
observer.releaseRef(self._page);
|
||||
self._mutation_observers.remove(&observer.node);
|
||||
}
|
||||
|
||||
@@ -1443,7 +1451,7 @@ pub fn registerIntersectionObserver(self: *Frame, observer: *IntersectionObserve
|
||||
pub fn unregisterIntersectionObserver(self: *Frame, observer: *IntersectionObserver) void {
|
||||
for (self._intersection_observers.items, 0..) |obs, i| {
|
||||
if (obs == observer) {
|
||||
observer.releaseRef(self._session);
|
||||
observer.releaseRef(self._page);
|
||||
_ = self._intersection_observers.swapRemove(i);
|
||||
return;
|
||||
}
|
||||
@@ -1468,7 +1476,7 @@ pub fn dispatchLoad(self: *Frame) !void {
|
||||
|
||||
for (to_process.items) |html_element| {
|
||||
if (has_dom_load_listener or html_element.hasAttributeFunction(.onload, self)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self._page);
|
||||
try self._event_manager.dispatch(html_element.asEventTarget(), event);
|
||||
}
|
||||
}
|
||||
@@ -1579,7 +1587,7 @@ pub fn deliverSlotchangeEvents(self: *Frame) void {
|
||||
self._slots_pending_slotchange.clearRetainingCapacity();
|
||||
|
||||
for (slots) |slot| {
|
||||
const event = Event.initTrusted(comptime .wrap("slotchange"), .{ .bubbles = true }, self._session) catch |err| {
|
||||
const event = Event.initTrusted(comptime .wrap("slotchange"), .{ .bubbles = true }, self._page) catch |err| {
|
||||
log.err(.frame, "deliverSlotchange.init", .{ .err = err, .type = self._type, .url = self.url });
|
||||
continue;
|
||||
};
|
||||
@@ -3520,7 +3528,7 @@ pub fn handleClick(self: *Frame, target: *Node) !void {
|
||||
pub fn triggerKeyboard(self: *Frame, keyboard_event: *KeyboardEvent) !void {
|
||||
const event = keyboard_event.asEvent();
|
||||
const element = self.window._document._active_element orelse {
|
||||
event.deinit(self._session);
|
||||
event.deinit(self._page);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -3616,7 +3624,7 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
|
||||
|
||||
// so submit_event is still valid when we check _prevent_default
|
||||
submit_event.acquireRef();
|
||||
defer _ = submit_event.releaseRef(self._session);
|
||||
defer _ = submit_event.releaseRef(self._page);
|
||||
|
||||
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
|
||||
// If the submit event was prevented, don't submit the form
|
||||
|
||||
232
src/browser/Page.zig
Normal file
232
src/browser/Page.zig
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright (C) 2023-2026 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 std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const App = @import("../App.zig");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
const v8 = js.v8;
|
||||
|
||||
const Frame = @import("Frame.zig");
|
||||
const Session = @import("Session.zig");
|
||||
const Factory = @import("Factory.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
// A Page is the container for a root Frame and all of its descendants
|
||||
// (nested iframes). It owns the resources that share the lifetime of the root
|
||||
// document: the DOM factory, the per-page arena, the JS identity map, shared
|
||||
// origins, v8 global handles, and queued navigation buffers.
|
||||
//
|
||||
// In the future, a Session may hold multiple Pages at once (e.g. during a
|
||||
// navigation, while the old Page is retiring and the new one is provisional).
|
||||
// For now, Session still holds a single Page.
|
||||
const Page = @This();
|
||||
|
||||
session: *Session,
|
||||
|
||||
// DOM object factory scoped to this Page's documents.
|
||||
factory: Factory,
|
||||
|
||||
// The arena for this Page's lifetime. Document / Frame / Factory / DOM
|
||||
// objects allocate out of this.
|
||||
frame_arena: Allocator,
|
||||
|
||||
// Origin map for same-origin context sharing. Entries live for the Page's
|
||||
// lifetime.
|
||||
origins: std.StringHashMapUnmanaged(*js.Origin) = .empty,
|
||||
|
||||
// Identity tracking for the main world. All main-world contexts in this Page
|
||||
// share this, ensuring object identity works across same-origin frames.
|
||||
identity: js.Identity = .{},
|
||||
|
||||
// Finalizer callbacks for Zig instances exposed to v8 in this Page. Keyed by
|
||||
// Zig instance ptr. The backing FinalizerCallback.Identity structs come from
|
||||
// Session.fc_identity_pool so they outlive the Page for v8 weak-callback
|
||||
// safety.
|
||||
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *Session.FinalizerCallback) = .empty,
|
||||
|
||||
// Tracked global v8 objects that need to be released when the Page tears down.
|
||||
globals: std.ArrayList(v8.Global) = .empty,
|
||||
|
||||
// Temporary v8 globals that can be released early. Key is global.data_ptr.
|
||||
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
|
||||
|
||||
// Double buffered so that, as we process one list of queued navigations, new
|
||||
// entries are added to the separate buffer. Prevents endless navigation loops
|
||||
// and invalidation of the list during iteration.
|
||||
queued_navigation_1: std.ArrayList(*Frame) = .empty,
|
||||
queued_navigation_2: std.ArrayList(*Frame) = .empty,
|
||||
// pointer to either queued_navigation_1 or queued_navigation_2
|
||||
queued_navigation: *std.ArrayList(*Frame) = undefined,
|
||||
|
||||
// Temporary buffer for about:blank navigations during processing.
|
||||
// We process async navigations first (safe from re-entrance), then sync
|
||||
// about:blank navigations (which may add to queued_navigation).
|
||||
queued_queued_navigation: std.ArrayList(*Frame) = .empty,
|
||||
|
||||
// The root Frame of this Page. Non-optional — a Page always has a root frame.
|
||||
frame: Frame,
|
||||
|
||||
// Initialize a Page and its root Frame.
|
||||
pub fn init(self: *Page, session: *Session, frame_id: u32) !void {
|
||||
const frame_arena = try session.arena_pool.acquire(.large, "Page.frame_arena");
|
||||
errdefer session.arena_pool.release(frame_arena);
|
||||
|
||||
self.* = .{
|
||||
.session = session,
|
||||
.frame = undefined,
|
||||
.frame_arena = frame_arena,
|
||||
.factory = Factory.init(frame_arena),
|
||||
};
|
||||
self.queued_navigation = &self.queued_navigation_1;
|
||||
|
||||
try Frame.init(&self.frame, frame_id, self, null);
|
||||
}
|
||||
|
||||
// Tear down the Page and its root Frame. Equivalent to the old
|
||||
// Session.removePage + Session.resetFrameResources.
|
||||
pub fn deinit(self: *Page, abort_http: bool) void {
|
||||
self.frame.deinit(abort_http);
|
||||
|
||||
const session = self.session;
|
||||
defer session.browser.env.memoryPressureNotification(.moderate);
|
||||
|
||||
self.identity.deinit();
|
||||
self.identity = .{};
|
||||
|
||||
// Force cleanup all remaining finalized objects.
|
||||
{
|
||||
var it = self.finalizer_callbacks.valueIterator();
|
||||
while (it.next()) |fc| {
|
||||
fc.*.deinit(self);
|
||||
}
|
||||
self.finalizer_callbacks = .empty;
|
||||
}
|
||||
|
||||
{
|
||||
for (self.globals.items) |*global| {
|
||||
v8.v8__Global__Reset(global);
|
||||
}
|
||||
self.globals = .empty;
|
||||
}
|
||||
|
||||
{
|
||||
var it = self.temps.valueIterator();
|
||||
while (it.next()) |global| {
|
||||
v8.v8__Global__Reset(global);
|
||||
}
|
||||
self.temps = .empty;
|
||||
}
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.origins.count() == 0);
|
||||
}
|
||||
// Defensive cleanup in case origins leaked.
|
||||
{
|
||||
const app = session.browser.app;
|
||||
var it = self.origins.valueIterator();
|
||||
while (it.next()) |value| {
|
||||
value.*.deinit(app);
|
||||
}
|
||||
self.origins = .empty;
|
||||
}
|
||||
|
||||
session.arena_pool.release(self.frame_arena);
|
||||
}
|
||||
|
||||
pub fn getArena(self: *Page, size_or_bucket: anytype, debug: []const u8) !Allocator {
|
||||
return self.session.getArena(size_or_bucket, debug);
|
||||
}
|
||||
|
||||
pub fn releaseArena(self: *Page, allocator: Allocator) void {
|
||||
return self.session.releaseArena(allocator);
|
||||
}
|
||||
|
||||
pub fn getOrCreateOrigin(self: *Page, key_: ?[]const u8) !*js.Origin {
|
||||
const session = self.session;
|
||||
const key = key_ orelse {
|
||||
var opaque_origin: [36]u8 = undefined;
|
||||
@import("../id.zig").uuidv4(&opaque_origin);
|
||||
// Origin.init will dupe opaque_origin. It's fine that this doesn't
|
||||
// get added to self.origins. In fact, it further isolates it. When the
|
||||
// context is freed, it'll call Page.releaseOrigin which will free it.
|
||||
return js.Origin.init(session.browser.app, session.browser.env.isolate, &opaque_origin);
|
||||
};
|
||||
|
||||
const gop = try self.origins.getOrPut(session.arena, key);
|
||||
if (gop.found_existing) {
|
||||
const origin = gop.value_ptr.*;
|
||||
origin.rc += 1;
|
||||
return origin;
|
||||
}
|
||||
|
||||
errdefer _ = self.origins.remove(key);
|
||||
|
||||
const origin = try js.Origin.init(session.browser.app, session.browser.env.isolate, key);
|
||||
gop.key_ptr.* = origin.key;
|
||||
gop.value_ptr.* = origin;
|
||||
return origin;
|
||||
}
|
||||
|
||||
pub fn releaseOrigin(self: *Page, origin: *js.Origin) void {
|
||||
const rc = origin.rc;
|
||||
if (rc == 1) {
|
||||
_ = self.origins.remove(origin.key);
|
||||
origin.deinit(self.session.browser.app);
|
||||
} else {
|
||||
origin.rc = rc - 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scheduleNavigation(self: *Page, frame: *Frame) !void {
|
||||
const list = self.queued_navigation;
|
||||
|
||||
// Check if frame is already queued
|
||||
for (list.items) |existing| {
|
||||
if (existing == frame) {
|
||||
// Already queued
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return list.append(self.session.arena, frame);
|
||||
}
|
||||
|
||||
pub fn findFrameByFrameId(self: *Page, frame_id: u32) ?*Frame {
|
||||
return findFrameBy(&self.frame, "_frame_id", frame_id);
|
||||
}
|
||||
|
||||
pub fn findFrameByLoaderId(self: *Page, loader_id: u32) ?*Frame {
|
||||
return findFrameBy(&self.frame, "_loader_id", loader_id);
|
||||
}
|
||||
|
||||
fn findFrameBy(frame: *Frame, comptime field: []const u8, id: u32) ?*Frame {
|
||||
if (@field(frame, field) == id) return frame;
|
||||
for (frame.child_frames.items) |f| {
|
||||
if (findFrameBy(f, field, id)) |found| {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ http_client: *HttpClient,
|
||||
pub const Opts = struct {};
|
||||
|
||||
pub fn init(session: *Session, _: Opts) !Runner {
|
||||
const frame = &(session.frame orelse return error.NoPage);
|
||||
const frame = session.currentFrame() orelse return error.NoPage;
|
||||
|
||||
return .{
|
||||
.frame = frame,
|
||||
@@ -150,10 +150,12 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
},
|
||||
.html, .complete => {
|
||||
const session = self.session;
|
||||
if (session.queued_navigation.items.len != 0) {
|
||||
try session.processQueuedNavigation();
|
||||
self.frame = &session.frame.?; // might have changed
|
||||
return .{ .ok = 0 };
|
||||
if (session.currentPage()) |page| {
|
||||
if (page.queued_navigation.items.len != 0) {
|
||||
try session.processQueuedNavigation();
|
||||
self.frame = session.currentFrame().?; // might have changed
|
||||
return .{ .ok = 0 };
|
||||
}
|
||||
}
|
||||
const browser = session.browser;
|
||||
|
||||
@@ -323,7 +325,7 @@ test "Runner: no page" {
|
||||
|
||||
test "Runner: waitForSelector timeout" {
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
|
||||
var runner = try frame._session.runner(.{});
|
||||
try testing.expectError(error.Timeout, runner.waitForSelector("#nope", 10));
|
||||
@@ -332,7 +334,7 @@ test "Runner: waitForSelector timeout" {
|
||||
test "Runner: waitForSelector" {
|
||||
defer testing.reset();
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
|
||||
var runner = try frame._session.runner(.{});
|
||||
const el = try runner.waitForSelector("#sel1", 10);
|
||||
@@ -341,7 +343,7 @@ test "Runner: waitForSelector" {
|
||||
|
||||
test "Runner: waitForScript timeout" {
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
|
||||
var runner = try frame._session.runner(.{});
|
||||
try testing.expectError(error.Timeout, runner.waitForScript("document.querySelector('#nope')", 10));
|
||||
@@ -349,7 +351,7 @@ test "Runner: waitForScript timeout" {
|
||||
|
||||
test "Runner: waitForScript" {
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
|
||||
var runner = try frame._session.runner(.{});
|
||||
try runner.waitForScript("document.querySelector('#sel1')", 10);
|
||||
|
||||
@@ -949,7 +949,7 @@ pub const Script = struct {
|
||||
|
||||
fn executeCallback(self: *const Script, typ: String, frame: *Frame) void {
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const event = Event.initTrusted(typ, .{}, frame._session) catch |err| {
|
||||
const event = Event.initTrusted(typ, .{}, frame._page) catch |err| {
|
||||
log.warn(.js, "script internal callback", .{
|
||||
.url = self.url,
|
||||
.type = typ,
|
||||
|
||||
@@ -29,9 +29,9 @@ const Navigation = @import("webapi/navigation/Navigation.zig");
|
||||
const History = @import("webapi/History.zig");
|
||||
|
||||
const Frame = @import("Frame.zig");
|
||||
const Page = @import("Page.zig");
|
||||
pub const Runner = @import("Runner.zig");
|
||||
const Browser = @import("Browser.zig");
|
||||
const Factory = @import("Factory.zig");
|
||||
const Notification = @import("../Notification.zig");
|
||||
const QueuedNavigation = Frame.QueuedNavigation;
|
||||
|
||||
@@ -40,17 +40,16 @@ const ArenaPool = App.ArenaPool;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
// You can create successively multiple frames for a session, but you must
|
||||
// deinit a frame before running another one. It manages two distinct lifetimes.
|
||||
// A Session represents a browsing context group (cookie jar, session storage,
|
||||
// navigation history) within a Browser. It hosts one Page at a time — the
|
||||
// root Frame and all of its descendants — and is responsible for Page
|
||||
// lifecycle (create, remove, replace on root navigation).
|
||||
//
|
||||
// The first is the lifetime of the Session itself, where frames are created and
|
||||
// removed, but share the same cookie jar and navigation history (etc...)
|
||||
//
|
||||
// The second is as a container the data needed by the full frame hierarchy, i.e. \
|
||||
// the root frame and all of its frames (and all of their frames.)
|
||||
// Multiple concurrent Pages (e.g. an old Page retiring while a new provisional
|
||||
// Page is loading) are not yet supported; see Page.zig for the intended
|
||||
// direction.
|
||||
const Session = @This();
|
||||
|
||||
// These are the fields that remain intact for the duration of the Session
|
||||
browser: *Browser,
|
||||
arena: Allocator,
|
||||
history: History,
|
||||
@@ -59,54 +58,18 @@ storage_shed: storage.Shed,
|
||||
notification: *Notification,
|
||||
cookie_jar: storage.Cookie.Jar,
|
||||
|
||||
// These are the fields that get reset whenever the Session's frame (the root) is reset.
|
||||
factory: Factory,
|
||||
|
||||
frame_arena: Allocator,
|
||||
|
||||
// Origin map for same-origin context sharing. Scoped to the root frame lifetime.
|
||||
origins: std.StringHashMapUnmanaged(*js.Origin) = .empty,
|
||||
|
||||
// Identity tracking for the main world. All main world contexts share this,
|
||||
// ensuring object identity works across same-origin frames.
|
||||
identity: js.Identity = .{},
|
||||
|
||||
// Shared finalizer callbacks across all Identities. Keyed by Zig instance ptr.
|
||||
// This ensures objects are only freed when ALL v8 wrappers are gone.
|
||||
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
|
||||
|
||||
// Pool for FinalizerCallback.Identity structs. These must survive frame resets
|
||||
// so V8 weak callbacks can validate the FC before dereferencing it.
|
||||
fc_identity_pool: std.heap.MemoryPool(FinalizerCallback.Identity),
|
||||
|
||||
// Tracked global v8 objects that need to be released on cleanup.
|
||||
// Lives at Session level so objects can outlive individual Identities.
|
||||
globals: std.ArrayList(v8.Global) = .empty,
|
||||
|
||||
// Temporary v8 globals that can be released early. Key is global.data_ptr.
|
||||
// Lives at Session level so objects holding Temps can outlive individual Identities.
|
||||
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
|
||||
|
||||
// Shared resources for all frames in this session.
|
||||
// These live for the duration of the frame tree (root + frames).
|
||||
// Shared allocator. Used by Session itself and borrowed by Pages.
|
||||
arena_pool: *ArenaPool,
|
||||
|
||||
frame: ?Frame,
|
||||
// Pool for FinalizerCallback.Identity structs. These must survive Page
|
||||
// teardowns so V8 weak callbacks can validate the FC before dereferencing it.
|
||||
fc_identity_pool: std.heap.MemoryPool(FinalizerCallback.Identity),
|
||||
|
||||
// Double buffer so that, as we process one list of queued navigations, new entries
|
||||
// are added to the separate buffer. This ensures that we don't end up with
|
||||
// endless navigation loops AND that we don't invalidate the list while iterating
|
||||
// if a new entry gets appended
|
||||
queued_navigation_1: std.ArrayList(*Frame),
|
||||
queued_navigation_2: std.ArrayList(*Frame),
|
||||
// pointer to either queued_navigation_1 or queued_navigation_2
|
||||
queued_navigation: *std.ArrayList(*Frame),
|
||||
|
||||
// Temporary buffer for about:blank navigations during processing.
|
||||
// We process async navigations first (safe from re-entrance), then sync
|
||||
// about:blank navigations (which may add to queued_navigation).
|
||||
queued_queued_navigation: std.ArrayList(*Frame),
|
||||
// The currently-active Page. Null when no Page exists (between removePage
|
||||
// and createPage, or at startup).
|
||||
page: ?Page,
|
||||
|
||||
// IDs. Kept at Session level so IDs can remain unique across Page replacements.
|
||||
frame_id_gen: u32 = 0,
|
||||
loader_id_gen: u32 = 0,
|
||||
|
||||
@@ -117,57 +80,47 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
||||
const arena = try arena_pool.acquire(.small, "Session");
|
||||
errdefer arena_pool.release(arena);
|
||||
|
||||
const frame_arena = try arena_pool.acquire(.large, "Session.frame_arena");
|
||||
errdefer arena_pool.release(frame_arena);
|
||||
|
||||
self.* = .{
|
||||
.frame = null,
|
||||
.page = null,
|
||||
.arena = arena,
|
||||
.arena_pool = arena_pool,
|
||||
.frame_arena = frame_arena,
|
||||
.factory = Factory.init(frame_arena),
|
||||
.history = .{},
|
||||
// The prototype (EventTarget) for Navigation is created when a Frame is created.
|
||||
.navigation = .{ ._proto = undefined },
|
||||
.storage_shed = .{},
|
||||
.browser = browser,
|
||||
.queued_navigation = undefined,
|
||||
.queued_navigation_1 = .{},
|
||||
.queued_navigation_2 = .{},
|
||||
.queued_queued_navigation = .{},
|
||||
.notification = notification,
|
||||
.cookie_jar = storage.Cookie.Jar.init(allocator),
|
||||
.fc_identity_pool = .init(allocator),
|
||||
.cookie_jar = storage.Cookie.Jar.init(allocator),
|
||||
};
|
||||
self.queued_navigation = &self.queued_navigation_1;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Session) void {
|
||||
if (self.frame != null) {
|
||||
self.removeFrame();
|
||||
if (self.page != null) {
|
||||
self.removePage();
|
||||
}
|
||||
self.cookie_jar.deinit();
|
||||
self.fc_identity_pool.deinit();
|
||||
|
||||
self.storage_shed.deinit(self.browser.app.allocator);
|
||||
self.arena_pool.release(self.frame_arena);
|
||||
self.arena_pool.release(self.arena);
|
||||
}
|
||||
|
||||
// NOTE: the caller is not the owner of the returned value,
|
||||
// the pointer on Frame is just returned as a convenience
|
||||
pub fn createFrame(self: *Session) !*Frame {
|
||||
lp.assert(self.frame == null, "Session.createFrame - frame not null", .{});
|
||||
pub fn createPage(self: *Session) !*Frame {
|
||||
lp.assert(self.page == null, "Session.createPage - page not null", .{});
|
||||
|
||||
self.frame = @as(Frame, undefined);
|
||||
const frame = &self.frame.?;
|
||||
try Frame.init(frame, self.nextFrameId(), self, null);
|
||||
self.page = @as(Page, undefined);
|
||||
const page = &self.page.?;
|
||||
try Page.init(page, self, self.nextFrameId());
|
||||
const frame = &page.frame;
|
||||
|
||||
// Creates a new NavigationEventTarget for this frame.
|
||||
try self.navigation.onNewFrame(frame);
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "create frame", .{});
|
||||
log.debug(.browser, "create page", .{});
|
||||
}
|
||||
// start JS env
|
||||
// Inform CDP the main frame has been created such that additional context for other Worlds can be created as well
|
||||
@@ -176,19 +129,22 @@ pub fn createFrame(self: *Session) !*Frame {
|
||||
return frame;
|
||||
}
|
||||
|
||||
pub fn removeFrame(self: *Session) void {
|
||||
pub fn removePage(self: *Session) void {
|
||||
// Inform CDP the frame is going to be removed, allowing other worlds to remove themselves before the main one
|
||||
self.notification.dispatch(.frame_remove, .{});
|
||||
lp.assert(self.frame != null, "Session.removeFrame - frame is null", .{});
|
||||
lp.assert(self.page != null, "Session.removePage - page is null", .{});
|
||||
|
||||
self.frame.?.deinit(false);
|
||||
self.frame = null;
|
||||
self.page.?.deinit(false);
|
||||
self.page = null;
|
||||
|
||||
self.navigation.onRemoveFrame();
|
||||
self.resetFrameResources();
|
||||
|
||||
// resetting frame_id_gen preserves previous behavior where removing the
|
||||
// root page returned us to a clean-slate state.
|
||||
self.frame_id_gen = 0;
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "remove frame", .{});
|
||||
log.debug(.browser, "remove page", .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,132 +157,49 @@ pub fn releaseArena(self: *Session, allocator: Allocator) void {
|
||||
}
|
||||
|
||||
pub fn getOrCreateOrigin(self: *Session, key_: ?[]const u8) !*js.Origin {
|
||||
const key = key_ orelse {
|
||||
var opaque_origin: [36]u8 = undefined;
|
||||
@import("../id.zig").uuidv4(&opaque_origin);
|
||||
// Origin.init will dupe opaque_origin. It's fine that this doesn't
|
||||
// get added to self.origins. In fact, it further isolates it. When the
|
||||
// context is freed, it'll call session.releaseOrigin which will free it.
|
||||
return js.Origin.init(self.browser.app, self.browser.env.isolate, &opaque_origin);
|
||||
};
|
||||
|
||||
const gop = try self.origins.getOrPut(self.arena, key);
|
||||
if (gop.found_existing) {
|
||||
const origin = gop.value_ptr.*;
|
||||
origin.rc += 1;
|
||||
return origin;
|
||||
}
|
||||
|
||||
errdefer _ = self.origins.remove(key);
|
||||
|
||||
const origin = try js.Origin.init(self.browser.app, self.browser.env.isolate, key);
|
||||
gop.key_ptr.* = origin.key;
|
||||
gop.value_ptr.* = origin;
|
||||
return origin;
|
||||
return self.page.?.getOrCreateOrigin(key_);
|
||||
}
|
||||
|
||||
pub fn releaseOrigin(self: *Session, origin: *js.Origin) void {
|
||||
const rc = origin.rc;
|
||||
if (rc == 1) {
|
||||
_ = self.origins.remove(origin.key);
|
||||
origin.deinit(self.browser.app);
|
||||
} else {
|
||||
origin.rc = rc - 1;
|
||||
}
|
||||
return self.page.?.releaseOrigin(origin);
|
||||
}
|
||||
|
||||
/// Reset frame_arena and factory for a clean slate.
|
||||
/// Called when root frame is removed.
|
||||
fn resetFrameResources(self: *Session) void {
|
||||
defer self.browser.env.memoryPressureNotification(.moderate);
|
||||
|
||||
self.identity.deinit();
|
||||
self.identity = .{};
|
||||
|
||||
// Force cleanup all remaining finalized objects
|
||||
{
|
||||
var it = self.finalizer_callbacks.valueIterator();
|
||||
while (it.next()) |fc| {
|
||||
fc.*.deinit(self);
|
||||
}
|
||||
self.finalizer_callbacks = .empty;
|
||||
}
|
||||
|
||||
{
|
||||
for (self.globals.items) |*global| {
|
||||
v8.v8__Global__Reset(global);
|
||||
}
|
||||
self.globals = .empty;
|
||||
}
|
||||
|
||||
{
|
||||
var it = self.temps.valueIterator();
|
||||
while (it.next()) |global| {
|
||||
v8.v8__Global__Reset(global);
|
||||
}
|
||||
self.temps = .empty;
|
||||
}
|
||||
|
||||
pub fn replacePage(self: *Session) !*Frame {
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.origins.count() == 0);
|
||||
}
|
||||
// Defensive cleanup in case origins leaked
|
||||
{
|
||||
const app = self.browser.app;
|
||||
var it = self.origins.valueIterator();
|
||||
while (it.next()) |value| {
|
||||
value.*.deinit(app);
|
||||
}
|
||||
self.origins = .empty;
|
||||
log.debug(.browser, "replace page", .{});
|
||||
}
|
||||
|
||||
self.frame_id_gen = 0;
|
||||
self.arena_pool.reset(self.frame_arena, 64 * 1024);
|
||||
self.factory = Factory.init(self.frame_arena);
|
||||
}
|
||||
lp.assert(self.page != null, "Session.replacePage null page", .{});
|
||||
const current = &self.page.?;
|
||||
lp.assert(current.frame.parent == null, "Session.replacePage with parent", .{});
|
||||
|
||||
pub fn replaceFrame(self: *Session) !*Frame {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "replace frame", .{});
|
||||
}
|
||||
|
||||
lp.assert(self.frame != null, "Session.replaceFrame null frame", .{});
|
||||
lp.assert(self.frame.?.parent == null, "Session.replaceFrame with parent", .{});
|
||||
|
||||
var current = self.frame.?;
|
||||
const frame_id = current._frame_id;
|
||||
const frame_id = current.frame._frame_id;
|
||||
current.deinit(true);
|
||||
self.page = null;
|
||||
|
||||
self.resetFrameResources();
|
||||
// Preserve prior behavior: frame_id_gen reset on root replacement so a
|
||||
// subsequent createPage starts from id 1. The captured frame_id is
|
||||
// passed into Page.init explicitly, so it isn't affected.
|
||||
self.frame_id_gen = 0;
|
||||
|
||||
self.frame = @as(Frame, undefined);
|
||||
const frame = &self.frame.?;
|
||||
try Frame.init(frame, frame_id, self, null);
|
||||
return frame;
|
||||
self.page = @as(Page, undefined);
|
||||
const page = &self.page.?;
|
||||
try Page.init(page, self, frame_id);
|
||||
return &page.frame;
|
||||
}
|
||||
|
||||
pub fn currentPage(self: *Session) ?*Page {
|
||||
return &(self.page orelse return null);
|
||||
}
|
||||
|
||||
pub fn currentFrame(self: *Session) ?*Frame {
|
||||
return &(self.frame orelse return null);
|
||||
const page = self.currentPage() orelse return null;
|
||||
return &page.frame;
|
||||
}
|
||||
|
||||
pub fn findFrameByFrameId(self: *Session, frame_id: u32) ?*Frame {
|
||||
const frame = self.currentFrame() orelse return null;
|
||||
return findFrameBy(frame, "_frame_id", frame_id);
|
||||
}
|
||||
|
||||
pub fn findFrameByLoaderId(self: *Session, loader_id: u32) ?*Frame {
|
||||
const frame = self.currentFrame() orelse return null;
|
||||
return findFrameBy(frame, "_loader_id", loader_id);
|
||||
}
|
||||
|
||||
fn findFrameBy(frame: *Frame, comptime field: []const u8, id: u32) ?*Frame {
|
||||
if (@field(frame, field) == id) return frame;
|
||||
for (frame.child_frames.items) |f| {
|
||||
if (findFrameBy(f, field, id)) |found| {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
const page = self.currentPage() orelse return null;
|
||||
return page.findFrameByFrameId(frame_id);
|
||||
}
|
||||
|
||||
pub fn runner(self: *Session, opts: Runner.Opts) !Runner {
|
||||
@@ -334,28 +207,19 @@ pub fn runner(self: *Session, opts: Runner.Opts) !Runner {
|
||||
}
|
||||
|
||||
pub fn scheduleNavigation(self: *Session, frame: *Frame) !void {
|
||||
const list = self.queued_navigation;
|
||||
|
||||
// Check if frame is already queued
|
||||
for (list.items) |existing| {
|
||||
if (existing == frame) {
|
||||
// Already queued
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return list.append(self.arena, frame);
|
||||
return self.page.?.scheduleNavigation(frame);
|
||||
}
|
||||
|
||||
pub fn processQueuedNavigation(self: *Session) !void {
|
||||
const navigations = self.queued_navigation;
|
||||
if (self.queued_navigation == &self.queued_navigation_1) {
|
||||
self.queued_navigation = &self.queued_navigation_2;
|
||||
const page = self.currentPage() orelse return;
|
||||
const navigations = page.queued_navigation;
|
||||
if (page.queued_navigation == &page.queued_navigation_1) {
|
||||
page.queued_navigation = &page.queued_navigation_2;
|
||||
} else {
|
||||
self.queued_navigation = &self.queued_navigation_1;
|
||||
page.queued_navigation = &page.queued_navigation_1;
|
||||
}
|
||||
|
||||
if (self.frame.?._queued_navigation != null) {
|
||||
if (page.frame._queued_navigation != null) {
|
||||
// This is both an optimization and a simplification of sorts. If the
|
||||
// root frame is navigating, then we don't need to process any other
|
||||
// navigation. Also, the navigation for the root frame and for a frame
|
||||
@@ -365,7 +229,7 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
return self.processRootQueuedNavigation();
|
||||
}
|
||||
|
||||
const about_blank_queue = &self.queued_queued_navigation;
|
||||
const about_blank_queue = &page.queued_queued_navigation;
|
||||
defer about_blank_queue.clearRetainingCapacity();
|
||||
|
||||
// First pass: process async navigations (non-about:blank)
|
||||
@@ -395,14 +259,14 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
// Safety: Remove any about:blank navigations that were queued during
|
||||
// processing to prevent infinite loops. New navigations have been queued
|
||||
// in the other buffer.
|
||||
const new_navigations = self.queued_navigation;
|
||||
const new_navigations = page.queued_navigation;
|
||||
var i: usize = 0;
|
||||
while (i < new_navigations.items.len) {
|
||||
const frame = new_navigations.items[i];
|
||||
if (frame._queued_navigation) |qn| {
|
||||
if (qn.is_about_blank) {
|
||||
log.warn(.frame, "recursive about blank", .{});
|
||||
_ = self.queued_navigation.swapRemove(i);
|
||||
_ = page.queued_navigation.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -428,10 +292,11 @@ fn processFrameNavigation(self: *Session, frame: *Frame, qn: *QueuedNavigation)
|
||||
}
|
||||
|
||||
const frame_id = frame._frame_id;
|
||||
const page = self.currentPage().?;
|
||||
frame.deinit(true);
|
||||
frame.* = undefined;
|
||||
|
||||
try Frame.init(frame, frame_id, self, parent);
|
||||
try Frame.init(frame, frame_id, page, parent);
|
||||
errdefer {
|
||||
for (parent.child_frames.items, 0..) |f, i| {
|
||||
if (f == frame) {
|
||||
@@ -456,7 +321,7 @@ fn processFrameNavigation(self: *Session, frame: *Frame, qn: *QueuedNavigation)
|
||||
}
|
||||
|
||||
fn processRootQueuedNavigation(self: *Session) !void {
|
||||
const current_frame = &self.frame.?;
|
||||
const current_frame = &self.page.?.frame;
|
||||
const frame_id = current_frame._frame_id;
|
||||
|
||||
// create a copy before the frame is cleared
|
||||
@@ -465,11 +330,21 @@ fn processRootQueuedNavigation(self: *Session) !void {
|
||||
|
||||
defer self.arena_pool.release(qn.arena);
|
||||
|
||||
self.removeFrame();
|
||||
// Dispatch frame_remove (same as removePage) then replace the Page
|
||||
// in-place, keeping the frame_id stable.
|
||||
self.notification.dispatch(.frame_remove, .{});
|
||||
self.page.?.deinit(true);
|
||||
self.page = null;
|
||||
|
||||
self.frame = @as(Frame, undefined);
|
||||
const new_frame = &self.frame.?;
|
||||
try Frame.init(new_frame, frame_id, self, null);
|
||||
self.navigation.onRemoveFrame();
|
||||
|
||||
// Preserve prior behavior: the old resetFrameResources reset frame_id_gen.
|
||||
self.frame_id_gen = 0;
|
||||
|
||||
self.page = @as(Page, undefined);
|
||||
const page = &self.page.?;
|
||||
try Page.init(page, self, frame_id);
|
||||
const new_frame = &page.frame;
|
||||
|
||||
// Creates a new NavigationEventTarget for this frame.
|
||||
try self.navigation.onNewFrame(new_frame);
|
||||
@@ -497,14 +372,14 @@ pub fn nextLoaderId(self: *Session) u32 {
|
||||
}
|
||||
|
||||
// Every finalizable instance of Zig gets 1 FinalizerCallback registered in the
|
||||
// session. This is to ensure that, if v8 doesn't finalize the value, we can
|
||||
// release on frame reset.
|
||||
// Page. This is to ensure that, if v8 doesn't finalize the value, we can
|
||||
// release on Page teardown.
|
||||
pub const FinalizerCallback = struct {
|
||||
page: *Page,
|
||||
arena: Allocator,
|
||||
session: *Session,
|
||||
resolved_ptr_id: usize,
|
||||
finalizer_ptr_id: usize,
|
||||
release_ref: *const fn (ptr_id: usize, session: *Session) void,
|
||||
release_ref: *const fn (ptr_id: usize, page: *Page) void,
|
||||
|
||||
// Linked list of Identities referencing this FC.
|
||||
identities: ?*Identity = null,
|
||||
@@ -513,10 +388,14 @@ pub const FinalizerCallback = struct {
|
||||
|
||||
// For every FinalizerCallback we'll have 1+ FinalizerCallback.Identity: one
|
||||
// for every identity that gets the instance. In most cases, that'll be 1.
|
||||
// Allocated from Session.fc_identity_pool so it survives frame resets and
|
||||
// Allocated from Session.fc_identity_pool so it survives Page teardowns and
|
||||
// allows the weak callback to safely check the done flag.
|
||||
pub const Identity = struct {
|
||||
session: *Session,
|
||||
// The Page that owns the FinalizerCallback this Identity references.
|
||||
// Only safe to dereference when `done == false`. When done is true,
|
||||
// the Page may have been torn down and this pointer is stale.
|
||||
page: *Page,
|
||||
identity: *js.Identity,
|
||||
finalizer_ptr_id: usize,
|
||||
resolved_ptr_id: usize,
|
||||
@@ -524,8 +403,8 @@ pub const FinalizerCallback = struct {
|
||||
done: bool = false,
|
||||
};
|
||||
|
||||
// Called during frame reset to force cleanup regardless of identities.
|
||||
fn deinit(self: *FinalizerCallback, session: *Session) void {
|
||||
// Called during Page teardown to force cleanup regardless of identities.
|
||||
pub fn deinit(self: *FinalizerCallback, page: *Page) void {
|
||||
// Mark all identities as done so stale V8 weak callbacks
|
||||
// won't find the wrong FC if resolved_ptr_id is reused.
|
||||
var id = self.identities;
|
||||
@@ -533,7 +412,7 @@ pub const FinalizerCallback = struct {
|
||||
identity.done = true;
|
||||
id = identity.next;
|
||||
}
|
||||
self.release_ref(self.finalizer_ptr_id, session);
|
||||
session.releaseArena(self.arena);
|
||||
self.release_ref(self.finalizer_ptr_id, page);
|
||||
page.releaseArena(self.arena);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,12 +28,12 @@ const Session = @import("Session.zig");
|
||||
const Selector = @import("webapi/selector/Selector.zig");
|
||||
|
||||
fn dispatchInputAndChangeEvents(el: *Element, frame: *Frame) !void {
|
||||
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, frame._session);
|
||||
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, frame._page);
|
||||
frame._event_manager.dispatch(el.asEventTarget(), input_evt) catch |err| {
|
||||
lp.log.err(.app, "dispatch input event failed", .{ .err = err });
|
||||
};
|
||||
|
||||
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, frame._session);
|
||||
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, frame._page);
|
||||
frame._event_manager.dispatch(el.asEventTarget(), change_evt) catch |err| {
|
||||
lp.log.err(.app, "dispatch change event failed", .{ .err = err });
|
||||
};
|
||||
@@ -196,7 +196,7 @@ pub fn scroll(node: ?*DOMNode, x: ?i32, y: ?i32, frame: *Frame) !void {
|
||||
};
|
||||
}
|
||||
|
||||
const scroll_evt: *Event = try .initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, frame._session);
|
||||
const scroll_evt: *Event = try .initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, frame._page);
|
||||
frame._event_manager.dispatch(el.asEventTarget(), scroll_evt) catch |err| {
|
||||
lp.log.err(.app, "dispatch scroll event failed", .{ .err = err });
|
||||
};
|
||||
|
||||
@@ -278,7 +278,7 @@ fn collectSelectOptions(
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
fn testForms(html: []const u8) ![]FormInfo {
|
||||
const frame = try testing.test_session.createFrame();
|
||||
const frame = try testing.test_session.createPage();
|
||||
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
@@ -289,7 +289,7 @@ fn testForms(html: []const u8) ![]FormInfo {
|
||||
|
||||
test "browser.forms: login form" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form action="/login" method="POST">
|
||||
\\ <input type="email" name="email" required placeholder="Email">
|
||||
@@ -310,7 +310,7 @@ test "browser.forms: login form" {
|
||||
|
||||
test "browser.forms: form with select" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <select name="color">
|
||||
@@ -329,7 +329,7 @@ test "browser.forms: form with select" {
|
||||
|
||||
test "browser.forms: form with textarea" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form method="POST">
|
||||
\\ <textarea name="message" placeholder="Your message"></textarea>
|
||||
@@ -343,7 +343,7 @@ test "browser.forms: form with textarea" {
|
||||
|
||||
test "browser.forms: empty form skipped" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form action="/empty">
|
||||
\\ <p>No fields here</p>
|
||||
@@ -354,7 +354,7 @@ test "browser.forms: empty form skipped" {
|
||||
|
||||
test "browser.forms: hidden inputs excluded" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <input type="hidden" name="csrf" value="token123">
|
||||
@@ -368,7 +368,7 @@ test "browser.forms: hidden inputs excluded" {
|
||||
|
||||
test "browser.forms: multiple forms" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form action="/search" method="GET">
|
||||
\\ <input type="text" name="q" placeholder="Search">
|
||||
@@ -385,7 +385,7 @@ test "browser.forms: multiple forms" {
|
||||
|
||||
test "browser.forms: disabled fields flagged" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <input type="text" name="enabled_field">
|
||||
@@ -400,7 +400,7 @@ test "browser.forms: disabled fields flagged" {
|
||||
|
||||
test "browser.forms: disabled fieldset" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <fieldset disabled>
|
||||
@@ -417,7 +417,7 @@ test "browser.forms: disabled fieldset" {
|
||||
|
||||
test "browser.forms: external field via form attribute" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<input type="text" name="external" form="myform">
|
||||
\\<form id="myform" action="/submit">
|
||||
@@ -430,7 +430,7 @@ test "browser.forms: external field via form attribute" {
|
||||
|
||||
test "browser.forms: checkbox and radio return value attribute" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <input type="checkbox" name="agree" value="yes" checked>
|
||||
@@ -447,7 +447,7 @@ test "browser.forms: checkbox and radio return value attribute" {
|
||||
|
||||
test "browser.forms: form without action or method" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removeFrame();
|
||||
defer testing.test_session.removePage();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <input type="text" name="q">
|
||||
|
||||
@@ -451,8 +451,8 @@ fn getListenerTypes(target: *EventTarget, listener_targets: ListenerTargetMap) [
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
fn testInteractive(html: []const u8) ![]InteractiveElement {
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
const frame = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
|
||||
@@ -21,6 +21,7 @@ const lp = @import("lightpanda");
|
||||
const string = @import("../../string.zig");
|
||||
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
|
||||
|
||||
@@ -447,10 +448,14 @@ fn tupleFieldName(comptime i: usize) [:0]const u8 {
|
||||
};
|
||||
}
|
||||
|
||||
fn isPage(comptime T: type) bool {
|
||||
fn isFrame(comptime T: type) bool {
|
||||
return T == *Frame or T == *const Frame;
|
||||
}
|
||||
|
||||
fn isPage(comptime T: type) bool {
|
||||
return T == *Page or T == *const Page;
|
||||
}
|
||||
|
||||
fn isSession(comptime T: type) bool {
|
||||
return T == *Session or T == *const Session;
|
||||
}
|
||||
@@ -460,19 +465,19 @@ fn isExecution(comptime T: type) bool {
|
||||
}
|
||||
|
||||
fn getGlobalArg(comptime T: type, ctx: *Context) T {
|
||||
if (comptime isPage(T)) {
|
||||
if (comptime isFrame(T)) {
|
||||
return switch (ctx.global) {
|
||||
.frame => |frame| frame,
|
||||
.worker => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
if (comptime isExecution(T)) {
|
||||
return &ctx.execution;
|
||||
if (comptime isPage(T)) {
|
||||
return ctx.page;
|
||||
}
|
||||
|
||||
if (comptime isSession(T)) {
|
||||
return ctx.session;
|
||||
if (comptime isExecution(T)) {
|
||||
return &ctx.execution;
|
||||
}
|
||||
|
||||
@compileError("Unsupported global arg type: " ++ @typeName(T));
|
||||
@@ -761,17 +766,17 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info:
|
||||
return args;
|
||||
}
|
||||
|
||||
// If the last parameter is the Page or Worker, set it, and exclude it
|
||||
// from our params slice, because we don't want to bind it to
|
||||
// a JS argument
|
||||
// If the last parameter is Frame/Page/Session/Execution, set it from
|
||||
// context and exclude it from our params slice, because we don't want
|
||||
// to bind it to a JS argument.
|
||||
const LastParamType = params[params.len - 1].type.?;
|
||||
if (comptime isPage(LastParamType) or isExecution(LastParamType) or isSession(LastParamType)) {
|
||||
if (comptime isFrame(LastParamType) or isPage(LastParamType) or isExecution(LastParamType) or isSession(LastParamType)) {
|
||||
@field(args, tupleFieldName(params.len - 1 + offset)) = getGlobalArg(LastParamType, local.ctx);
|
||||
break :blk params[0 .. params.len - 1];
|
||||
}
|
||||
|
||||
// we have neither a Page, Execution, nor a JsObject. All params must be
|
||||
// bound to a JavaScript value.
|
||||
// we have neither a Frame/Page/Session/Execution nor a JsObject.
|
||||
// All params must be bound to a JavaScript value.
|
||||
break :blk params;
|
||||
};
|
||||
|
||||
@@ -818,7 +823,9 @@ fn getArgs(comptime F: type, comptime offset: usize, local: *const Local, info:
|
||||
}
|
||||
}
|
||||
|
||||
if (comptime isPage(param.type.?)) {
|
||||
if (comptime isFrame(param.type.?)) {
|
||||
@compileError("Frame must be the last parameter: " ++ @typeName(F));
|
||||
} else if (comptime isPage(param.type.?)) {
|
||||
@compileError("Page must be the last parameter: " ++ @typeName(F));
|
||||
} else if (comptime isExecution(param.type.?)) {
|
||||
@compileError("Execution must be the last parameter: " ++ @typeName(F));
|
||||
|
||||
@@ -27,6 +27,7 @@ const Scheduler = @import("Scheduler.zig");
|
||||
const Execution = @import("Execution.zig");
|
||||
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const ScriptManager = @import("../ScriptManager.zig");
|
||||
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
|
||||
@@ -69,7 +70,14 @@ pub const GlobalScope = union(enum) {
|
||||
id: usize,
|
||||
env: *Env,
|
||||
global: GlobalScope,
|
||||
session: *Session,
|
||||
|
||||
// The Page this Context belongs to. For main-world frame contexts, this is
|
||||
// the Page of the frame. For worker contexts, this is the Page of the
|
||||
// worker's parent frame — a worker's v8 globals and identity tracking live
|
||||
// on the same Page as its owning frame (worker dies with its page). The
|
||||
// Session is always reachable via `page.session`.
|
||||
page: *Page,
|
||||
|
||||
isolate: js.Isolate,
|
||||
|
||||
// Per-context microtask queue for isolation between contexts
|
||||
@@ -114,7 +122,7 @@ origin: *Origin,
|
||||
identity: *js.Identity,
|
||||
|
||||
// Allocator to use for identity map operations. For main world contexts this is
|
||||
// session.frame_arena, for isolated worlds it's the isolated world's arena.
|
||||
// page.frame_arena, for isolated worlds it's the isolated world's arena.
|
||||
identity_arena: Allocator,
|
||||
|
||||
// Unlike other v8 types, like functions or objects, modules are not shared
|
||||
@@ -207,7 +215,7 @@ pub fn deinit(self: *Context) void {
|
||||
v8.v8__Global__Reset(global);
|
||||
}
|
||||
|
||||
self.session.releaseOrigin(self.origin);
|
||||
self.page.releaseOrigin(self.origin);
|
||||
|
||||
// Clear the embedder data so that if V8 keeps this context alive
|
||||
// (because objects created in it are still referenced), we don't
|
||||
@@ -234,9 +242,9 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
|
||||
lp.assert(self.origin.rc == 1, "Ref opaque origin", .{ .rc = self.origin.rc });
|
||||
}
|
||||
|
||||
const origin = try self.session.getOrCreateOrigin(key);
|
||||
const origin = try self.page.getOrCreateOrigin(key);
|
||||
|
||||
self.session.releaseOrigin(self.origin);
|
||||
self.page.releaseOrigin(self.origin);
|
||||
self.origin = origin;
|
||||
|
||||
{
|
||||
@@ -252,11 +260,11 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
|
||||
}
|
||||
|
||||
pub fn trackGlobal(self: *Context, global: v8.Global) !void {
|
||||
return self.session.globals.append(self.session.frame_arena, global);
|
||||
return self.page.globals.append(self.page.frame_arena, global);
|
||||
}
|
||||
|
||||
pub fn trackTemp(self: *Context, global: v8.Global) !void {
|
||||
return self.session.temps.put(self.session.frame_arena, global.data_ptr, global);
|
||||
return self.page.temps.put(self.page.frame_arena, global.data_ptr, global);
|
||||
}
|
||||
|
||||
pub const IdentityResult = struct {
|
||||
|
||||
@@ -275,9 +275,9 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
const context_id = self.context_id;
|
||||
self.context_id = context_id + 1;
|
||||
|
||||
const session = global._session;
|
||||
const origin = try session.getOrCreateOrigin(null);
|
||||
errdefer session.releaseOrigin(origin);
|
||||
const page = global._page;
|
||||
const origin = try page.getOrCreateOrigin(null);
|
||||
errdefer page.releaseOrigin(origin);
|
||||
|
||||
const context = try context_arena.create(Context);
|
||||
context.* = .{
|
||||
@@ -285,7 +285,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
.global = if (comptime is_frame) .{ .frame = global } else .{ .worker = global },
|
||||
.origin = origin,
|
||||
.id = context_id,
|
||||
.session = session,
|
||||
.page = page,
|
||||
.isolate = isolate,
|
||||
.arena = context_arena,
|
||||
.handle = context_global,
|
||||
@@ -558,8 +558,8 @@ const PrivateSymbols = struct {
|
||||
const testing = @import("../../testing.zig");
|
||||
test "Env: Worker context " {
|
||||
const session = testing.test_session;
|
||||
const frame = try session.createFrame();
|
||||
defer session.removeFrame();
|
||||
const frame = try session.createPage();
|
||||
defer session.removePage();
|
||||
|
||||
const worker = try @import("../webapi/Worker.zig").init("http://localhost:9582/src/browser/tests/testing.js", &frame.js.execution);
|
||||
|
||||
@@ -573,8 +573,8 @@ test "Env: Worker context " {
|
||||
|
||||
test "Env: Frame context" {
|
||||
const session = testing.test_session;
|
||||
const frame = try session.createFrame();
|
||||
defer session.removeFrame();
|
||||
const frame = try session.createPage();
|
||||
defer session.removePage();
|
||||
|
||||
// Frame already has a context created, use it directly
|
||||
const ctx = frame.js;
|
||||
|
||||
@@ -215,7 +215,7 @@ fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Gl
|
||||
return .{ .handle = global, .temps = {} };
|
||||
}
|
||||
try ctx.trackTemp(global);
|
||||
return .{ .handle = global, .temps = &ctx.session.temps };
|
||||
return .{ .handle = global, .temps = &ctx.page.temps };
|
||||
}
|
||||
|
||||
pub fn tempWithThis(self: *const Function, value: anytype) !Temp {
|
||||
|
||||
@@ -20,7 +20,8 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const string = @import("../../string.zig");
|
||||
|
||||
const Session = @import("../Session.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const FinalizerCallback = @import("../Session.zig").FinalizerCallback;
|
||||
|
||||
const js = @import("js.zig");
|
||||
const bridge = @import("bridge.zig");
|
||||
@@ -270,19 +271,21 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
|
||||
if (resolved.finalizer) |finalizer| {
|
||||
const finalizer_ptr_id = finalizer.ptr_id;
|
||||
|
||||
const session = ctx.session;
|
||||
const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.frame_arena, finalizer_ptr_id);
|
||||
const page = ctx.page;
|
||||
const session = page.session;
|
||||
const finalizer_gop = try page.finalizer_callbacks.getOrPut(page.frame_arena, finalizer_ptr_id);
|
||||
if (finalizer_gop.found_existing == false) {
|
||||
// This is the first context (and very likely only one) to
|
||||
// see this Zig instance. We need to create the FinalizerCallback
|
||||
// so that we can cleanup on frame reset if v8 doesn't finalize.
|
||||
errdefer _ = session.finalizer_callbacks.remove(finalizer_ptr_id);
|
||||
// so that we can cleanup on Page teardown if v8 doesn't finalize.
|
||||
errdefer _ = page.finalizer_callbacks.remove(finalizer_ptr_id);
|
||||
finalizer.acquire_ref(finalizer_ptr_id);
|
||||
finalizer_gop.value_ptr.* = try self.createFinalizerCallback(resolved_ptr_id, finalizer_ptr_id, finalizer.release_ref_from_zig);
|
||||
}
|
||||
const fc = finalizer_gop.value_ptr.*;
|
||||
const identity_finalizer = try session.fc_identity_pool.create();
|
||||
identity_finalizer.* = .{
|
||||
.page = page,
|
||||
.session = session,
|
||||
.identity = ctx.identity,
|
||||
.finalizer_ptr_id = finalizer_ptr_id,
|
||||
@@ -1178,7 +1181,7 @@ const Resolved = struct {
|
||||
ptr_id: usize,
|
||||
acquire_ref: *const fn (ptr_id: usize) void,
|
||||
release_ref: *const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void,
|
||||
release_ref_from_zig: *const fn (ptr_id: usize, session: *Session) void,
|
||||
release_ref_from_zig: *const fn (ptr_id: usize, page: *Page) void,
|
||||
};
|
||||
};
|
||||
pub fn resolveValue(value: anytype) Resolved {
|
||||
@@ -1224,12 +1227,12 @@ fn resolveT(comptime T: type, value: *T) Resolved {
|
||||
|
||||
fn releaseRef(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
|
||||
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
|
||||
const identity_finalizer: *Session.FinalizerCallback.Identity = @ptrCast(@alignCast(ptr));
|
||||
const identity_finalizer: *FinalizerCallback.Identity = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// Identity is allocated from pool, so it's valid even after frame reset.
|
||||
const session = identity_finalizer.session;
|
||||
const page = identity_finalizer.page;
|
||||
const resolved_ptr_id = identity_finalizer.resolved_ptr_id;
|
||||
defer session.fc_identity_pool.destroy(identity_finalizer);
|
||||
defer page.session.fc_identity_pool.destroy(identity_finalizer);
|
||||
|
||||
// Always clean up the identity map entry
|
||||
if (identity_finalizer.identity.identity_map.fetchRemove(resolved_ptr_id)) |kv| {
|
||||
@@ -1237,28 +1240,29 @@ fn resolveT(comptime T: type, value: *T) Resolved {
|
||||
v8.v8__Global__Reset(&global);
|
||||
}
|
||||
|
||||
// If done, FC was already cleaned up during frame reset. The
|
||||
// finalizer_ptr_id may have been reused for a new object, so
|
||||
// we must not look it up in the map.
|
||||
// If done, FC was already cleaned up during Page teardown.
|
||||
// The finalizer_ptr_id may have been reused for a new object,
|
||||
// so we must not look it up in the map. It's also unsafe to
|
||||
// dereference identity_finalizer.page after done is true.
|
||||
if (identity_finalizer.done) return;
|
||||
|
||||
const finalizer_ptr_id = identity_finalizer.finalizer_ptr_id;
|
||||
const fc = session.finalizer_callbacks.get(finalizer_ptr_id) orelse return;
|
||||
const fc = page.finalizer_callbacks.get(finalizer_ptr_id) orelse return;
|
||||
|
||||
const identity_count = fc.identity_count;
|
||||
if (identity_count == 1) {
|
||||
// Last identity - clean up the FC.
|
||||
// Remove from map before releaseRef to prevent address reuse issues.
|
||||
_ = session.finalizer_callbacks.remove(finalizer_ptr_id);
|
||||
FT.releaseRef(@ptrFromInt(finalizer_ptr_id), session);
|
||||
session.releaseArena(fc.arena);
|
||||
_ = page.finalizer_callbacks.remove(finalizer_ptr_id);
|
||||
FT.releaseRef(@ptrFromInt(finalizer_ptr_id), page);
|
||||
page.releaseArena(fc.arena);
|
||||
} else {
|
||||
fc.identity_count = identity_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn releaseRefFromZig(ptr_id: usize, session: *Session) void {
|
||||
FT.releaseRef(@ptrFromInt(ptr_id), session);
|
||||
fn releaseRefFromZig(ptr_id: usize, page: *Page) void {
|
||||
FT.releaseRef(@ptrFromInt(ptr_id), page);
|
||||
}
|
||||
};
|
||||
break :blk .{
|
||||
@@ -1524,17 +1528,17 @@ fn createFinalizerCallback(
|
||||
// The most specific value where finalizers are defined
|
||||
// What actually gets acquired / released / deinit
|
||||
finalizer_ptr_id: usize,
|
||||
release_ref: *const fn (ptr_id: usize, session: *Session) void,
|
||||
) !*Session.FinalizerCallback {
|
||||
const session = self.ctx.session;
|
||||
release_ref: *const fn (ptr_id: usize, page: *Page) void,
|
||||
) !*FinalizerCallback {
|
||||
const page = self.ctx.page;
|
||||
|
||||
const arena = try session.getArena(.tiny, "FinalizerCallback");
|
||||
errdefer session.releaseArena(arena);
|
||||
const arena = try page.getArena(.tiny, "FinalizerCallback");
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const fc = try arena.create(Session.FinalizerCallback);
|
||||
const fc = try arena.create(FinalizerCallback);
|
||||
fc.* = .{
|
||||
.page = page,
|
||||
.arena = arena,
|
||||
.session = session,
|
||||
.release_ref = release_ref,
|
||||
.resolved_ptr_id = resolved_ptr_id,
|
||||
.finalizer_ptr_id = finalizer_ptr_id,
|
||||
|
||||
@@ -67,7 +67,7 @@ fn _persist(self: *const Promise, comptime is_global: bool) !(if (is_global) Glo
|
||||
return .{ .handle = global, .temps = {} };
|
||||
}
|
||||
try ctx.trackTemp(global);
|
||||
return .{ .handle = global, .temps = &ctx.session.temps };
|
||||
return .{ .handle = global, .temps = &ctx.page.temps };
|
||||
}
|
||||
|
||||
pub const Temp = G(.temp);
|
||||
|
||||
@@ -57,7 +57,7 @@ fn _toSlice(self: String, comptime null_terminate: bool, allocator: Allocator) !
|
||||
|
||||
pub fn toSSO(self: String, comptime global: bool) !(if (global) lp.String.Global else lp.String) {
|
||||
if (comptime global) {
|
||||
return .{ .str = try self.toSSOWithAlloc(self.local.ctx.session.frame_arena) };
|
||||
return .{ .str = try self.toSSOWithAlloc(self.local.ctx.page.frame_arena) };
|
||||
}
|
||||
return self.toSSOWithAlloc(self.local.call_arena);
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Globa
|
||||
return .{ .handle = global, .temps = {} };
|
||||
}
|
||||
try ctx.trackTemp(global);
|
||||
return .{ .handle = global, .temps = &ctx.session.temps };
|
||||
return .{ .handle = global, .temps = &ctx.page.temps };
|
||||
}
|
||||
|
||||
pub fn toZig(self: Value, comptime T: type) !T {
|
||||
|
||||
@@ -31,7 +31,7 @@ pub fn collectLinks(arena: Allocator, root: *Node, frame: *Frame) ![]const []con
|
||||
var links: std.ArrayList([]const u8) = .empty;
|
||||
|
||||
if (Selector.querySelectorAll(root, "a[href]", frame)) |list| {
|
||||
defer list.deinit(frame._session);
|
||||
defer list.deinit(frame._page);
|
||||
|
||||
for (list._nodes) |node| {
|
||||
if (node.is(Element.Html.Anchor)) |anchor| {
|
||||
|
||||
@@ -474,8 +474,8 @@ pub fn dump(node: *Node, opts: Opts, writer: *std.Io.Writer, frame: *Frame) !voi
|
||||
|
||||
fn testMarkdownHTML(html: []const u8, expected: []const u8) !void {
|
||||
const testing = @import("../testing.zig");
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
const frame = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
frame.url = "http://localhost/";
|
||||
|
||||
const doc = frame.window._document;
|
||||
@@ -677,8 +677,8 @@ test "browser.markdown: skip empty links" {
|
||||
|
||||
test "browser.markdown: resolve links" {
|
||||
const testing = @import("../testing.zig");
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
const frame = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
frame.url = "https://example.com/a/index.html";
|
||||
|
||||
const doc = frame.window._document;
|
||||
|
||||
@@ -318,8 +318,8 @@ fn collectLink(
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
fn testStructuredData(html: []const u8) !StructuredData {
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
const frame = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
|
||||
@@ -83,7 +83,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, exec: *const Execution) !void
|
||||
switch (exec.context.global) {
|
||||
inline else => |g| {
|
||||
if (g._event_manager.hasDirectListeners(target, "abort", on_abort)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, g._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, g._page);
|
||||
try g.dispatch(target, event, on_abort, .{ .context = "abort signal" });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -20,8 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Session = @import("../Session.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
const Range = @import("Range.zig");
|
||||
@@ -49,15 +48,15 @@ pub fn acquireRef(self: *AbstractRange) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AbstractRange, session: *Session) void {
|
||||
if (session.findFrameByLoaderId(self._frame_loader_id)) |frame| {
|
||||
pub fn deinit(self: *AbstractRange, page: *Page) void {
|
||||
if (page.findFrameByLoaderId(self._frame_loader_id)) |frame| {
|
||||
frame._live_ranges.remove(&self._range_link);
|
||||
}
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *AbstractRange, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *AbstractRange, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub const Type = union(enum) {
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const Mime = @import("../Mime.zig");
|
||||
|
||||
@@ -61,7 +61,8 @@ const InitOptions = struct {
|
||||
|
||||
/// Creates a new Blob from JS values with optional MIME validation.
|
||||
/// This is the JS Constructor
|
||||
pub fn init(parts_: ?[]const js.Value, opts_: ?InitOptions, session: *Session) !*Blob {
|
||||
pub fn init(parts_: ?[]const js.Value, opts_: ?InitOptions, page: *Page) !*Blob {
|
||||
const session = page.session;
|
||||
const arena = try session.getArena(.large, "Blob");
|
||||
errdefer session.releaseArena(arena);
|
||||
|
||||
@@ -94,9 +95,9 @@ pub fn init(parts_: ?[]const js.Value, opts_: ?InitOptions, session: *Session) !
|
||||
}
|
||||
|
||||
/// Creates a new Blob from raw byte slices (for internal Zig use).
|
||||
pub fn initFromBytes(data: []const u8, content_type: []const u8, validate_mime: bool, session: *Session) !*Blob {
|
||||
const arena = try session.getArena(.large, "Blob");
|
||||
errdefer session.releaseArena(arena);
|
||||
pub fn initFromBytes(data: []const u8, content_type: []const u8, validate_mime: bool, page: *Page) !*Blob {
|
||||
const arena = try page.getArena(.large, "Blob");
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const mime = try validateMimeType(arena, content_type, validate_mime);
|
||||
|
||||
@@ -137,12 +138,12 @@ fn validateMimeType(arena: Allocator, mime_type: []const u8, full_validation: bo
|
||||
return buf;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Blob, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *Blob, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Blob, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *Blob, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Blob) void {
|
||||
@@ -291,7 +292,7 @@ pub fn slice(
|
||||
start_: ?i32,
|
||||
end_: ?i32,
|
||||
content_type_: ?[]const u8,
|
||||
session: *Session,
|
||||
page: *Page,
|
||||
) !*Blob {
|
||||
const data = self._slice;
|
||||
|
||||
@@ -312,7 +313,7 @@ pub fn slice(
|
||||
break :blk @min(data.len, @max(start, @as(u31, @intCast(requested_end))));
|
||||
};
|
||||
|
||||
return Blob.initFromBytes(data[start..end], content_type_ orelse "", false, session);
|
||||
return Blob.initFromBytes(data[start..end], content_type_ orelse "", false, page);
|
||||
}
|
||||
|
||||
/// Returns the size of the Blob in bytes.
|
||||
|
||||
@@ -353,12 +353,12 @@ pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*
|
||||
const normalized = std.ascii.lowerString(&frame.buf, event_type);
|
||||
|
||||
if (std.mem.eql(u8, normalized, "event") or std.mem.eql(u8, normalized, "events") or std.mem.eql(u8, normalized, "htmlevents")) {
|
||||
return Event.init("", null, frame);
|
||||
return Event.init("", null, frame._page);
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "customevent") or std.mem.eql(u8, normalized, "customevents")) {
|
||||
const CustomEvent = @import("event/CustomEvent.zig");
|
||||
return (try CustomEvent.init("", null, frame)).asEvent();
|
||||
return (try CustomEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "keyboardevent")) {
|
||||
@@ -378,7 +378,7 @@ pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*
|
||||
|
||||
if (std.mem.eql(u8, normalized, "messageevent")) {
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
return (try MessageEvent.init("", null, frame._session)).asEvent();
|
||||
return (try MessageEvent.init("", null, frame._page)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "uievent") or std.mem.eql(u8, normalized, "uievents")) {
|
||||
|
||||
@@ -20,8 +20,8 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
@@ -89,16 +89,16 @@ pub const Options = struct {
|
||||
composed: bool = false,
|
||||
};
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?Options, frame: *Frame) !*Event {
|
||||
const arena = try frame.getArena(.tiny, "Event");
|
||||
errdefer frame.releaseArena(arena);
|
||||
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event {
|
||||
const arena = try page.getArena(.tiny, "Event");
|
||||
errdefer page.releaseArena(arena);
|
||||
const str = try String.init(arena, typ, .{});
|
||||
return initWithTrusted(arena, str, opts_, false);
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, session: *Session) !*Event {
|
||||
const arena = try session.getArena(.tiny, "Event.trusted");
|
||||
errdefer session.releaseArena(arena);
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*Event {
|
||||
const arena = try page.getArena(.tiny, "Event.trusted");
|
||||
errdefer page.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, opts_, true);
|
||||
}
|
||||
|
||||
@@ -145,13 +145,12 @@ pub fn acquireRef(self: *Event) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
/// Force cleanup on Session shutdown.
|
||||
pub fn deinit(self: *Event, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *Event, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Event, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *Event, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn as(self: *Event, comptime T: type) *T {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Session = @import("../Session.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const EventManager = @import("../EventManager.zig");
|
||||
|
||||
const Event = @import("Event.zig");
|
||||
@@ -52,8 +52,8 @@ pub const Type = union(enum) {
|
||||
websocket: *@import("net/WebSocket.zig"),
|
||||
};
|
||||
|
||||
pub fn init(session: *Session) !*EventTarget {
|
||||
return session.factory.create(EventTarget{
|
||||
pub fn init(page: *Page) !*EventTarget {
|
||||
return page.factory.create(EventTarget{
|
||||
._type = .generic,
|
||||
});
|
||||
}
|
||||
@@ -67,7 +67,7 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, exec: *js.Execution) !bo
|
||||
switch (exec.context.global) {
|
||||
.frame => |frame| {
|
||||
event.acquireRef();
|
||||
defer _ = event.releaseRef(frame._session);
|
||||
defer _ = event.releaseRef(frame._page);
|
||||
try frame._event_manager.dispatch(self, event);
|
||||
},
|
||||
.worker => |wgs| try wgs.dispatch(self, event, null, .{}),
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const Blob = @import("Blob.zig");
|
||||
|
||||
@@ -29,10 +29,11 @@ const File = @This();
|
||||
_proto: *Blob,
|
||||
|
||||
// TODO: Implement File API.
|
||||
pub fn init(session: *Session) !*File {
|
||||
pub fn init(page: *Page) !*File {
|
||||
const session = page.session;
|
||||
const arena = try session.getArena(.tiny, "File");
|
||||
errdefer session.releaseArena(arena);
|
||||
return session.factory.blob(arena, File{ ._proto = undefined });
|
||||
return page.factory.blob(arena, File{ ._proto = undefined });
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -21,8 +21,8 @@ const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
const ProgressEvent = @import("event/ProgressEvent.zig");
|
||||
const Blob = @import("Blob.zig");
|
||||
@@ -73,7 +73,7 @@ pub fn init(frame: *Frame) !*FileReader {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *FileReader, session: *Session) void {
|
||||
pub fn deinit(self: *FileReader, page: *Page) void {
|
||||
if (self._on_abort) |func| func.release();
|
||||
if (self._on_error) |func| func.release();
|
||||
if (self._on_load) |func| func.release();
|
||||
@@ -81,11 +81,11 @@ pub fn deinit(self: *FileReader, session: *Session) void {
|
||||
if (self._on_load_start) |func| func.release();
|
||||
if (self._on_progress) |func| func.release();
|
||||
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *FileReader, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *FileReader, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *FileReader) void {
|
||||
|
||||
@@ -20,8 +20,8 @@ const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -110,18 +110,18 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, frame: *Frame) !
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *IntersectionObserver, session: *Session) void {
|
||||
pub fn deinit(self: *IntersectionObserver, page: *Page) void {
|
||||
self._callback.release();
|
||||
for (self._pending_entries.items) |entry| {
|
||||
// These were never handed to v8, they do not have a corresponding
|
||||
// FinalizerCallback. We 100% own them.
|
||||
entry.deinit(session);
|
||||
entry.deinit(page);
|
||||
}
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *IntersectionObserver, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *IntersectionObserver, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *IntersectionObserver) void {
|
||||
@@ -164,7 +164,7 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, frame: *Frame) v
|
||||
while (j < self._pending_entries.items.len) {
|
||||
if (self._pending_entries.items[j]._target == target) {
|
||||
const entry = self._pending_entries.swapRemove(j);
|
||||
entry.deinit(frame._session);
|
||||
entry.deinit(frame._page);
|
||||
} else {
|
||||
j += 1;
|
||||
}
|
||||
@@ -180,7 +180,7 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, frame: *Frame) v
|
||||
|
||||
pub fn disconnect(self: *IntersectionObserver, frame: *Frame) void {
|
||||
for (self._pending_entries.items) |entry| {
|
||||
entry.deinit(frame._session);
|
||||
entry.deinit(frame._page);
|
||||
}
|
||||
self._pending_entries.clearRetainingCapacity();
|
||||
self._previous_states.clearRetainingCapacity();
|
||||
@@ -330,12 +330,12 @@ pub const IntersectionObserverEntry = struct {
|
||||
_intersection_ratio: f64,
|
||||
_is_intersecting: bool,
|
||||
|
||||
pub fn deinit(self: *IntersectionObserverEntry, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *IntersectionObserverEntry, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *IntersectionObserverEntry, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *IntersectionObserverEntry, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *IntersectionObserverEntry) void {
|
||||
|
||||
@@ -131,7 +131,7 @@ const PostMessageCallback = struct {
|
||||
.data = .{ .value = self.message },
|
||||
.origin = "",
|
||||
.source = null,
|
||||
}, frame._session) catch |err| {
|
||||
}, frame._page) catch |err| {
|
||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||
return null;
|
||||
}).asEvent();
|
||||
|
||||
@@ -20,8 +20,8 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -86,18 +86,18 @@ pub fn init(callback: js.Function.Temp, frame: *Frame) !*MutationObserver {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *MutationObserver, session: *Session) void {
|
||||
pub fn deinit(self: *MutationObserver, page: *Page) void {
|
||||
for (self._pending_records.items) |record| {
|
||||
// These were never handed to v8, they do not have a corresponding
|
||||
// FinalizerCallback. We 100% own them.
|
||||
record.deinit(session);
|
||||
record.deinit(page);
|
||||
}
|
||||
self._callback.release();
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *MutationObserver, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *MutationObserver, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *MutationObserver) void {
|
||||
@@ -178,7 +178,7 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
|
||||
|
||||
pub fn disconnect(self: *MutationObserver, frame: *Frame) void {
|
||||
for (self._pending_records.items) |record| {
|
||||
record.deinit(frame._session);
|
||||
record.deinit(frame._page);
|
||||
}
|
||||
self._pending_records.clearRetainingCapacity();
|
||||
|
||||
@@ -375,11 +375,11 @@ pub const MutationRecord = struct {
|
||||
characterData,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *MutationRecord, session: *Session) void {
|
||||
pub fn deinit(self: *MutationRecord, session: *Page) void {
|
||||
session.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *MutationRecord, session: *Session) void {
|
||||
pub fn releaseRef(self: *MutationRecord, session: *Page) void {
|
||||
self._rc.release(self, session);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -56,12 +57,12 @@ const PermissionStatus = struct {
|
||||
_name: []const u8,
|
||||
_state: []const u8,
|
||||
|
||||
pub fn deinit(self: *PermissionStatus, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *PermissionStatus, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *PermissionStatus, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *PermissionStatus, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *PermissionStatus) void {
|
||||
|
||||
@@ -20,8 +20,8 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const Range = @import("Range.zig");
|
||||
const AbstractRange = @import("AbstractRange.zig");
|
||||
@@ -39,15 +39,15 @@ _direction: SelectionDirection = .none,
|
||||
|
||||
pub const init: Selection = .{};
|
||||
|
||||
pub fn deinit(self: *Selection, session: *Session) void {
|
||||
pub fn deinit(self: *Selection, page: *Page) void {
|
||||
if (self._range) |r| {
|
||||
r.asAbstractRange().releaseRef(session);
|
||||
r.asAbstractRange().releaseRef(page);
|
||||
self._range = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Selection, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *Selection, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Selection) void {
|
||||
@@ -55,7 +55,7 @@ pub fn acquireRef(self: *Selection) void {
|
||||
}
|
||||
|
||||
fn dispatchSelectionChangeEvent(frame: *Frame) !void {
|
||||
const event = try Event.init("selectionchange", .{}, frame);
|
||||
const event = try Event.init("selectionchange", .{}, frame._page);
|
||||
try frame._event_manager.dispatch(frame.document.asEventTarget(), event);
|
||||
}
|
||||
|
||||
@@ -703,7 +703,7 @@ pub fn toString(self: *const Selection, frame: *Frame) ![]const u8 {
|
||||
|
||||
fn setRange(self: *Selection, new_range: ?*Range, frame: *Frame) void {
|
||||
if (self._range) |existing| {
|
||||
_ = existing.asAbstractRange().releaseRef(frame._session);
|
||||
_ = existing.asAbstractRange().releaseRef(frame._page);
|
||||
}
|
||||
if (new_range) |nr| {
|
||||
nr.asAbstractRange().acquireRef();
|
||||
|
||||
@@ -160,8 +160,8 @@ pub fn TreeWalker(comptime mode: Mode) type {
|
||||
|
||||
test "TreeWalker: skipChildren" {
|
||||
const testing = @import("../../testing.zig");
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
const frame = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
const doc = frame.window._document;
|
||||
|
||||
// <div>
|
||||
|
||||
@@ -274,7 +274,7 @@ pub fn revokeObjectURL(url: []const u8, exec: *const Execution) void {
|
||||
switch (exec.context.global) {
|
||||
inline else => |g| {
|
||||
if (g._blob_urls.fetchRemove(url)) |entry| {
|
||||
entry.value.releaseRef(g._session);
|
||||
entry.value.releaseRef(g._page);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ pub fn reportError(self: *Window, err: js.Value, frame: *Frame) !void {
|
||||
.message = err.toStringSlice() catch "Unknown error",
|
||||
.bubbles = false,
|
||||
.cancelable = true,
|
||||
}, frame._session);
|
||||
}, frame._page);
|
||||
|
||||
// Invoke window.onerror callback if set (per WHATWG spec, this is called
|
||||
// with 5 arguments: message, source, lineno, colno, error)
|
||||
@@ -530,17 +530,16 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void
|
||||
frame,
|
||||
struct {
|
||||
fn dispatch(_frame: *anyopaque) anyerror!?u32 {
|
||||
const p: *Frame = @ptrCast(@alignCast(_frame));
|
||||
const pos = &p.window._scroll_pos;
|
||||
const f: *Frame = @ptrCast(@alignCast(_frame));
|
||||
const pos = &f.window._scroll_pos;
|
||||
// If the state isn't scroll, we can ignore safely to throttle
|
||||
// the events.
|
||||
if (pos.state != .scroll) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const event = try Event.initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, p._session);
|
||||
try p._event_manager.dispatch(p.document.asEventTarget(), event);
|
||||
|
||||
const event = try Event.initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, f._page);
|
||||
try f._event_manager.dispatch(f.document.asEventTarget(), event);
|
||||
pos.state = .end;
|
||||
|
||||
return null;
|
||||
@@ -554,8 +553,8 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void
|
||||
frame,
|
||||
struct {
|
||||
fn dispatch(_frame: *anyopaque) anyerror!?u32 {
|
||||
const p: *Frame = @ptrCast(@alignCast(_frame));
|
||||
const pos = &p.window._scroll_pos;
|
||||
const f: *Frame = @ptrCast(@alignCast(_frame));
|
||||
const pos = &f.window._scroll_pos;
|
||||
// Dispatch only if the state is .end.
|
||||
// If a scroll is pending, retry in 10ms.
|
||||
// If the state is .end, the event has been dispatched, so
|
||||
@@ -565,9 +564,8 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void
|
||||
.end => {},
|
||||
.done => return null,
|
||||
}
|
||||
const event = try Event.initTrusted(comptime .wrap("scrollend"), .{ .bubbles = true }, p._session);
|
||||
try p._event_manager.dispatch(p.document.asEventTarget(), event);
|
||||
|
||||
const event = try Event.initTrusted(comptime .wrap("scrollend"), .{ .bubbles = true }, f._page);
|
||||
try f._event_manager.dispatch(f.document.asEventTarget(), event);
|
||||
pos.state = .done;
|
||||
|
||||
return null;
|
||||
@@ -622,7 +620,7 @@ pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js.
|
||||
const event = (try @import("event/PromiseRejectionEvent.zig").init(event_name, .{
|
||||
.reason = if (rejection.reason()) |r| try r.temp() else null,
|
||||
.promise = try rejection.promise().temp(),
|
||||
}, frame._session)).asEvent();
|
||||
}, frame._page)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(target, event, attribute_callback, .{ .context = "window.unhandledrejection" });
|
||||
}
|
||||
}
|
||||
@@ -811,7 +809,7 @@ const PostMessageCallback = struct {
|
||||
.source = self.source,
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, frame._session)).asEvent();
|
||||
}, frame._page)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(event_target, event, window._on_message, .{ .context = "window.postMessage" });
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ const http = @import("../../network/http.zig");
|
||||
|
||||
const URL = @import("../URL.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const HttpClient = @import("../HttpClient.zig");
|
||||
|
||||
const Blob = @import("Blob.zig");
|
||||
@@ -71,7 +70,7 @@ pub fn init(url: []const u8, exec: *Execution) !*Worker {
|
||||
errdefer session.releaseArena(arena);
|
||||
|
||||
const resolved_url = try URL.resolve(arena, exec.url.*, url, .{});
|
||||
const self = try session.factory.eventTargetWithAllocator(arena, Worker{
|
||||
const self = try frame._page.factory.eventTargetWithAllocator(arena, Worker{
|
||||
._arena = arena,
|
||||
._proto = undefined,
|
||||
._frame = frame,
|
||||
@@ -216,7 +215,6 @@ fn fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Tem
|
||||
|
||||
fn _fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Temp) !void {
|
||||
const frame = self._frame;
|
||||
const session = frame._session;
|
||||
const target = self.asEventTarget();
|
||||
const on_error = self._on_error;
|
||||
|
||||
@@ -232,7 +230,7 @@ fn _fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Te
|
||||
.filename = self._url,
|
||||
.bubbles = false,
|
||||
.cancelable = true,
|
||||
}, session);
|
||||
}, frame._page);
|
||||
|
||||
try frame._event_manager.dispatchDirect(target, error_event.asEvent(), on_error, .{
|
||||
.context = "Worker.onerror",
|
||||
@@ -354,7 +352,7 @@ const ReceiveMessageCallback = struct {
|
||||
.data = .{ .string = @errorName(err) },
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, frame._session)).asEvent();
|
||||
}, frame._page)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(target, event, on_messageerror, .{ .context = "Worker.messageerror" });
|
||||
return null;
|
||||
};
|
||||
@@ -371,7 +369,7 @@ const ReceiveMessageCallback = struct {
|
||||
.data = .{ .value = data },
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, frame._session)).asEvent();
|
||||
}, frame._page)).asEvent();
|
||||
|
||||
try frame._event_manager.dispatchDirect(target, event, on_message, .{ .context = "Worker.receiveMessage" });
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const JS = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Factory = @import("../Factory.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const EventManagerBase = @import("../EventManagerBase.zig");
|
||||
@@ -48,6 +49,7 @@ const WorkerGlobalScope = @This();
|
||||
// can access these the same for a Page of a WGS.
|
||||
// These fields represent the "Page"-like component of the WGS
|
||||
_session: *Session,
|
||||
_page: *Page,
|
||||
_factory: *Factory,
|
||||
_identity: JS.Identity = .{},
|
||||
arena: Allocator,
|
||||
@@ -83,12 +85,12 @@ _on_messageerror: ?JS.Function.Global = null,
|
||||
pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
|
||||
const arena = worker._arena;
|
||||
const parent = worker._frame;
|
||||
const session = parent._session;
|
||||
const factory = &session.factory;
|
||||
const session = worker._frame._session;
|
||||
|
||||
const call_arena = try session.getArena(.small, "WorkerGlobalScope.call_arena");
|
||||
errdefer session.releaseArena(call_arena);
|
||||
|
||||
const factory = parent._factory;
|
||||
const self = try factory.eventTargetWithAllocator(arena, WorkerGlobalScope{
|
||||
.url = url,
|
||||
.arena = arena,
|
||||
@@ -96,6 +98,7 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
|
||||
.js = undefined,
|
||||
.call_arena = call_arena,
|
||||
._session = session,
|
||||
._page = parent._page,
|
||||
._identity = .{},
|
||||
._proto = undefined,
|
||||
._factory = factory,
|
||||
@@ -115,13 +118,13 @@ pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
|
||||
|
||||
pub fn deinit(self: *WorkerGlobalScope) void {
|
||||
self._identity.deinit();
|
||||
const session = self._session;
|
||||
const page = self._page;
|
||||
var it = self._blob_urls.valueIterator();
|
||||
while (it.next()) |blob| {
|
||||
blob.*.releaseRef(session);
|
||||
blob.*.releaseRef(page);
|
||||
}
|
||||
session.browser.env.destroyContext(self.js);
|
||||
session.releaseArena(self.call_arena);
|
||||
page.session.browser.env.destroyContext(self.js);
|
||||
page.releaseArena(self.call_arena);
|
||||
}
|
||||
|
||||
pub fn base(self: *const WorkerGlobalScope) [:0]const u8 {
|
||||
@@ -148,7 +151,7 @@ pub fn dispatch(
|
||||
target,
|
||||
event,
|
||||
handler,
|
||||
self._session,
|
||||
self._page,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
@@ -282,7 +285,7 @@ pub fn unhandledPromiseRejection(self: *WorkerGlobalScope, no_handler: bool, rej
|
||||
const event = (try @import("event/PromiseRejectionEvent.zig").init(event_name, .{
|
||||
.reason = if (rejection.reason()) |r| try r.temp() else null,
|
||||
.promise = try rejection.promise().temp(),
|
||||
}, self._session)).asEvent();
|
||||
}, self._page)).asEvent();
|
||||
try self.dispatch(target, event, attribute_callback, .{});
|
||||
}
|
||||
}
|
||||
@@ -299,7 +302,7 @@ pub fn reportError(self: *WorkerGlobalScope, err: JS.Value) !void {
|
||||
.message = err.toStringSlice() catch "Unknown error",
|
||||
.bubbles = false,
|
||||
.cancelable = true,
|
||||
}, self._session);
|
||||
}, self._page);
|
||||
|
||||
// Invoke onerror callback if set (per WHATWG spec, this is called
|
||||
// with 5 arguments: message, source, lineno, colno, error)
|
||||
@@ -392,7 +395,7 @@ const ReceiveMessageCallback = struct {
|
||||
const event = (try MessageEvent.initTrusted(comptime .wrap("messageerror"), .{
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, worker_scope._session)).asEvent();
|
||||
}, worker_scope._page)).asEvent();
|
||||
try worker_scope.dispatch(target, event, on_messageerror, .{});
|
||||
return null;
|
||||
}
|
||||
@@ -409,7 +412,7 @@ const ReceiveMessageCallback = struct {
|
||||
.data = .{ .value = self.data.? },
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, worker_scope._session)).asEvent();
|
||||
}, worker_scope._page)).asEvent();
|
||||
try worker_scope.dispatch(target, event, on_message, .{});
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -64,12 +64,12 @@ pub fn init(frame: *Frame) !*Animation {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Animation, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *Animation, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Animation, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *Animation, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Animation) void {
|
||||
@@ -211,7 +211,7 @@ fn update(ctx: *anyopaque) !?u32 {
|
||||
}
|
||||
|
||||
// No future change scheduled, set the object weak for garbage collection.
|
||||
self.releaseRef(self._frame._session);
|
||||
self.releaseRef(self._frame._page);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ pub fn getContext(_: *OffscreenCanvas, context_type: []const u8, frame: *Frame)
|
||||
/// Returns a Promise that resolves to a Blob containing the image.
|
||||
/// Since we have no actual rendering, this returns an empty blob.
|
||||
pub fn convertToBlob(_: *OffscreenCanvas, frame: *Frame) !js.Promise {
|
||||
const blob = try Blob.init(null, null, frame._session);
|
||||
const blob = try Blob.init(null, null, frame._page);
|
||||
return frame.js.local.?.resolvePromise(blob);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Node = @import("../Node.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Node = @import("../Node.zig");
|
||||
const GenericIterator = @import("iterator.zig").Entry;
|
||||
|
||||
// Optimized for node.childNodes, which has to be a live list.
|
||||
@@ -55,8 +56,8 @@ pub fn init(node: *Node, frame: *Frame) !*ChildNodes {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const ChildNodes, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *const ChildNodes, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn length(self: *ChildNodes, frame: *Frame) !u32 {
|
||||
|
||||
@@ -20,8 +20,9 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Node = @import("../Node.zig");
|
||||
|
||||
const ChildNodes = @import("ChildNodes.zig");
|
||||
@@ -41,16 +42,16 @@ _data: union(enum) {
|
||||
},
|
||||
_rc: lp.RC(u32) = .{},
|
||||
|
||||
pub fn deinit(self: *NodeList, session: *Session) void {
|
||||
pub fn deinit(self: *NodeList, page: *Page) void {
|
||||
switch (self._data) {
|
||||
.child_nodes => |cn| cn.deinit(session),
|
||||
.selector_list => |list| list.deinit(session),
|
||||
.child_nodes => |cn| cn.deinit(page),
|
||||
.selector_list => |list| list.deinit(page),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *NodeList, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *NodeList, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *NodeList) void {
|
||||
@@ -115,12 +116,12 @@ const Iterator = struct {
|
||||
|
||||
const Entry = struct { u32, *Node };
|
||||
|
||||
pub fn deinit(self: *Iterator, session: *Session) void {
|
||||
self.list.deinit(session);
|
||||
pub fn deinit(self: *Iterator, page: *Page) void {
|
||||
self.list.deinit(page);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Iterator, session: *Session) void {
|
||||
self.list.releaseRef(session);
|
||||
pub fn releaseRef(self: *Iterator, page: *Page) void {
|
||||
self.list.releaseRef(page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Iterator) void {
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Execution = js.Execution;
|
||||
|
||||
pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
|
||||
@@ -48,15 +50,15 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self, session: *Session) void {
|
||||
pub fn deinit(self: *Self, page: *Page) void {
|
||||
if (@hasDecl(Inner, "releaseRef")) {
|
||||
self._inner.releaseRef(session);
|
||||
self._inner.releaseRef(page);
|
||||
}
|
||||
session.factory.destroy(self);
|
||||
page.factory.destroy(self);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Self, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *Self, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Self) void {
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -44,12 +45,12 @@ pub fn init(family: []const u8, source: []const u8, frame: *Frame) !*FontFace {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *FontFace, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *FontFace, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *FontFace, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *FontFace, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *FontFace) void {
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
const FontFace = @import("FontFace.zig");
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
|
||||
const FontFace = @import("FontFace.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
@@ -43,12 +46,12 @@ pub fn init(frame: *Frame) !*FontFaceSet {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *FontFaceSet, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *FontFaceSet, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *FontFaceSet, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *FontFaceSet, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *FontFaceSet) void {
|
||||
@@ -80,13 +83,13 @@ pub fn load(self: *FontFaceSet, font: []const u8, frame: *Frame) !js.Promise {
|
||||
// Dispatch loading event
|
||||
const target = self.asEventTarget();
|
||||
if (frame._event_manager.hasDirectListeners(target, "loading", null)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("loading"), .{}, frame._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("loading"), .{}, frame._page);
|
||||
try frame._event_manager.dispatchDirect(target, event, null, .{ .context = "load font face set" });
|
||||
}
|
||||
|
||||
// Dispatch loadingdone event
|
||||
if (frame._event_manager.hasDirectListeners(target, "loadingdone", null)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("loadingdone"), .{}, frame._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("loadingdone"), .{}, frame._page);
|
||||
try frame._event_manager.dispatchDirect(target, event, null, .{ .context = "load font face set" });
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ pub fn setOnSelectionChange(self: *Input, listener: ?js.Function) !void {
|
||||
}
|
||||
|
||||
fn dispatchSelectionChangeEvent(self: *Input, frame: *Frame) !void {
|
||||
const event = try Event.init("selectionchange", .{ .bubbles = true }, frame);
|
||||
const event = try Event.init("selectionchange", .{ .bubbles = true }, frame._page);
|
||||
try frame._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ pub fn setAutocomplete(self: *Input, autocomplete: []const u8, frame: *Frame) !v
|
||||
pub fn select(self: *Input, frame: *Frame) !void {
|
||||
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
||||
try self.setSelectionRange(0, len, null, frame);
|
||||
const event = try Event.init("select", .{ .bubbles = true }, frame);
|
||||
const event = try Event.init("select", .{ .bubbles = true }, frame._page);
|
||||
try frame._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ pub fn load(self: *Media, frame: *Frame) !void {
|
||||
}
|
||||
|
||||
fn dispatchEvent(self: *Media, name: []const u8, frame: *Frame) !void {
|
||||
const event = try Event.init(name, .{ .bubbles = false, .cancelable = false }, frame);
|
||||
const event = try Event.init(name, .{ .bubbles = false, .cancelable = false }, frame._page);
|
||||
try frame._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn setOnSelectionChange(self: *TextArea, listener: ?js.Function) !void {
|
||||
}
|
||||
|
||||
fn dispatchSelectionChangeEvent(self: *TextArea, frame: *Frame) !void {
|
||||
const event = try Event.init("selectionchange", .{ .bubbles = true }, frame);
|
||||
const event = try Event.init("selectionchange", .{ .bubbles = true }, frame._page);
|
||||
try frame._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ pub fn setRequired(self: *TextArea, required: bool, frame: *Frame) !void {
|
||||
pub fn select(self: *TextArea, frame: *Frame) !void {
|
||||
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
||||
try self.setSelectionRange(0, len, null, frame);
|
||||
const event = try Event.init("select", .{ .bubbles = true }, frame);
|
||||
const event = try Event.init("select", .{ .bubbles = true }, frame._page);
|
||||
try frame._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const html5ever = @import("../../parser/html5ever.zig");
|
||||
|
||||
const Session = @import("../../Session.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const TextDecoder = @This();
|
||||
@@ -41,7 +43,7 @@ const InitOpts = struct {
|
||||
ignoreBOM: bool = false,
|
||||
};
|
||||
|
||||
pub fn init(label_: ?[]const u8, opts_: ?InitOpts, session: *Session) !*TextDecoder {
|
||||
pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*TextDecoder {
|
||||
const label = label_ orelse "utf-8";
|
||||
|
||||
const info = html5ever.encoding_for_label(label.ptr, label.len);
|
||||
@@ -55,8 +57,8 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, session: *Session) !*TextDeco
|
||||
return error.RangeError;
|
||||
}
|
||||
|
||||
const arena = try session.getArena(.large, "TextDecoder");
|
||||
errdefer session.releaseArena(arena);
|
||||
const arena = try page.getArena(.large, "TextDecoder");
|
||||
errdefer page.releaseArena(arena);
|
||||
|
||||
const opts = opts_ orelse InitOpts{};
|
||||
const self = try arena.create(TextDecoder);
|
||||
@@ -73,15 +75,15 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, session: *Session) !*TextDeco
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *TextDecoder, session: *Session) void {
|
||||
pub fn deinit(self: *TextDecoder, page: *Page) void {
|
||||
if (self._decoder) |decoder| {
|
||||
html5ever.encoding_decoder_free(decoder);
|
||||
}
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *TextDecoder, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *TextDecoder, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *TextDecoder) void {
|
||||
|
||||
@@ -20,8 +20,9 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
const String = lp.String;
|
||||
@@ -39,13 +40,13 @@ const CustomEventOptions = struct {
|
||||
|
||||
const Options = Event.inheritOptions(CustomEvent, CustomEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?Options, frame: *Frame) !*CustomEvent {
|
||||
const arena = try frame.getArena(.tiny, "CustomEvent");
|
||||
errdefer frame.releaseArena(arena);
|
||||
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CustomEvent {
|
||||
const arena = try page.getArena(.tiny, "CustomEvent");
|
||||
errdefer page.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
|
||||
const opts = opts_ orelse Options{};
|
||||
const event = try frame._factory.event(
|
||||
const event = try page.factory.event(
|
||||
arena,
|
||||
type_string,
|
||||
CustomEvent{
|
||||
@@ -75,20 +76,21 @@ pub fn initCustomEvent(
|
||||
self._detail = detail_;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *CustomEvent, session: *Session) void {
|
||||
pub fn deinit(self: *CustomEvent, page: *Page) void {
|
||||
if (self._detail) |d| {
|
||||
d.release();
|
||||
}
|
||||
self._proto.deinit(session);
|
||||
self._proto.deinit(page);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *CustomEvent, page: *Page) void {
|
||||
self._proto._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *CustomEvent) void {
|
||||
self._proto.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *CustomEvent, session: *Session) void {
|
||||
self._proto._rc.release(self, session);
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *CustomEvent) *Event {
|
||||
return self._proto;
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
@@ -47,23 +47,23 @@ pub const ErrorEventOptions = struct {
|
||||
|
||||
const Options = Event.inheritOptions(ErrorEvent, ErrorEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?Options, session: *Session) !*ErrorEvent {
|
||||
const arena = try session.getArena(.small, "ErrorEvent");
|
||||
errdefer session.releaseArena(arena);
|
||||
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*ErrorEvent {
|
||||
const arena = try page.getArena(.small, "ErrorEvent");
|
||||
errdefer page.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
return initWithTrusted(arena, type_string, opts_, false, session);
|
||||
return initWithTrusted(arena, type_string, opts_, false, page);
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, session: *Session) !*ErrorEvent {
|
||||
const arena = try session.getArena(.small, "ErrorEvent.trusted");
|
||||
errdefer session.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, opts_, true, session);
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*ErrorEvent {
|
||||
const arena = try page.getArena(.small, "ErrorEvent.trusted");
|
||||
errdefer page.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, opts_, true, page);
|
||||
}
|
||||
|
||||
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool, session: *Session) !*ErrorEvent {
|
||||
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool, page: *Page) !*ErrorEvent {
|
||||
const opts = opts_ orelse Options{};
|
||||
|
||||
const event = try session.factory.event(
|
||||
const event = try page.factory.event(
|
||||
arena,
|
||||
typ,
|
||||
ErrorEvent{
|
||||
@@ -81,21 +81,21 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ErrorEvent, session: *Session) void {
|
||||
pub fn deinit(self: *ErrorEvent, page: *Page) void {
|
||||
if (self._error) |e| {
|
||||
e.release();
|
||||
}
|
||||
self._proto.deinit(session);
|
||||
self._proto.deinit(page);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *ErrorEvent, page: *Page) void {
|
||||
self._proto._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *ErrorEvent) void {
|
||||
self._proto.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *ErrorEvent, session: *Session) void {
|
||||
self._proto._rc.release(self, session);
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *ErrorEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
const Window = @import("../Window.zig");
|
||||
@@ -50,23 +50,23 @@ pub const Data = union(enum) {
|
||||
|
||||
const Options = Event.inheritOptions(MessageEvent, MessageEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?Options, session: *Session) !*MessageEvent {
|
||||
const arena = try session.getArena(.small, "MessageEvent");
|
||||
errdefer session.releaseArena(arena);
|
||||
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*MessageEvent {
|
||||
const arena = try page.getArena(.small, "MessageEvent");
|
||||
errdefer page.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
return initWithTrusted(arena, type_string, opts_, false, session);
|
||||
return initWithTrusted(arena, type_string, opts_, false, page);
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, session: *Session) !*MessageEvent {
|
||||
const arena = try session.getArena(.small, "MessageEvent.trusted");
|
||||
errdefer session.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, opts_, true, session);
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*MessageEvent {
|
||||
const arena = try page.getArena(.small, "MessageEvent.trusted");
|
||||
errdefer page.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, opts_, true, page);
|
||||
}
|
||||
|
||||
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool, session: *Session) !*MessageEvent {
|
||||
fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool, page: *Page) !*MessageEvent {
|
||||
const opts = opts_ orelse Options{};
|
||||
|
||||
const event = try session.factory.event(
|
||||
const event = try page.factory.event(
|
||||
arena,
|
||||
typ,
|
||||
MessageEvent{
|
||||
@@ -81,23 +81,23 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *MessageEvent, session: *Session) void {
|
||||
pub fn deinit(self: *MessageEvent, page: *Page) void {
|
||||
if (self._data) |d| {
|
||||
switch (d) {
|
||||
.value => |js_val| js_val.release(),
|
||||
.blob => |blob| blob.releaseRef(session),
|
||||
.blob => |blob| blob.releaseRef(page),
|
||||
.string, .arraybuffer => {},
|
||||
}
|
||||
}
|
||||
self._proto.deinit(session);
|
||||
self._proto.deinit(page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *MessageEvent) void {
|
||||
self._proto.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *MessageEvent, session: *Session) void {
|
||||
self._proto._rc.release(self, session);
|
||||
pub fn releaseRef(self: *MessageEvent, page: *Page) void {
|
||||
self._proto._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *MessageEvent) *Event {
|
||||
|
||||
@@ -19,7 +19,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
|
||||
@@ -38,13 +38,13 @@ const PromiseRejectionEventOptions = struct {
|
||||
|
||||
const Options = Event.inheritOptions(PromiseRejectionEvent, PromiseRejectionEventOptions);
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?Options, session: *Session) !*PromiseRejectionEvent {
|
||||
const arena = try session.getArena(.tiny, "PromiseRejectionEvent");
|
||||
errdefer session.releaseArena(arena);
|
||||
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*PromiseRejectionEvent {
|
||||
const arena = try page.getArena(.tiny, "PromiseRejectionEvent");
|
||||
errdefer page.releaseArena(arena);
|
||||
const type_string = try String.init(arena, typ, .{});
|
||||
|
||||
const opts = opts_ orelse Options{};
|
||||
const event = try session.factory.event(
|
||||
const event = try page.factory.event(
|
||||
arena,
|
||||
type_string,
|
||||
PromiseRejectionEvent{
|
||||
@@ -58,24 +58,24 @@ pub fn init(typ: []const u8, opts_: ?Options, session: *Session) !*PromiseReject
|
||||
return event;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *PromiseRejectionEvent, session: *Session) void {
|
||||
pub fn deinit(self: *PromiseRejectionEvent, page: *Page) void {
|
||||
if (self._reason) |r| {
|
||||
r.release();
|
||||
}
|
||||
if (self._promise) |p| {
|
||||
p.release();
|
||||
}
|
||||
self._proto.deinit(session);
|
||||
self._proto.deinit(page);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *PromiseRejectionEvent, page: *Page) void {
|
||||
self._proto._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *PromiseRejectionEvent) void {
|
||||
self._proto.acquireRef();
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *PromiseRejectionEvent, session: *Session) void {
|
||||
self._proto._rc.release(self, session);
|
||||
}
|
||||
|
||||
pub fn asEvent(self: *PromiseRejectionEvent) *Event {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ pub fn init(input: Input, options: ?InitOpts, frame: *Frame) !js.Promise {
|
||||
}
|
||||
|
||||
const response = try Response.init(null, .{ .status = 0 }, &frame.js.execution);
|
||||
errdefer response.deinit(frame._session);
|
||||
errdefer response.deinit(frame._page);
|
||||
|
||||
const fetch = try response._arena.create(Fetch);
|
||||
fetch.* = .{
|
||||
@@ -248,7 +248,7 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
|
||||
// clear this. (defer since `self is in the response's arena).
|
||||
|
||||
defer if (self._owns_response) {
|
||||
response.deinit(self._frame._session);
|
||||
response.deinit(self._frame._page);
|
||||
};
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
@@ -269,7 +269,7 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
|
||||
if (self._owns_response) {
|
||||
var response = self._response;
|
||||
response._http_response = null;
|
||||
response.deinit(self._frame._session);
|
||||
response.deinit(self._frame._page);
|
||||
// Do not access `self` after this point: the Fetch struct was
|
||||
// allocated from response._arena which has been released.
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ pub fn blob(self: *Request, exec: *const Execution) !js.Promise {
|
||||
const headers = try self.getHeaders(exec);
|
||||
const content_type = try headers.get("content-type", exec) orelse "";
|
||||
|
||||
const b = try Blob.initFromBytes(body, content_type, true, exec.context.session);
|
||||
const b = try Blob.initFromBytes(body, content_type, true, exec.context.page);
|
||||
|
||||
return exec.context.local.?.resolvePromise(b);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const HttpClient = @import("../../HttpClient.zig");
|
||||
|
||||
const Blob = @import("../Blob.zig");
|
||||
@@ -72,7 +72,7 @@ pub const BodyInit = union(enum) {
|
||||
};
|
||||
|
||||
pub fn init(body_: ?BodyInit, opts_: ?InitOpts, exec: *const Execution) !*Response {
|
||||
const session = exec.context.session;
|
||||
const session = exec.context.page.session;
|
||||
const arena = try session.getArena(.large, "Response");
|
||||
errdefer session.releaseArena(arena);
|
||||
|
||||
@@ -109,16 +109,16 @@ pub fn init(body_: ?BodyInit, opts_: ?InitOpts, exec: *const Execution) !*Respon
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Response, session: *Session) void {
|
||||
pub fn deinit(self: *Response, page: *Page) void {
|
||||
if (self._http_response) |resp| {
|
||||
resp.abort(error.Abort);
|
||||
self._http_response = null;
|
||||
}
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *Response, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *Response, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *Response) void {
|
||||
@@ -321,7 +321,7 @@ pub fn blob(self: *const Response, exec: *const Execution) !js.Promise {
|
||||
.stream => return local.rejectPromise(.{ .type_error = "Cannot read blob from stream body" }),
|
||||
};
|
||||
const content_type = try self._headers.get("content-type", exec) orelse "";
|
||||
const b = try Blob.initFromBytes(body, content_type, true, exec.context.session);
|
||||
const b = try Blob.initFromBytes(body, content_type, true, exec.context.page);
|
||||
return local.resolvePromise(b);
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ pub fn bytes(self: *const Response, exec: *const Execution) !js.Promise {
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Response, exec: *const Execution) !*Response {
|
||||
const session = exec.context.session;
|
||||
const session = exec.context.page.session;
|
||||
const body_len = switch (self._body) {
|
||||
.bytes => |b| b.len,
|
||||
.empty => 0,
|
||||
|
||||
@@ -24,14 +24,15 @@ const http = @import("../../../network/http.zig");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Blob = @import("../Blob.zig");
|
||||
const URL = @import("../../URL.zig");
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
const HttpClient = @import("../../HttpClient.zig");
|
||||
|
||||
const Event = @import("../Event.zig");
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
const MessageEvent = @import("../event/MessageEvent.zig");
|
||||
const CloseEvent = @import("../event/CloseEvent.zig");
|
||||
const MessageEvent = @import("../event/MessageEvent.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -160,7 +161,7 @@ pub fn init(url: []const u8, protocols: [][]const u8, frame: *Frame) !*WebSocket
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *WebSocket, session: *Session) void {
|
||||
pub fn deinit(self: *WebSocket, page: *Page) void {
|
||||
self.cleanup();
|
||||
|
||||
if (self._on_open) |func| {
|
||||
@@ -177,14 +178,14 @@ pub fn deinit(self: *WebSocket, session: *Session) void {
|
||||
}
|
||||
|
||||
for (self._send_queue.items) |msg| {
|
||||
msg.deinit(session);
|
||||
msg.deinit(page);
|
||||
}
|
||||
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *WebSocket, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *WebSocket, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *WebSocket) void {
|
||||
@@ -235,7 +236,7 @@ fn cleanup(self: *WebSocket) void {
|
||||
self._http_client.removeConn(conn);
|
||||
self._req_headers.deinit();
|
||||
self._conn = null;
|
||||
self.releaseRef(self._frame._session);
|
||||
self.releaseRef(self._frame._page);
|
||||
self._send_queue.clearRetainingCapacity();
|
||||
}
|
||||
}
|
||||
@@ -303,8 +304,8 @@ pub fn send(self: *WebSocket, data: SendData) !void {
|
||||
|
||||
switch (data) {
|
||||
.blob => |blob| {
|
||||
const arena = try self._frame._session.getArena(blob._slice.len, "WebSocket.message");
|
||||
errdefer self._frame._session.releaseArena(arena);
|
||||
const arena = try self._frame.getArena(blob._slice.len, "WebSocket.message");
|
||||
errdefer self._frame.releaseArena(arena);
|
||||
try self.queueMessage(.{ .binary = .{
|
||||
.arena = arena,
|
||||
.data = try arena.dupe(u8, blob._slice),
|
||||
@@ -312,8 +313,8 @@ pub fn send(self: *WebSocket, data: SendData) !void {
|
||||
},
|
||||
.js_val => |js_val| {
|
||||
if (js_val.isString()) |str| {
|
||||
const arena = try self._frame._session.getArena(str.len(), "WebSocket.message");
|
||||
errdefer self._frame._session.releaseArena(arena);
|
||||
const arena = try self._frame.getArena(str.len(), "WebSocket.message");
|
||||
errdefer self._frame.releaseArena(arena);
|
||||
try self.queueMessage(.{ .text = .{
|
||||
.arena = arena,
|
||||
.data = try str.toSliceWithAlloc(arena),
|
||||
@@ -322,8 +323,8 @@ pub fn send(self: *WebSocket, data: SendData) !void {
|
||||
const binary = try js_val.toZig(BinaryData);
|
||||
const buffer = binary.asBuffer();
|
||||
|
||||
const arena = try self._frame._session.getArena(buffer.len, "WebSocket.message");
|
||||
errdefer self._frame._session.releaseArena(arena);
|
||||
const arena = try self._frame.getArena(buffer.len, "WebSocket.message");
|
||||
errdefer self._frame.releaseArena(arena);
|
||||
try self.queueMessage(.{ .binary = .{
|
||||
.arena = arena,
|
||||
.data = try arena.dupe(u8, buffer),
|
||||
@@ -452,7 +453,7 @@ fn dispatchOpenEvent(self: *WebSocket) !void {
|
||||
const target = self.asEventTarget();
|
||||
|
||||
if (frame._event_manager.hasDirectListeners(target, "open", self._on_open)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("open"), .{}, frame._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("open"), .{}, frame._page);
|
||||
try frame._event_manager.dispatchDirect(target, event, self._on_open, .{ .context = "WebSocket open" });
|
||||
}
|
||||
}
|
||||
@@ -466,7 +467,7 @@ fn dispatchMessageEvent(self: *WebSocket, data: []const u8, frame_type: http.WsF
|
||||
switch (self._binary_type) {
|
||||
.arraybuffer => .{ .arraybuffer = .{ .values = data } },
|
||||
.blob => blk: {
|
||||
const blob = try Blob.initFromBytes(data, "", false, frame._session);
|
||||
const blob = try Blob.initFromBytes(data, "", false, frame._page);
|
||||
blob.acquireRef();
|
||||
break :blk .{ .blob = blob };
|
||||
},
|
||||
@@ -477,7 +478,7 @@ fn dispatchMessageEvent(self: *WebSocket, data: []const u8, frame_type: http.WsF
|
||||
const event = try MessageEvent.initTrusted(comptime .wrap("message"), .{
|
||||
.data = msg_data,
|
||||
.origin = "",
|
||||
}, frame._session);
|
||||
}, frame._page);
|
||||
try frame._event_manager.dispatchDirect(target, event.asEvent(), self._on_message, .{ .context = "WebSocket message" });
|
||||
}
|
||||
}
|
||||
@@ -487,7 +488,7 @@ fn dispatchErrorEvent(self: *WebSocket) !void {
|
||||
const target = self.asEventTarget();
|
||||
|
||||
if (frame._event_manager.hasDirectListeners(target, "error", self._on_error)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("error"), .{}, frame._session);
|
||||
const event = try Event.initTrusted(comptime .wrap("error"), .{}, frame._page);
|
||||
try frame._event_manager.dispatchDirect(target, event, self._on_error, .{ .context = "WebSocket error" });
|
||||
}
|
||||
}
|
||||
@@ -575,7 +576,7 @@ fn writeContent(self: *WebSocket, conn: *http.Connection, buf: []u8, byte_msg: M
|
||||
|
||||
if (self._send_offset >= byte_msg.data.len) {
|
||||
const removed = self._send_queue.orderedRemove(0);
|
||||
removed.deinit(self._frame._session);
|
||||
removed.deinit(self._frame._page);
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.websocket, "send complete", .{ .url = self._url, .len = byte_msg.data.len, .queue = self._send_queue.items.len });
|
||||
}
|
||||
@@ -718,9 +719,9 @@ const Message = union(enum) {
|
||||
arena: Allocator,
|
||||
data: []const u8,
|
||||
};
|
||||
fn deinit(self: Message, session: *Session) void {
|
||||
fn deinit(self: Message, page: *Page) void {
|
||||
switch (self) {
|
||||
.text, .binary => |msg| session.releaseArena(msg.arena),
|
||||
.text, .binary => |msg| page.releaseArena(msg.arena),
|
||||
.close => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ const http = @import("../../../network/http.zig");
|
||||
|
||||
const URL = @import("../../URL.zig");
|
||||
const Mime = @import("../../Mime.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Node = @import("../Node.zig");
|
||||
const Event = @import("../Event.zig");
|
||||
@@ -100,7 +100,7 @@ pub fn init(frame: *Frame) !*XMLHttpRequest {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *XMLHttpRequest, session: *Session) void {
|
||||
pub fn deinit(self: *XMLHttpRequest, page: *Page) void {
|
||||
if (self._http_response) |resp| {
|
||||
resp.abort(error.Abort);
|
||||
self._http_response = null;
|
||||
@@ -135,19 +135,19 @@ pub fn deinit(self: *XMLHttpRequest, session: *Session) void {
|
||||
}
|
||||
}
|
||||
|
||||
session.releaseArena(self._arena);
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
fn releaseSelfRef(self: *XMLHttpRequest) void {
|
||||
if (self._active_request == false) {
|
||||
return;
|
||||
}
|
||||
self.releaseRef(self._frame._session);
|
||||
self.releaseRef(self._frame._page);
|
||||
self._active_request = false;
|
||||
}
|
||||
|
||||
pub fn releaseRef(self: *XMLHttpRequest, session: *Session) void {
|
||||
self._rc.release(self, session);
|
||||
pub fn releaseRef(self: *XMLHttpRequest, page: *Page) void {
|
||||
self._rc.release(self, page);
|
||||
}
|
||||
|
||||
pub fn acquireRef(self: *XMLHttpRequest) void {
|
||||
@@ -588,7 +588,7 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, frame: *Frame) !void {
|
||||
|
||||
const target = self.asEventTarget();
|
||||
if (frame._event_manager.hasDirectListeners(target, "readystatechange", self._on_ready_state_change)) {
|
||||
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, frame._session);
|
||||
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, frame._page);
|
||||
try frame._event_manager.dispatchDirect(target, event, self._on_ready_state_change, .{ .context = "XHR state change" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Session = @import("../../Session.zig");
|
||||
|
||||
const Node = @import("../Node.zig");
|
||||
const Part = @import("Selector.zig").Part;
|
||||
@@ -41,8 +41,8 @@ pub const EntryIterator = GenericIterator(Iterator, null);
|
||||
pub const KeyIterator = GenericIterator(Iterator, "0");
|
||||
pub const ValueIterator = GenericIterator(Iterator, "1");
|
||||
|
||||
pub fn deinit(self: *const List, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
pub fn deinit(self: *const List, page: *Page) void {
|
||||
page.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn collect(
|
||||
|
||||
@@ -1366,7 +1366,7 @@ test "AXNode: writer" {
|
||||
defer registry.deinit();
|
||||
|
||||
var frame = try testing.pageTest("cdp/dom3.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
var doc = frame.window._document;
|
||||
|
||||
const node = try registry.register(doc.asNode());
|
||||
@@ -1440,7 +1440,7 @@ test "AXNode: writer prunes hidden and resolves labels" {
|
||||
defer registry.deinit();
|
||||
|
||||
var frame = try testing.pageTest("cdp/ax_tree.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
var doc = frame.window._document;
|
||||
|
||||
const node = try registry.register(doc.asNode());
|
||||
|
||||
@@ -837,7 +837,7 @@ const IsolatedWorld = struct {
|
||||
// The isolate world must share at least some of the state with the related frame, specifically the DocumentHTML
|
||||
// (assuming grantUniversalAccess will be set to True!).
|
||||
// We just created the world and the frame. The frame's state lives in the session, but is update on navigation.
|
||||
// This also means this pointer becomes invalid after removeFrame until a new frame is created.
|
||||
// This also means this pointer becomes invalid after removePage until a new frame is created.
|
||||
// Currently we have only 1 frame and thus also only 1 state in the isolate world.
|
||||
pub fn createContext(self: *IsolatedWorld, frame: *Frame) !*js.Context {
|
||||
if (self.context == null) {
|
||||
|
||||
@@ -346,7 +346,7 @@ test "cdp Node: Registry register" {
|
||||
try testing.expectEqual(0, registry.lookup_by_node.count());
|
||||
|
||||
var frame = try testing.pageTest("cdp/registry1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
var doc = frame.window._document;
|
||||
|
||||
{
|
||||
@@ -403,12 +403,12 @@ test "cdp Node: search list" {
|
||||
|
||||
{
|
||||
var frame = try testing.pageTest("cdp/registry2.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
var doc = frame.window._document;
|
||||
|
||||
{
|
||||
const l1 = try doc.querySelectorAll(.wrap("a"), frame);
|
||||
defer l1.deinit(frame._session);
|
||||
defer l1.deinit(frame._page);
|
||||
const s1 = try search_list.create(l1._nodes);
|
||||
try testing.expectEqual("1", s1.name);
|
||||
try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
|
||||
@@ -419,7 +419,7 @@ test "cdp Node: search list" {
|
||||
|
||||
{
|
||||
const l2 = try doc.querySelectorAll(.wrap("#a1"), frame);
|
||||
defer l2.deinit(frame._session);
|
||||
defer l2.deinit(frame._page);
|
||||
const s2 = try search_list.create(l2._nodes);
|
||||
try testing.expectEqual("2", s2.name);
|
||||
try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
|
||||
@@ -427,7 +427,7 @@ test "cdp Node: search list" {
|
||||
|
||||
{
|
||||
const l3 = try doc.querySelectorAll(.wrap("#a2"), frame);
|
||||
defer l3.deinit(frame._session);
|
||||
defer l3.deinit(frame._page);
|
||||
const s3 = try search_list.create(l3._nodes);
|
||||
try testing.expectEqual("3", s3.name);
|
||||
try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);
|
||||
@@ -443,7 +443,7 @@ test "cdp Node: Writer" {
|
||||
defer registry.deinit();
|
||||
|
||||
var frame = try testing.pageTest("cdp/registry3.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
defer frame._session.removePage();
|
||||
var doc = frame.window._document;
|
||||
|
||||
{
|
||||
|
||||
@@ -101,7 +101,7 @@ fn performSearch(cmd: *CDP.Command) !void {
|
||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||
const frame = bc.session.currentFrame() orelse return error.FrameNotLoaded;
|
||||
const list = try Selector.querySelectorAll(frame.window._document.asNode(), params.query, frame);
|
||||
defer list.deinit(frame._session);
|
||||
defer list.deinit(frame._page);
|
||||
|
||||
const search = try bc.node_search_list.create(list._nodes);
|
||||
|
||||
@@ -252,7 +252,7 @@ fn querySelectorAll(cmd: *CDP.Command) !void {
|
||||
};
|
||||
|
||||
const selected_nodes = try Selector.querySelectorAll(node.dom, params.selector, frame);
|
||||
defer selected_nodes.deinit(frame._session);
|
||||
defer selected_nodes.deinit(frame._page);
|
||||
|
||||
const nodes = selected_nodes._nodes;
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ test "cdp.lp: getMarkdown" {
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
_ = try bc.session.createFrame();
|
||||
_ = try bc.session.createPage();
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
@@ -315,7 +315,7 @@ test "cdp.lp: getInteractiveElements" {
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
_ = try bc.session.createFrame();
|
||||
_ = try bc.session.createPage();
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
@@ -331,7 +331,7 @@ test "cdp.lp: getStructuredData" {
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
_ = try bc.session.createFrame();
|
||||
_ = try bc.session.createPage();
|
||||
|
||||
try ctx.processMessage(.{
|
||||
.id = 1,
|
||||
@@ -347,7 +347,7 @@ test "cdp.lp: action tools" {
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
const frame = try bc.session.createFrame();
|
||||
const frame = try bc.session.createPage();
|
||||
const url = "http://localhost:9582/src/browser/tests/mcp_actions.html";
|
||||
try frame.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
||||
var runner = try bc.session.runner(.{});
|
||||
@@ -408,7 +408,7 @@ test "cdp.lp: waitForSelector" {
|
||||
defer ctx.deinit();
|
||||
|
||||
const bc = try ctx.loadBrowserContext(.{});
|
||||
const frame = try bc.session.createFrame();
|
||||
const frame = try bc.session.createPage();
|
||||
const url = "http://localhost:9582/src/browser/tests/mcp_wait_for_selector.html";
|
||||
try frame.navigate(url, .{ .reason = .address_bar, .kind = .{ .push = null } });
|
||||
var runner = try bc.session.runner(.{});
|
||||
|
||||
@@ -187,7 +187,7 @@ fn getCookies(cmd: *CDP.Command) !void {
|
||||
const params = (try cmd.params(GetCookiesParam)) orelse GetCookiesParam{};
|
||||
|
||||
// If not specified, use the URLs of the page and all of its subframes. TODO subframes
|
||||
const frame_url = if (bc.session.frame) |frame| frame.url else null;
|
||||
const frame_url = if (bc.session.currentFrame()) |frame| frame.url else null;
|
||||
const param_urls = params.urls orelse &[_][:0]const u8{frame_url orelse return error.InvalidParams};
|
||||
|
||||
var urls = try std.ArrayList(CdpStorage.PreparedUri).initCapacity(cmd.arena, param_urls.len);
|
||||
@@ -239,7 +239,7 @@ pub fn httpRequestFail(bc: *CDP.BrowserContext, msg: *const Notification.Request
|
||||
|
||||
// Isn't possible to do a network request within a Browser (which our
|
||||
// notification is tied to), without a frame.
|
||||
lp.assert(bc.session.frame != null, "CDP.network.httpRequestFail null frame", .{});
|
||||
lp.assert(bc.session.page != null, "CDP.network.httpRequestFail null frame", .{});
|
||||
|
||||
// We're missing a bunch of fields, but, for now, this seems like enough
|
||||
try bc.cdp.sendEvent("Network.loadingFailed", .{
|
||||
|
||||
@@ -212,7 +212,7 @@ fn close(cmd: *CDP.Command) !void {
|
||||
const target_id = bc.target_id orelse return error.TargetNotLoaded;
|
||||
|
||||
// can't be null if we have a target_id
|
||||
lp.assert(bc.session.frame != null, "CDP.frame.close null frame", .{});
|
||||
lp.assert(bc.session.page != null, "CDP.frame.close null frame", .{});
|
||||
|
||||
try cmd.sendResult(.{}, .{});
|
||||
|
||||
@@ -235,7 +235,7 @@ fn close(cmd: *CDP.Command) !void {
|
||||
bc.session_id = null;
|
||||
}
|
||||
|
||||
bc.session.removeFrame();
|
||||
bc.session.removePage();
|
||||
for (bc.isolated_worlds.items) |world| {
|
||||
world.deinit();
|
||||
}
|
||||
@@ -307,7 +307,7 @@ fn navigate(cmd: *CDP.Command) !void {
|
||||
isolated_world.identity.deinit();
|
||||
isolated_world.identity = .{};
|
||||
}
|
||||
frame = try session.replaceFrame();
|
||||
frame = try session.replacePage();
|
||||
}
|
||||
|
||||
const encoded_url = try URL.ensureEncoded(frame.call_arena, params.url, "UTF-8");
|
||||
@@ -333,7 +333,7 @@ fn doReload(cmd: *CDP.Command) !void {
|
||||
const session = bc.session;
|
||||
var frame = session.currentFrame() orelse return error.FrameNotLoaded;
|
||||
|
||||
// Dupe URL before replaceFrame() frees the old frame's arena.
|
||||
// Dupe URL before replacePage() frees the old frame's arena.
|
||||
const reload_url = try cmd.arena.dupeZ(u8, frame.url);
|
||||
|
||||
if (frame._load_state != .waiting) {
|
||||
@@ -343,7 +343,7 @@ fn doReload(cmd: *CDP.Command) !void {
|
||||
isolated_world.identity.deinit();
|
||||
isolated_world.identity = .{};
|
||||
}
|
||||
frame = try session.replaceFrame();
|
||||
frame = try session.replacePage();
|
||||
}
|
||||
|
||||
try frame.navigate(reload_url, .{
|
||||
|
||||
@@ -171,12 +171,12 @@ fn createTarget(cmd: *CDP.Command) !void {
|
||||
}
|
||||
|
||||
// if target_id is null, we should never have a blank frame
|
||||
lp.assert(bc.session.frame == null, "CDP.target.createTarget not null page", .{});
|
||||
lp.assert(bc.session.page == null, "CDP.target.createTarget not null page", .{});
|
||||
|
||||
// if target_id is null, we should never have a session_id
|
||||
lp.assert(bc.session_id == null, "CDP.target.createTarget not null session_id", .{});
|
||||
|
||||
const frame = try bc.session.createFrame();
|
||||
const frame = try bc.session.createPage();
|
||||
|
||||
// the target_id == the frame_id of the "root" frame
|
||||
const frame_id = id.toFrameId(frame._frame_id);
|
||||
@@ -284,7 +284,7 @@ fn closeTarget(cmd: *CDP.Command) !void {
|
||||
}
|
||||
|
||||
// can't be null if we have a target_id
|
||||
lp.assert(bc.session.frame != null, "CDP.target.closeTarget null frame", .{});
|
||||
lp.assert(bc.session.page != null, "CDP.target.closeTarget null frame", .{});
|
||||
|
||||
try cmd.sendResult(.{ .success = true }, .{ .include_session_id = false });
|
||||
|
||||
@@ -305,7 +305,7 @@ fn closeTarget(cmd: *CDP.Command) !void {
|
||||
bc.session_id = null;
|
||||
}
|
||||
|
||||
bc.session.removeFrame();
|
||||
bc.session.removePage();
|
||||
for (bc.isolated_worlds.items) |world| {
|
||||
world.deinit();
|
||||
}
|
||||
@@ -626,7 +626,7 @@ test "cdp.target: closeTarget" {
|
||||
}
|
||||
|
||||
// pretend we createdTarget first
|
||||
_ = try bc.session.createFrame();
|
||||
_ = try bc.session.createPage();
|
||||
bc.target_id = "TID-000000000A".*;
|
||||
{
|
||||
try ctx.processMessage(.{ .id = 10, .method = "Target.closeTarget", .params = .{ .targetId = "TID-8" } });
|
||||
@@ -636,7 +636,7 @@ test "cdp.target: closeTarget" {
|
||||
{
|
||||
try ctx.processMessage(.{ .id = 11, .method = "Target.closeTarget", .params = .{ .targetId = "TID-000000000A" } });
|
||||
try ctx.expectSentResult(.{ .success = true }, .{ .id = 11 });
|
||||
try testing.expectEqual(null, bc.session.frame);
|
||||
try testing.expectEqual(null, bc.session.page);
|
||||
try testing.expectEqual(null, bc.target_id);
|
||||
}
|
||||
}
|
||||
@@ -657,7 +657,7 @@ test "cdp.target: attachToTarget" {
|
||||
}
|
||||
|
||||
// pretend we createdTarget first
|
||||
_ = try bc.session.createFrame();
|
||||
_ = try bc.session.createPage();
|
||||
bc.target_id = "TID-000000000B".*;
|
||||
{
|
||||
try ctx.processMessage(.{ .id = 10, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-8" } });
|
||||
@@ -701,7 +701,7 @@ test "cdp.target: getTargetInfo" {
|
||||
}
|
||||
|
||||
// pretend we createdTarget first
|
||||
_ = try bc.session.createFrame();
|
||||
_ = try bc.session.createPage();
|
||||
bc.target_id = "TID-000000000C".*;
|
||||
{
|
||||
try ctx.processMessage(.{ .id = 10, .method = "Target.getTargetInfo", .params = .{ .targetId = "TID-8" } });
|
||||
|
||||
@@ -93,7 +93,7 @@ const TestContext = struct {
|
||||
if (bc.target_id == null) {
|
||||
bc.target_id = "TID-000000000Z".*;
|
||||
}
|
||||
const frame = try bc.session.createFrame();
|
||||
const frame = try bc.session.createPage();
|
||||
const full_url = try std.fmt.allocPrintSentinel(
|
||||
base.arena_allocator,
|
||||
"http://127.0.0.1:9582/src/browser/tests/{s}",
|
||||
@@ -204,7 +204,7 @@ const TestContext = struct {
|
||||
|
||||
if (self.cdp_) |*cdp__| {
|
||||
if (cdp__.browser_context) |*bc| {
|
||||
if (bc.session.frame != null) {
|
||||
if (bc.session.page != null) {
|
||||
var runner = try bc.session.runner(.{});
|
||||
_ = try runner.tick(.{ .ms = 1000 });
|
||||
}
|
||||
|
||||
@@ -23,12 +23,14 @@ pub const App = @import("App.zig");
|
||||
pub const Network = @import("network/Network.zig");
|
||||
pub const Server = @import("Server.zig");
|
||||
pub const Config = @import("Config.zig");
|
||||
pub const URL = @import("browser/URL.zig");
|
||||
pub const String = @import("string.zig").String;
|
||||
pub const Notification = @import("Notification.zig");
|
||||
|
||||
pub const URL = @import("browser/URL.zig");
|
||||
pub const Page = @import("browser/Page.zig");
|
||||
pub const Frame = @import("browser/Frame.zig");
|
||||
pub const Browser = @import("browser/Browser.zig");
|
||||
pub const Session = @import("browser/Session.zig");
|
||||
pub const Notification = @import("Notification.zig");
|
||||
|
||||
pub const js = @import("browser/js/js.zig");
|
||||
pub const dump = @import("browser/dump.zig");
|
||||
@@ -40,13 +42,13 @@ pub const links = @import("browser/links.zig");
|
||||
pub const forms = @import("browser/forms.zig");
|
||||
pub const actions = @import("browser/actions.zig");
|
||||
pub const structured_data = @import("browser/structured_data.zig");
|
||||
pub const HttpClient = @import("browser/HttpClient.zig");
|
||||
|
||||
pub const mcp = @import("mcp.zig");
|
||||
pub const cookies = @import("cookies.zig");
|
||||
pub const build_config = @import("build_config");
|
||||
pub const crash_handler = @import("crash_handler.zig");
|
||||
|
||||
pub const HttpClient = @import("browser/HttpClient.zig");
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
pub const FetchOpts = struct {
|
||||
@@ -80,7 +82,7 @@ pub fn fetch(app: *App, url: [:0]const u8, opts: FetchOpts) !void {
|
||||
}
|
||||
}
|
||||
|
||||
const frame = try session.createFrame();
|
||||
const frame = try session.createPage();
|
||||
|
||||
// // Comment this out to get a profile of the JS code in v8/profile.json.
|
||||
// // You can open this in Chrome's profiler.
|
||||
@@ -262,7 +264,7 @@ pub fn RC(comptime T: type) type {
|
||||
self._refs += 1;
|
||||
}
|
||||
|
||||
pub fn release(self: *@This(), value: anytype, session: *Session) void {
|
||||
pub fn release(self: *@This(), value: anytype, page: *Page) void {
|
||||
assert(self._refs > 0, "release overflow", .{ .type = @typeName(@TypeOf(value)) });
|
||||
|
||||
const refs = self._refs - 1;
|
||||
@@ -270,7 +272,7 @@ pub fn RC(comptime T: type) type {
|
||||
if (refs > 0) {
|
||||
return;
|
||||
}
|
||||
value.deinit(session);
|
||||
value.deinit(page);
|
||||
}
|
||||
|
||||
pub fn format(self: @This(), writer: *std.Io.Writer) !void {
|
||||
|
||||
@@ -94,8 +94,8 @@ pub fn main() !void {
|
||||
pub fn run(allocator: Allocator, file: []const u8, session: *lp.Session) !void {
|
||||
const url = try std.fmt.allocPrintSentinel(allocator, "http://localhost:9589/{s}", .{file}, 0);
|
||||
|
||||
const frame = try session.createFrame();
|
||||
defer session.removeFrame();
|
||||
const frame = try session.createPage();
|
||||
defer session.removePage();
|
||||
|
||||
var ls: lp.js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
|
||||
@@ -920,10 +920,10 @@ fn parseArgs(comptime T: type, arena: std.mem.Allocator, arguments: ?std.json.Va
|
||||
|
||||
fn performGoto(server: *Server, url: [:0]const u8, id: std.json.Value, timeout: ?u32, waitUntil: ?lp.Config.WaitUntil) !void {
|
||||
const session = server.session;
|
||||
if (session.frame != null) {
|
||||
session.removeFrame();
|
||||
if (session.page != null) {
|
||||
session.removePage();
|
||||
}
|
||||
const frame = session.createFrame() catch {
|
||||
const frame = session.createPage() catch {
|
||||
try server.sendError(id, .InternalError, "Failed to create page");
|
||||
return error.NavigationFailed;
|
||||
};
|
||||
@@ -988,7 +988,7 @@ test "MCP - Actions: click, fill, scroll, hover, press, selectOption, setChecked
|
||||
const server = try testLoadPage("http://localhost:9582/src/browser/tests/mcp_actions.html", &out.writer);
|
||||
defer server.deinit();
|
||||
|
||||
const frame = &server.session.frame.?;
|
||||
const frame = server.session.currentFrame().?;
|
||||
|
||||
{
|
||||
// Test Click
|
||||
@@ -1233,7 +1233,7 @@ fn testLoadPage(url: [:0]const u8, writer: *std.Io.Writer) !*Server {
|
||||
var server = try Server.init(testing.allocator, testing.test_app, writer);
|
||||
errdefer server.deinit();
|
||||
|
||||
const frame = try server.session.createFrame();
|
||||
const frame = try server.session.createPage();
|
||||
try frame.navigate(url, .{});
|
||||
|
||||
var runner = try server.session.runner(.{});
|
||||
|
||||
@@ -394,8 +394,8 @@ pub fn htmlRunner(comptime path: []const u8, opts: HtmlRunnerOpts) !void {
|
||||
}
|
||||
|
||||
fn runWebApiTest(test_file: [:0]const u8) !void {
|
||||
const frame = try test_session.createFrame();
|
||||
defer test_session.removeFrame();
|
||||
const frame = try test_session.createPage();
|
||||
defer test_session.removePage();
|
||||
|
||||
const url = try std.fmt.allocPrintSentinel(
|
||||
arena_allocator,
|
||||
@@ -453,8 +453,8 @@ const PageTestOpts = struct {
|
||||
wait_until_done: bool = true,
|
||||
};
|
||||
pub fn pageTest(comptime test_file: []const u8, opts: PageTestOpts) !*Frame {
|
||||
const frame = try test_session.createFrame();
|
||||
errdefer test_session.removeFrame();
|
||||
const frame = try test_session.createPage();
|
||||
errdefer test_session.removePage();
|
||||
|
||||
const url = try std.fmt.allocPrintSentinel(
|
||||
arena_allocator,
|
||||
|
||||
Reference in New Issue
Block a user