mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Merge branch 'main' into agent
This commit is contained in:
10
README.md
10
README.md
@@ -117,12 +117,12 @@ const browser = await puppeteer.connect({
|
||||
|
||||
// The rest of your script remains the same.
|
||||
const context = await browser.createBrowserContext();
|
||||
const page = await context.newPage();
|
||||
const frame = await context.newPage();
|
||||
|
||||
// Dump all the links from the page.
|
||||
await page.goto('https://demo-browser.lightpanda.io/amiibo/', {waitUntil: "networkidle0"});
|
||||
// Dump all the links from the frame.
|
||||
await frame.goto('https://demo-browser.lightpanda.io/amiibo/', {waitUntil: "networkidle0"});
|
||||
|
||||
const links = await page.evaluate(() => {
|
||||
const links = await frame.evaluate(() => {
|
||||
return Array.from(document.querySelectorAll('a')).map(row => {
|
||||
return row.getAttribute('href');
|
||||
});
|
||||
@@ -130,7 +130,7 @@ const links = await page.evaluate(() => {
|
||||
|
||||
console.log(links);
|
||||
|
||||
await page.close();
|
||||
await frame.close();
|
||||
await context.close();
|
||||
await browser.disconnect();
|
||||
```
|
||||
|
||||
21
build.zig
21
build.zig
@@ -93,6 +93,27 @@ pub fn build(b: *Build) !void {
|
||||
break :blk mod;
|
||||
};
|
||||
|
||||
lightpanda_module.addCSourceFile(.{
|
||||
.file = b.path("lib/sqlite3/sqlite3.c"),
|
||||
.flags = &[_][]const u8{
|
||||
"-DSQLITE_DQS=0",
|
||||
"-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1",
|
||||
"-DSQLITE_USE_ALLOCA=1",
|
||||
"-DSQLITE_THREADSAFE=1",
|
||||
"-DSQLITE_TEMP_STORE=3",
|
||||
"-DSQLITE_ENABLE_API_ARMOR=1",
|
||||
"-DSQLITE_ENABLE_UNLOCK_NOTIFY",
|
||||
"-DSQLITE_DEFAULT_FILE_PERMISSIONS=0600",
|
||||
"-DSQLITE_OMIT_DECLTYPE=1",
|
||||
"-DSQLITE_OMIT_DEPRECATED=1",
|
||||
"-DSQLITE_OMIT_LOAD_EXTENSION=1",
|
||||
"-DSQLITE_OMIT_PROGRESS_CALLBACK=1",
|
||||
"-DSQLITE_OMIT_SHARED_CACHE",
|
||||
"-DSQLITE_OMIT_TRACE=1",
|
||||
"-DSQLITE_OMIT_UTF16=1",
|
||||
},
|
||||
});
|
||||
|
||||
// Check compilation
|
||||
const check = b.step("check", "Check if lightpanda compiles");
|
||||
|
||||
|
||||
265977
lib/sqlite3/sqlite3.c
Normal file
265977
lib/sqlite3/sqlite3.c
Normal file
File diff suppressed because it is too large
Load Diff
24
src/App.zig
24
src/App.zig
@@ -24,6 +24,7 @@ const Snapshot = @import("browser/js/Snapshot.zig");
|
||||
const Platform = @import("browser/js/Platform.zig");
|
||||
const Telemetry = @import("telemetry/telemetry.zig").Telemetry;
|
||||
|
||||
const Storage = @import("storage/Storage.zig");
|
||||
const Network = @import("network/Network.zig");
|
||||
pub const ArenaPool = @import("ArenaPool.zig");
|
||||
|
||||
@@ -34,6 +35,7 @@ const App = @This();
|
||||
|
||||
network: Network,
|
||||
config: *const Config,
|
||||
storage: Storage,
|
||||
platform: Platform,
|
||||
snapshot: Snapshot,
|
||||
telemetry: Telemetry,
|
||||
@@ -42,26 +44,29 @@ arena_pool: ArenaPool,
|
||||
app_dir_path: ?[]const u8,
|
||||
|
||||
pub fn init(allocator: Allocator, config: *const Config) !*App {
|
||||
const platform = try Platform.init();
|
||||
errdefer platform.deinit();
|
||||
|
||||
const snapshot = try Snapshot.load();
|
||||
errdefer snapshot.deinit();
|
||||
|
||||
var storage = try Storage.init(allocator, config);
|
||||
errdefer storage.deinit(allocator);
|
||||
|
||||
const app = try allocator.create(App);
|
||||
errdefer allocator.destroy(app);
|
||||
|
||||
app.* = .{
|
||||
.config = config,
|
||||
.allocator = allocator,
|
||||
.platform = platform,
|
||||
.snapshot = snapshot,
|
||||
.storage = storage,
|
||||
.network = undefined,
|
||||
.platform = undefined,
|
||||
.snapshot = undefined,
|
||||
.app_dir_path = undefined,
|
||||
.telemetry = undefined,
|
||||
.arena_pool = undefined,
|
||||
};
|
||||
|
||||
app.platform = try Platform.init();
|
||||
errdefer app.platform.deinit();
|
||||
|
||||
app.snapshot = try Snapshot.load();
|
||||
errdefer app.snapshot.deinit();
|
||||
|
||||
app.network = try Network.init(allocator, app, config);
|
||||
errdefer app.network.deinit();
|
||||
|
||||
@@ -91,6 +96,7 @@ pub fn deinit(self: *App) void {
|
||||
self.snapshot.deinit();
|
||||
self.platform.deinit();
|
||||
self.arena_pool.deinit();
|
||||
self.storage.deinit(allocator);
|
||||
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ const builtin = @import("builtin");
|
||||
const dump = @import("browser/dump.zig");
|
||||
|
||||
const mcp = @import("mcp.zig");
|
||||
const Storage = @import("storage/Storage.zig");
|
||||
const WebBotAuthConfig = @import("network/WebBotAuth.zig").Config;
|
||||
|
||||
const log = lp.log;
|
||||
@@ -220,6 +221,20 @@ pub fn maxPendingConnections(self: *const Config) u31 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn storageEngine(self: *const Config) ?Storage.EngineType {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp => |opts| opts.common.storage_engine,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn storageSqlitePath(self: *const Config) ?[:0]const u8 {
|
||||
return switch (self.mode) {
|
||||
inline .serve, .fetch, .mcp => |opts| opts.common.storage_sqlite_path,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Mode = union(RunMode) {
|
||||
help: bool, // false when being printed because of an error
|
||||
fetch: Fetch,
|
||||
@@ -314,6 +329,8 @@ pub const Common = struct {
|
||||
http_cache_dir: ?[]const u8 = null,
|
||||
cookie: ?[]const u8 = null,
|
||||
cookie_jar: ?[]const u8 = null,
|
||||
storage_engine: ?Storage.EngineType = null,
|
||||
storage_sqlite_path: ?[:0]const u8 = null,
|
||||
|
||||
web_bot_auth_key_file: ?[]const u8 = null,
|
||||
web_bot_auth_keyid: ?[]const u8 = null,
|
||||
@@ -469,6 +486,14 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void {
|
||||
\\ Path to a directory to use as a Filesystem Cache for network resources.
|
||||
\\ Omitting this will result is no caching.
|
||||
\\ Defaults to no caching.
|
||||
\\
|
||||
\\--storage-engine
|
||||
\\ The storage engine to use. Choices are: sqlite.
|
||||
\\ Default to sqlite.
|
||||
\\
|
||||
\\--storage-sqlite-path
|
||||
\\ Path to SQLite database file for persistent storage.
|
||||
\\ Use ":memory:" for in-memory storage.
|
||||
;
|
||||
|
||||
// MAX_HELP_LEN|
|
||||
@@ -1345,6 +1370,27 @@ fn parseCommonArg(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, "--storage-engine", opt)) {
|
||||
const str = args.next() orelse {
|
||||
log.fatal(.app, "missing argument value", .{ .arg = "--storage-engine" });
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
common.storage_engine = std.meta.stringToEnum(Storage.EngineType, str) orelse {
|
||||
log.fatal(.app, "invalid argument value", .{ .arg = opt, .val = str });
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, "--storage-sqlite-path", opt)) {
|
||||
const str = args.next() orelse {
|
||||
log.fatal(.app, "missing argument value", .{ .arg = "--storage-sqlite-path" });
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
common.storage_sqlite_path = try allocator.dupeZ(u8, str);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, "--block-private-networks", opt)) {
|
||||
common.block_private_networks = true;
|
||||
return true;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const Page = @import("browser/Page.zig");
|
||||
const Frame = @import("browser/Frame.zig");
|
||||
const Transfer = @import("browser/HttpClient.zig").Transfer;
|
||||
|
||||
const log = lp.log;
|
||||
@@ -66,15 +66,15 @@ allocator: Allocator,
|
||||
mem_pool: std.heap.MemoryPool(Listener),
|
||||
|
||||
const EventListeners = struct {
|
||||
page_remove: List = .{},
|
||||
page_created: List = .{},
|
||||
page_navigate: List = .{},
|
||||
page_navigated: List = .{},
|
||||
page_network_idle: List = .{},
|
||||
page_network_almost_idle: List = .{},
|
||||
page_frame_created: List = .{},
|
||||
page_dom_content_loaded: List = .{},
|
||||
page_loaded: List = .{},
|
||||
frame_remove: List = .{},
|
||||
frame_created: List = .{},
|
||||
frame_navigate: List = .{},
|
||||
frame_navigated: List = .{},
|
||||
frame_network_idle: List = .{},
|
||||
frame_network_almost_idle: List = .{},
|
||||
frame_child_frame_created: List = .{},
|
||||
frame_dom_content_loaded: List = .{},
|
||||
frame_loaded: List = .{},
|
||||
http_request_fail: List = .{},
|
||||
http_request_start: List = .{},
|
||||
http_request_intercept: List = .{},
|
||||
@@ -86,15 +86,15 @@ const EventListeners = struct {
|
||||
};
|
||||
|
||||
const Events = union(enum) {
|
||||
page_remove: PageRemove,
|
||||
page_created: *Page,
|
||||
page_navigate: *const PageNavigate,
|
||||
page_navigated: *const PageNavigated,
|
||||
page_network_idle: *const PageNetworkIdle,
|
||||
page_network_almost_idle: *const PageNetworkAlmostIdle,
|
||||
page_frame_created: *const PageFrameCreated,
|
||||
page_dom_content_loaded: *const PageDOMContentLoaded,
|
||||
page_loaded: *const PageLoaded,
|
||||
frame_remove: FrameRemove,
|
||||
frame_created: *Frame,
|
||||
frame_navigate: *const FrameNavigate,
|
||||
frame_navigated: *const FrameNavigated,
|
||||
frame_network_idle: *const FrameNetworkIdle,
|
||||
frame_network_almost_idle: *const FrameNetworkAlmostIdle,
|
||||
frame_child_frame_created: *const FrameChildFrameCreated,
|
||||
frame_dom_content_loaded: *const FrameDOMContentLoaded,
|
||||
frame_loaded: *const FrameLoaded,
|
||||
http_request_fail: *const RequestFail,
|
||||
http_request_start: *const RequestStart,
|
||||
http_request_intercept: *const RequestIntercept,
|
||||
@@ -106,58 +106,58 @@ const Events = union(enum) {
|
||||
};
|
||||
const EventType = std.meta.FieldEnum(Events);
|
||||
|
||||
pub const PageRemove = struct {};
|
||||
pub const FrameRemove = struct {};
|
||||
|
||||
pub const PageNavigate = struct {
|
||||
pub const FrameNavigate = struct {
|
||||
req_id: u32,
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
timestamp: u64,
|
||||
url: [:0]const u8,
|
||||
opts: Page.NavigateOpts,
|
||||
opts: Frame.NavigateOpts,
|
||||
};
|
||||
|
||||
pub const PageNavigated = struct {
|
||||
pub const FrameNavigated = struct {
|
||||
req_id: u32,
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
timestamp: u64,
|
||||
url: [:0]const u8,
|
||||
opts: Page.NavigatedOpts,
|
||||
opts: Frame.NavigatedOpts,
|
||||
};
|
||||
|
||||
pub const PageNetworkIdle = struct {
|
||||
pub const FrameNetworkIdle = struct {
|
||||
req_id: u32,
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
timestamp: u64,
|
||||
};
|
||||
|
||||
pub const PageNetworkAlmostIdle = struct {
|
||||
pub const FrameNetworkAlmostIdle = struct {
|
||||
req_id: u32,
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
timestamp: u64,
|
||||
};
|
||||
|
||||
pub const PageFrameCreated = struct {
|
||||
page_id: u32,
|
||||
pub const FrameChildFrameCreated = struct {
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
parent_id: u32,
|
||||
timestamp: u64,
|
||||
};
|
||||
|
||||
pub const PageDOMContentLoaded = struct {
|
||||
pub const FrameDOMContentLoaded = struct {
|
||||
req_id: u32,
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
timestamp: u64,
|
||||
};
|
||||
|
||||
pub const PageLoaded = struct {
|
||||
pub const FrameLoaded = struct {
|
||||
req_id: u32,
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
timestamp: u64,
|
||||
};
|
||||
|
||||
@@ -348,8 +348,8 @@ test "Notification" {
|
||||
defer notifier.deinit();
|
||||
|
||||
// noop
|
||||
notifier.dispatch(.page_navigate, &.{
|
||||
.page_id = 39,
|
||||
notifier.dispatch(.frame_navigate, &.{
|
||||
.loader_id = 39,
|
||||
.frame_id = 0,
|
||||
.req_id = 1,
|
||||
.timestamp = 4,
|
||||
@@ -359,96 +359,96 @@ test "Notification" {
|
||||
|
||||
var tc = TestClient{};
|
||||
|
||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||
notifier.dispatch(.page_navigate, &.{
|
||||
.page_id = 39,
|
||||
try notifier.register(.frame_navigate, &tc, TestClient.frameNavigate);
|
||||
notifier.dispatch(.frame_navigate, &.{
|
||||
.loader_id = 39,
|
||||
.frame_id = 0,
|
||||
.req_id = 1,
|
||||
.timestamp = 4,
|
||||
.url = undefined,
|
||||
.opts = .{},
|
||||
});
|
||||
try testing.expectEqual(4, tc.page_navigate);
|
||||
try testing.expectEqual(4, tc.frame_navigate);
|
||||
|
||||
notifier.unregisterAll(&tc);
|
||||
notifier.dispatch(.page_navigate, &.{
|
||||
.page_id = 39,
|
||||
notifier.dispatch(.frame_navigate, &.{
|
||||
.loader_id = 39,
|
||||
.frame_id = 0,
|
||||
.req_id = 1,
|
||||
.timestamp = 10,
|
||||
.url = undefined,
|
||||
.opts = .{},
|
||||
});
|
||||
try testing.expectEqual(4, tc.page_navigate);
|
||||
try testing.expectEqual(4, tc.frame_navigate);
|
||||
|
||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
||||
notifier.dispatch(.page_navigate, &.{
|
||||
.page_id = 39,
|
||||
try notifier.register(.frame_navigate, &tc, TestClient.frameNavigate);
|
||||
try notifier.register(.frame_navigated, &tc, TestClient.frameNavigated);
|
||||
notifier.dispatch(.frame_navigate, &.{
|
||||
.loader_id = 39,
|
||||
.frame_id = 0,
|
||||
.req_id = 1,
|
||||
.timestamp = 10,
|
||||
.url = undefined,
|
||||
.opts = .{},
|
||||
});
|
||||
notifier.dispatch(.page_navigated, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(14, tc.page_navigate);
|
||||
try testing.expectEqual(6, tc.page_navigated);
|
||||
notifier.dispatch(.frame_navigated, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(14, tc.frame_navigate);
|
||||
try testing.expectEqual(6, tc.frame_navigated);
|
||||
|
||||
notifier.unregisterAll(&tc);
|
||||
notifier.dispatch(.page_navigate, &.{
|
||||
.page_id = 39,
|
||||
notifier.dispatch(.frame_navigate, &.{
|
||||
.loader_id = 39,
|
||||
.frame_id = 0,
|
||||
.req_id = 1,
|
||||
.timestamp = 100,
|
||||
.url = undefined,
|
||||
.opts = .{},
|
||||
});
|
||||
notifier.dispatch(.page_navigated, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(14, tc.page_navigate);
|
||||
try testing.expectEqual(6, tc.page_navigated);
|
||||
notifier.dispatch(.frame_navigated, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(14, tc.frame_navigate);
|
||||
try testing.expectEqual(6, tc.frame_navigated);
|
||||
|
||||
{
|
||||
// unregister
|
||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
||||
notifier.dispatch(.page_navigate, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.page_navigated, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.page_navigate);
|
||||
try testing.expectEqual(1006, tc.page_navigated);
|
||||
try notifier.register(.frame_navigate, &tc, TestClient.frameNavigate);
|
||||
try notifier.register(.frame_navigated, &tc, TestClient.frameNavigated);
|
||||
notifier.dispatch(.frame_navigate, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.frame_navigated, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.frame_navigate);
|
||||
try testing.expectEqual(1006, tc.frame_navigated);
|
||||
|
||||
notifier.unregister(.page_navigate, &tc);
|
||||
notifier.dispatch(.page_navigate, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.page_navigated, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.page_navigate);
|
||||
try testing.expectEqual(2006, tc.page_navigated);
|
||||
notifier.unregister(.frame_navigate, &tc);
|
||||
notifier.dispatch(.frame_navigate, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.frame_navigated, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.frame_navigate);
|
||||
try testing.expectEqual(2006, tc.frame_navigated);
|
||||
|
||||
notifier.unregister(.page_navigated, &tc);
|
||||
notifier.dispatch(.page_navigate, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.page_navigated, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.page_navigate);
|
||||
try testing.expectEqual(2006, tc.page_navigated);
|
||||
notifier.unregister(.frame_navigated, &tc);
|
||||
notifier.dispatch(.frame_navigate, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.frame_navigated, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.frame_navigate);
|
||||
try testing.expectEqual(2006, tc.frame_navigated);
|
||||
|
||||
// already unregistered, try anyways
|
||||
notifier.unregister(.page_navigated, &tc);
|
||||
notifier.dispatch(.page_navigate, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.page_navigated, &.{ .page_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.page_navigate);
|
||||
try testing.expectEqual(2006, tc.page_navigated);
|
||||
notifier.unregister(.frame_navigated, &tc);
|
||||
notifier.dispatch(.frame_navigate, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||
notifier.dispatch(.frame_navigated, &.{ .loader_id = 39, .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||
try testing.expectEqual(114, tc.frame_navigate);
|
||||
try testing.expectEqual(2006, tc.frame_navigated);
|
||||
}
|
||||
}
|
||||
|
||||
const TestClient = struct {
|
||||
page_navigate: u64 = 0,
|
||||
page_navigated: u64 = 0,
|
||||
frame_navigate: u64 = 0,
|
||||
frame_navigated: u64 = 0,
|
||||
|
||||
fn pageNavigate(ptr: *anyopaque, data: *const Notification.PageNavigate) !void {
|
||||
fn frameNavigate(ptr: *anyopaque, data: *const Notification.FrameNavigate) !void {
|
||||
const self: *TestClient = @ptrCast(@alignCast(ptr));
|
||||
self.page_navigate += data.timestamp;
|
||||
self.frame_navigate += data.timestamp;
|
||||
}
|
||||
|
||||
fn pageNavigated(ptr: *anyopaque, data: *const Notification.PageNavigated) !void {
|
||||
fn frameNavigated(ptr: *anyopaque, data: *const Notification.FrameNavigated) !void {
|
||||
const self: *TestClient = @ptrCast(@alignCast(ptr));
|
||||
self.page_navigated += data.timestamp;
|
||||
self.frame_navigated += data.timestamp;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,13 +29,13 @@ const AXNode = @import("cdp/AXNode.zig");
|
||||
const CDPNode = @import("cdp/Node.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Page = lp.Page;
|
||||
const Frame = lp.Frame;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
dom_node: *Node,
|
||||
registry: *CDPNode.Registry,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
arena: std.mem.Allocator,
|
||||
prune: bool = true,
|
||||
interactive_only: bool = false,
|
||||
@@ -44,7 +44,7 @@ max_depth: u32 = std.math.maxInt(u32) - 1,
|
||||
pub fn jsonStringify(self: @This(), jw: *std.json.Stringify) error{WriteFailed}!void {
|
||||
var visitor = JsonVisitor{ .jw = jw, .tree = self };
|
||||
var xpath_buffer: std.ArrayList(u8) = .{};
|
||||
const listener_targets = interactive.buildListenerTargetMap(self.page, self.arena) catch |err| {
|
||||
const listener_targets = interactive.buildListenerTargetMap(self.frame, self.arena) catch |err| {
|
||||
log.err(.app, "listener map failed", .{ .err = err });
|
||||
return error.WriteFailed;
|
||||
};
|
||||
@@ -65,7 +65,7 @@ pub fn jsonStringify(self: @This(), jw: *std.json.Stringify) error{WriteFailed}!
|
||||
pub fn textStringify(self: @This(), writer: *std.Io.Writer) error{WriteFailed}!void {
|
||||
var visitor = TextVisitor{ .writer = writer, .tree = self, .depth = 0 };
|
||||
var xpath_buffer: std.ArrayList(u8) = .empty;
|
||||
const listener_targets = interactive.buildListenerTargetMap(self.page, self.arena) catch |err| {
|
||||
const listener_targets = interactive.buildListenerTargetMap(self.frame, self.arena) catch |err| {
|
||||
log.err(.app, "listener map failed", .{ .err = err });
|
||||
return error.WriteFailed;
|
||||
};
|
||||
@@ -130,7 +130,7 @@ fn walk(
|
||||
if (tag == .datalist or tag == .option or tag == .optgroup) return;
|
||||
|
||||
// Check visibility using the engine's checkVisibility which handles CSS display: none
|
||||
if (!el.checkVisibilityCached(ctx.visibility_cache, self.page)) {
|
||||
if (!el.checkVisibilityCached(ctx.visibility_cache, self.frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,17 +166,17 @@ fn walk(
|
||||
checked = input.getChecked();
|
||||
}
|
||||
if (el.getAttributeSafe(comptime .wrap("list"))) |list_id| {
|
||||
options = try extractDataListOptions(list_id, self.page, self.arena);
|
||||
options = try extractDataListOptions(list_id, self.frame, self.arena);
|
||||
}
|
||||
} else if (el.is(Element.Html.TextArea)) |textarea| {
|
||||
value = textarea.getValue();
|
||||
} else if (el.is(Element.Html.Select)) |select| {
|
||||
value = select.getValue(self.page);
|
||||
options = try extractSelectOptions(el.asNode(), self.page, self.arena);
|
||||
value = select.getValue(self.frame);
|
||||
options = try extractSelectOptions(el.asNode(), self.frame, self.arena);
|
||||
}
|
||||
|
||||
if (el.is(Element.Html)) |html_el| {
|
||||
if (interactive.classifyInteractivity(self.page, el, html_el, ctx.listener_targets, ctx.pointer_events_cache) != null) {
|
||||
if (interactive.classifyInteractivity(self.frame, el, html_el, ctx.listener_targets, ctx.pointer_events_cache) != null) {
|
||||
is_interactive = true;
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ fn walk(
|
||||
try appendXPathSegment(node, ctx.xpath_buffer.writer(self.arena), index);
|
||||
const xpath = ctx.xpath_buffer.items;
|
||||
|
||||
var name = try axn.getName(self.page, self.arena);
|
||||
var name = try axn.getName(self.frame, self.arena);
|
||||
|
||||
const has_explicit_label = if (node.is(Element)) |el|
|
||||
el.getAttributeSafe(.wrap("aria-label")) != null or el.getAttributeSafe(.wrap("title")) != null
|
||||
@@ -287,15 +287,15 @@ fn walk(
|
||||
ctx.xpath_buffer.shrinkRetainingCapacity(initial_xpath_len);
|
||||
}
|
||||
|
||||
fn extractSelectOptions(node: *Node, page: *Page, arena: std.mem.Allocator) ![]OptionData {
|
||||
fn extractSelectOptions(node: *Node, frame: *Frame, arena: std.mem.Allocator) ![]OptionData {
|
||||
var options: std.ArrayList(OptionData) = .empty;
|
||||
var it = node.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
if (child.is(Element)) |el| {
|
||||
if (el.getTag() == .option) {
|
||||
if (el.is(Element.Html.Option)) |opt| {
|
||||
const text = opt.getText(page);
|
||||
const value = opt.getValue(page);
|
||||
const text = opt.getText(frame);
|
||||
const value = opt.getValue(frame);
|
||||
const selected = opt.getSelected();
|
||||
try options.append(arena, .{ .text = text, .value = value, .selected = selected });
|
||||
}
|
||||
@@ -303,8 +303,8 @@ fn extractSelectOptions(node: *Node, page: *Page, arena: std.mem.Allocator) ![]O
|
||||
var group_it = child.childrenIterator();
|
||||
while (group_it.next()) |group_child| {
|
||||
if (group_child.is(Element.Html.Option)) |opt| {
|
||||
const text = opt.getText(page);
|
||||
const value = opt.getValue(page);
|
||||
const text = opt.getText(frame);
|
||||
const value = opt.getValue(frame);
|
||||
const selected = opt.getSelected();
|
||||
try options.append(arena, .{ .text = text, .value = value, .selected = selected });
|
||||
}
|
||||
@@ -315,10 +315,10 @@ fn extractSelectOptions(node: *Node, page: *Page, arena: std.mem.Allocator) ![]O
|
||||
return options.toOwnedSlice(arena);
|
||||
}
|
||||
|
||||
fn extractDataListOptions(list_id: []const u8, page: *Page, arena: std.mem.Allocator) !?[]OptionData {
|
||||
if (page.document.getElementById(list_id, page)) |referenced_el| {
|
||||
fn extractDataListOptions(list_id: []const u8, frame: *Frame, arena: std.mem.Allocator) !?[]OptionData {
|
||||
if (frame.document.getElementById(list_id, frame)) |referenced_el| {
|
||||
if (referenced_el.getTag() == .datalist) {
|
||||
return try extractSelectOptions(referenced_el.asNode(), page, arena);
|
||||
return try extractSelectOptions(referenced_el.asNode(), frame, arena);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -644,12 +644,12 @@ pub fn getNodeDetails(
|
||||
arena: std.mem.Allocator,
|
||||
node: *Node,
|
||||
registry: *CDPNode.Registry,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !NodeDetails {
|
||||
const cdp_node = try registry.register(node);
|
||||
const axn = AXNode.fromNode(node);
|
||||
const role = try axn.getRole();
|
||||
const name = try axn.getName(page, arena);
|
||||
const name = try axn.getName(frame, arena);
|
||||
|
||||
var is_interactive = false;
|
||||
var is_disabled = false;
|
||||
@@ -672,7 +672,7 @@ pub fn getNodeDetails(
|
||||
|
||||
if (el.getAttributeSafe(comptime .wrap("href"))) |h| {
|
||||
const URL = lp.URL;
|
||||
href = URL.resolve(arena, page.base(), h, .{ .encoding = page.charset }) catch h;
|
||||
href = URL.resolve(arena, frame.base(), h, .{ .encoding = frame.charset }) catch h;
|
||||
}
|
||||
|
||||
if (el.is(Element.Html.Input)) |input| {
|
||||
@@ -682,19 +682,19 @@ pub fn getNodeDetails(
|
||||
checked = input.getChecked();
|
||||
}
|
||||
if (el.getAttributeSafe(comptime .wrap("list"))) |list_id| {
|
||||
options = try extractDataListOptions(list_id, page, arena);
|
||||
options = try extractDataListOptions(list_id, frame, arena);
|
||||
}
|
||||
} else if (el.is(Element.Html.TextArea)) |textarea| {
|
||||
value = textarea.getValue();
|
||||
} else if (el.is(Element.Html.Select)) |select| {
|
||||
value = select.getValue(page);
|
||||
options = try extractSelectOptions(el.asNode(), page, arena);
|
||||
value = select.getValue(frame);
|
||||
options = try extractSelectOptions(el.asNode(), frame, arena);
|
||||
}
|
||||
|
||||
if (el.is(Element.Html)) |html_el| {
|
||||
const listener_targets = try interactive.buildListenerTargetMap(page, arena);
|
||||
const listener_targets = try interactive.buildListenerTargetMap(frame, arena);
|
||||
var pointer_events_cache: Element.PointerEventsCache = .empty;
|
||||
if (interactive.classifyInteractivity(page, el, html_el, listener_targets, &pointer_events_cache) != null) {
|
||||
if (interactive.classifyInteractivity(frame, el, html_el, listener_targets, &pointer_events_cache) != null) {
|
||||
is_interactive = true;
|
||||
}
|
||||
}
|
||||
@@ -724,14 +724,14 @@ test "SemanticTree backendDOMNodeId" {
|
||||
var registry: CDPNode.Registry = .init(testing.allocator);
|
||||
defer registry.deinit();
|
||||
|
||||
var page = try testing.pageTest("cdp/registry1.html", .{});
|
||||
var frame = try testing.pageTest("cdp/registry1.html", .{});
|
||||
defer testing.reset();
|
||||
defer page._session.removePage();
|
||||
defer frame._session.removeFrame();
|
||||
|
||||
const st: Self = .{
|
||||
.dom_node = page.window._document.asNode(),
|
||||
.dom_node = frame.window._document.asNode(),
|
||||
.registry = ®istry,
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.arena = testing.arena_allocator,
|
||||
.prune = false,
|
||||
.interactive_only = false,
|
||||
@@ -748,14 +748,14 @@ test "SemanticTree max_depth" {
|
||||
var registry: CDPNode.Registry = .init(testing.allocator);
|
||||
defer registry.deinit();
|
||||
|
||||
var page = try testing.pageTest("cdp/registry1.html", .{});
|
||||
var frame = try testing.pageTest("cdp/registry1.html", .{});
|
||||
defer testing.reset();
|
||||
defer page._session.removePage();
|
||||
defer frame._session.removeFrame();
|
||||
|
||||
const st: Self = .{
|
||||
.dom_node = page.window._document.asNode(),
|
||||
.dom_node = frame.window._document.asNode(),
|
||||
.registry = ®istry,
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.arena = testing.arena_allocator,
|
||||
.prune = false,
|
||||
.interactive_only = false,
|
||||
|
||||
@@ -77,7 +77,7 @@ pub fn getTools(self: *Self) ![]const zenai.provider.Tool {
|
||||
}
|
||||
|
||||
pub fn getCurrentUrl(self: *Self) []const u8 {
|
||||
const page = self.session.currentPage() orelse return "(no page loaded)";
|
||||
const page = self.session.currentFrame() orelse return "(no page loaded)";
|
||||
return page.url;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const EventManagerBase = @import("EventManagerBase.zig");
|
||||
|
||||
const Node = @import("webapi/Node.zig");
|
||||
@@ -42,7 +42,7 @@ const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
pub const EventManager = @This();
|
||||
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
base: EventManagerBase,
|
||||
|
||||
// Used as an optimization in Page._documentIsComplete. If we know there are no
|
||||
@@ -52,9 +52,9 @@ has_dom_load_listener: bool,
|
||||
|
||||
ignore_list: std.ArrayList(*Listener),
|
||||
|
||||
pub fn init(arena: Allocator, page: *Page) EventManager {
|
||||
pub fn init(arena: Allocator, frame: *Frame) EventManager {
|
||||
return .{
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.ignore_list = .{},
|
||||
.has_dom_load_listener = false,
|
||||
.base = EventManagerBase.init(arena),
|
||||
@@ -104,10 +104,10 @@ 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.page._session);
|
||||
defer _ = event.releaseRef(self.frame._session);
|
||||
|
||||
// Increment event count for Event Timing API
|
||||
self.page.window._performance._event_counts.increment(event._type_string.str());
|
||||
self.frame.window._performance._event_counts.increment(event._type_string.str());
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
|
||||
@@ -130,15 +130,15 @@ const DispatchDirectOptions = EventManagerBase.DispatchDirectOptions;
|
||||
// property handlers. No propagation - just calls the handler and registered listeners.
|
||||
// Handler can be: null, ?js.Function.Global, ?js.Function.Temp, or js.Function
|
||||
pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, handler: anytype, comptime opts: DispatchDirectOptions) !void {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
// Set window.event to the currently dispatching event (WHATWG spec)
|
||||
const window = page.window;
|
||||
const window = frame.window;
|
||||
const prev_event = window._current_event;
|
||||
window._current_event = event;
|
||||
defer window._current_event = prev_event;
|
||||
|
||||
try self.base.dispatchDirect(page.call_arena, page.js, target, event, handler, page._session, opts);
|
||||
try self.base.dispatchDirect(frame.call_arena, frame.js, target, event, handler, frame._session, opts);
|
||||
}
|
||||
|
||||
/// Check if there are any listeners for a direct dispatch (non-DOM target).
|
||||
@@ -156,10 +156,10 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
event._dispatch_target = et; // Store original target for composedPath()
|
||||
}
|
||||
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
// Set window.event to the currently dispatching event (WHATWG spec)
|
||||
const window = page.window;
|
||||
const window = frame.window;
|
||||
const prev_event = window._current_event;
|
||||
window._current_event = event;
|
||||
defer window._current_event = prev_event;
|
||||
@@ -170,7 +170,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
// This ensures function handles passed to queueMicrotask remain valid
|
||||
// throughout the entire dispatch, preventing crashes when microtasks run.
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer {
|
||||
if (was_handled) {
|
||||
ls.local.runMicrotasks();
|
||||
@@ -178,7 +178,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
ls.deinit();
|
||||
}
|
||||
|
||||
const activation_state = try ActivationState.create(event, target, page);
|
||||
const activation_state = try ActivationState.create(event, target, frame);
|
||||
|
||||
// Defer runs even on early return - ensures event phase is reset
|
||||
// and default actions execute (unless prevented)
|
||||
@@ -188,19 +188,19 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
event._stop_immediate_propagation = false;
|
||||
// Handle checkbox/radio activation rollback or commit
|
||||
if (activation_state) |state| {
|
||||
state.restore(event, page);
|
||||
state.restore(event, frame);
|
||||
}
|
||||
|
||||
// Execute default action if not prevented
|
||||
if (event._prevent_default) {
|
||||
// can't return in a defer (╯°□°)╯︵ ┻━┻
|
||||
} else if (event._type_string.eql(comptime .wrap("click"))) {
|
||||
page.handleClick(target) catch |err| {
|
||||
log.warn(.event, "page.click", .{ .err = err });
|
||||
frame.handleClick(target) catch |err| {
|
||||
log.warn(.event, "frame.click", .{ .err = err });
|
||||
};
|
||||
} else if (event._type_string.eql(comptime .wrap("keydown"))) {
|
||||
page.handleKeydown(target, event) catch |err| {
|
||||
log.warn(.event, "page.keydown", .{ .err = err });
|
||||
frame.handleKeydown(target, event) catch |err| {
|
||||
log.warn(.event, "frame.keydown", .{ .err = err });
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
|
||||
// The only explicit exception is "load"
|
||||
if (event._type_string.eql(comptime .wrap("load")) == false) {
|
||||
if (path_len < path_buffer.len) {
|
||||
path_buffer[path_len] = page.window.asEventTarget();
|
||||
path_buffer[path_len] = frame.window.asEventTarget();
|
||||
path_len += 1;
|
||||
}
|
||||
}
|
||||
@@ -312,7 +312,7 @@ const DispatchPhaseOpts = struct {
|
||||
};
|
||||
|
||||
fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool, local: *const js.Local, comptime opts: DispatchPhaseOpts) !void {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
const base = &self.base;
|
||||
|
||||
// Track dispatch depth for deferred removal
|
||||
@@ -392,7 +392,7 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
||||
switch (listener.function) {
|
||||
.value => |value| try local.toLocal(value).callWithThis(void, current_target, .{event}),
|
||||
.string => |string| {
|
||||
const str = try page.call_arena.dupeZ(u8, string.str());
|
||||
const str = try frame.call_arena.dupeZ(u8, string.str());
|
||||
try local.eval(str, null);
|
||||
},
|
||||
.object => |obj_global| {
|
||||
@@ -424,7 +424,7 @@ fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?j
|
||||
else => return null,
|
||||
};
|
||||
|
||||
return html_element.getAttributeFunction(handler_type, self.page) catch |err| {
|
||||
return html_element.getAttributeFunction(handler_type, self.frame) catch |err| {
|
||||
log.warn(.event, "inline html callback", .{ .type = handler_type, .err = err });
|
||||
return null;
|
||||
};
|
||||
@@ -502,7 +502,7 @@ const ActivationState = struct {
|
||||
|
||||
const Input = Element.Html.Input;
|
||||
|
||||
fn create(event: *const Event, target: *Node, page: *Page) !?ActivationState {
|
||||
fn create(event: *const Event, target: *Node, frame: *Frame) !?ActivationState {
|
||||
if (event._type_string.eql(comptime .wrap("click")) == false) {
|
||||
return null;
|
||||
}
|
||||
@@ -517,12 +517,12 @@ const ActivationState = struct {
|
||||
|
||||
// For radio buttons, find the currently checked radio in the group
|
||||
if (input._input_type == .radio and !old_checked) {
|
||||
previously_checked_radio = try findCheckedRadioInGroup(input, page);
|
||||
previously_checked_radio = try findCheckedRadioInGroup(input, frame);
|
||||
}
|
||||
|
||||
// Toggle checkbox or check radio (which unchecks others in group)
|
||||
const new_checked = if (input._input_type == .checkbox) !old_checked else true;
|
||||
try input.setChecked(new_checked, page);
|
||||
try input.setChecked(new_checked, frame);
|
||||
|
||||
return .{
|
||||
.input = input,
|
||||
@@ -531,7 +531,7 @@ const ActivationState = struct {
|
||||
};
|
||||
}
|
||||
|
||||
fn restore(self: *const ActivationState, event: *const Event, page: *Page) void {
|
||||
fn restore(self: *const ActivationState, event: *const Event, frame: *Frame) void {
|
||||
const input = self.input;
|
||||
if (event._prevent_default) {
|
||||
// Rollback: restore previous state
|
||||
@@ -549,16 +549,16 @@ const ActivationState = struct {
|
||||
// For checkboxes, state always changes. For radios, only if was unchecked.
|
||||
const state_changed = (input._input_type == .checkbox) or !self.old_checked;
|
||||
if (state_changed and input.asElement().asNode().isConnected()) {
|
||||
fireEvent(page, input, "input") catch |err| {
|
||||
fireEvent(frame, input, "input") catch |err| {
|
||||
log.warn(.event, "input event", .{ .err = err });
|
||||
};
|
||||
fireEvent(page, input, "change") catch |err| {
|
||||
fireEvent(frame, input, "change") catch |err| {
|
||||
log.warn(.event, "change event", .{ .err = err });
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn findCheckedRadioInGroup(input: *Input, page: *Page) !?*Input {
|
||||
fn findCheckedRadioInGroup(input: *Input, frame: *Frame) !?*Input {
|
||||
const elem = input.asElement();
|
||||
|
||||
const name = elem.getAttributeSafe(comptime .wrap("name")) orelse return null;
|
||||
@@ -566,7 +566,7 @@ const ActivationState = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
const form = input.getForm(page);
|
||||
const form = input.getForm(frame);
|
||||
|
||||
// Walk from the root of the tree containing this element
|
||||
// This handles both document-attached and orphaned elements
|
||||
@@ -594,7 +594,7 @@ const ActivationState = struct {
|
||||
}
|
||||
|
||||
// Check if same form context
|
||||
const other_form = other_input.getForm(page);
|
||||
const other_form = other_input.getForm(frame);
|
||||
if (form) |f| {
|
||||
const of = other_form orelse continue;
|
||||
if (f != of) {
|
||||
@@ -613,13 +613,13 @@ const ActivationState = struct {
|
||||
}
|
||||
|
||||
// Fire input or change event
|
||||
fn fireEvent(page: *Page, input: *Input, comptime typ: []const u8) !void {
|
||||
fn fireEvent(frame: *Frame, input: *Input, comptime typ: []const u8) !void {
|
||||
const event = try Event.initTrusted(comptime .wrap(typ), .{
|
||||
.bubbles = true,
|
||||
.cancelable = false,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
const target = input.asElement().asEventTarget();
|
||||
try page._event_manager.dispatch(target, event);
|
||||
try frame._event_manager.dispatch(target, event);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ const reflect = @import("reflect.zig");
|
||||
|
||||
const SlabAllocator = @import("../slab.zig").SlabAllocator;
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const Node = @import("webapi/Node.zig");
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const UIEvent = @import("webapi/event/UIEvent.zig");
|
||||
@@ -266,23 +266,23 @@ pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child
|
||||
return chain.get(1);
|
||||
}
|
||||
|
||||
pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, page: *Page) !*@TypeOf(child) {
|
||||
pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, frame: *Frame) !*@TypeOf(child) {
|
||||
const chain = try PrototypeChain(&.{ AbstractRange, @TypeOf(child) }).allocate(arena);
|
||||
|
||||
const doc = page.document.asNode();
|
||||
const doc = frame.document.asNode();
|
||||
const abstract_range = chain.get(0);
|
||||
abstract_range.* = AbstractRange{
|
||||
._rc = .{},
|
||||
._arena = arena,
|
||||
._page_id = page.id,
|
||||
._type = unionInit(AbstractRange.Type, chain.get(1)),
|
||||
._end_offset = 0,
|
||||
._start_offset = 0,
|
||||
._end_container = doc,
|
||||
._start_container = doc,
|
||||
._frame_loader_id = frame._loader_id,
|
||||
._type = unionInit(AbstractRange.Type, chain.get(1)),
|
||||
};
|
||||
chain.setLeaf(1, child);
|
||||
page._live_ranges.append(&abstract_range._range_link);
|
||||
frame._live_ranges.append(&abstract_range._range_link);
|
||||
return chain.get(1);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,13 +17,9 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const posix = std.posix;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
const log = lp.log;
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const URL = @import("URL.zig");
|
||||
const Config = @import("../Config.zig");
|
||||
const Notification = @import("../Notification.zig");
|
||||
@@ -34,26 +30,31 @@ const http = @import("../network/http.zig");
|
||||
const Network = @import("../network/Network.zig");
|
||||
const Robots = @import("../network/Robots.zig");
|
||||
const Cache = @import("../network/cache/Cache.zig");
|
||||
const CacheMetadata = Cache.CachedMetadata;
|
||||
const CachedResponse = Cache.CachedResponse;
|
||||
const timestamp = @import("../datetime.zig").timestamp;
|
||||
|
||||
const log = lp.log;
|
||||
const posix = std.posix;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
pub const Method = http.Method;
|
||||
pub const Headers = http.Headers;
|
||||
pub const ResponseHead = http.ResponseHead;
|
||||
pub const HeaderIterator = http.HeaderIterator;
|
||||
const CacheMetadata = Cache.CachedMetadata;
|
||||
const CachedResponse = Cache.CachedResponse;
|
||||
|
||||
// This is loosely tied to a browser Page. Loading all the <scripts>, doing
|
||||
// XHR requests, and loading imports all happens through here. Sine the app
|
||||
// currently supports 1 browser and 1 page at-a-time, we only have 1 Client and
|
||||
// re-use it from page to page. This allows us better re-use of the various
|
||||
// currently supports 1 browser and 1 frame at-a-time, we only have 1 Client and
|
||||
// re-use it from frame to frame. This allows us better re-use of the various
|
||||
// buffers/caches (including keepalive connections) that libcurl has.
|
||||
//
|
||||
// The app has other secondary http needs, like telemetry. While we want to
|
||||
// share some things (namely the ca blob, and maybe some configuration
|
||||
// (TODO: ??? should proxy settings be global ???)), we're able to do call
|
||||
// client.abort() to abort the transfers being made by a page, without impacting
|
||||
// client.abort() to abort the transfers being made by a frame, without impacting
|
||||
// those other http requests.
|
||||
pub const Client = @This();
|
||||
|
||||
@@ -328,7 +329,7 @@ fn abortConnections(list: std.DoublyLinkedList, comptime abort_all: bool, frame_
|
||||
}
|
||||
},
|
||||
.websocket => |ws| {
|
||||
if ((comptime abort_all) or ws._page._frame_id == frame_id) {
|
||||
if ((comptime abort_all) or ws._frame._frame_id == frame_id) {
|
||||
ws.kill();
|
||||
}
|
||||
},
|
||||
@@ -516,8 +517,8 @@ fn fetchRobotsThenProcessRequest(self: *Client, robots_url: [:0]const u8, req: R
|
||||
.method = .GET,
|
||||
.headers = headers,
|
||||
.blocking = false,
|
||||
.page_id = req.page_id,
|
||||
.frame_id = req.frame_id,
|
||||
.loader_id = req.loader_id,
|
||||
.cookie_jar = req.cookie_jar,
|
||||
.cookie_origin = req.cookie_origin,
|
||||
.notification = req.notification,
|
||||
@@ -769,6 +770,7 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
|
||||
|
||||
const id = self.incrReqId();
|
||||
transfer.* = .{
|
||||
.start_time = timestamp(.monotonic),
|
||||
.arena = ArenaAllocator.init(self.allocator),
|
||||
.id = id,
|
||||
.url = req.url,
|
||||
@@ -1155,8 +1157,8 @@ fn ensureNoActiveConnection(self: *const Client) !void {
|
||||
}
|
||||
|
||||
pub const Request = struct {
|
||||
page_id: u32,
|
||||
frame_id: u32,
|
||||
loader_id: u32,
|
||||
method: Method,
|
||||
url: [:0]const u8,
|
||||
headers: http.Headers,
|
||||
@@ -1292,6 +1294,7 @@ pub const Transfer = struct {
|
||||
bytes_received: usize = 0,
|
||||
_pending_cache_metadata: ?*CacheMetadata = null,
|
||||
|
||||
start_time: u64,
|
||||
aborted: bool = false,
|
||||
|
||||
// We'll store the response header here
|
||||
@@ -1373,7 +1376,7 @@ pub const Transfer = struct {
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
// internal, when the page is shutting down. Doesn't have the same ceremony
|
||||
// internal, when the frame is shutting down. Doesn't have the same ceremony
|
||||
// as abort (doesn't send a notification, doesn't invoke an error callback)
|
||||
fn kill(self: *Transfer) void {
|
||||
if (self.req.shutdown_callback) |cb| {
|
||||
|
||||
@@ -21,7 +21,7 @@ const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const Session = @import("Session.zig");
|
||||
const HttpClient = @import("HttpClient.zig");
|
||||
|
||||
@@ -33,17 +33,17 @@ const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
const Runner = @This();
|
||||
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
session: *Session,
|
||||
http_client: *HttpClient,
|
||||
|
||||
pub const Opts = struct {};
|
||||
|
||||
pub fn init(session: *Session, _: Opts) !Runner {
|
||||
const page = &(session.page orelse return error.NoPage);
|
||||
const frame = &(session.frame orelse return error.NoPage);
|
||||
|
||||
return .{
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.session = session,
|
||||
.http_client = session.browser.http_client,
|
||||
};
|
||||
@@ -78,7 +78,7 @@ fn _wait(self: *Runner, comptime is_cdp: bool, opts: WaitOpts) !CDPWaitResult {
|
||||
error.JsError => {}, // already logged (with hopefully more context)
|
||||
else => log.err(.browser, "session wait", .{
|
||||
.err = err,
|
||||
.url = self.page.url,
|
||||
.url = self.frame.url,
|
||||
}),
|
||||
}
|
||||
return err;
|
||||
@@ -127,12 +127,12 @@ pub fn tickCDP(self: *Runner, opts: TickOpts) !CDPTickResult {
|
||||
}
|
||||
|
||||
fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
const http_client = self.http_client;
|
||||
|
||||
switch (page._parse_state) {
|
||||
switch (frame._parse_state) {
|
||||
.pre, .raw, .text, .image => {
|
||||
// The main page hasn't started/finished navigating.
|
||||
// The main frame hasn't started/finished navigating.
|
||||
// There's no JS to run, and no reason to run the scheduler.
|
||||
if (http_client.http_active == 0 and (comptime is_cdp) == false) {
|
||||
// haven't started navigating, I guess.
|
||||
@@ -152,7 +152,7 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
const session = self.session;
|
||||
if (session.queued_navigation.items.len != 0) {
|
||||
try session.processQueuedNavigation();
|
||||
self.page = &session.page.?; // might have changed
|
||||
self.frame = &session.frame.?; // might have changed
|
||||
return .{ .ok = 0 };
|
||||
}
|
||||
const browser = session.browser;
|
||||
@@ -166,26 +166,26 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
try browser.runMacrotasks();
|
||||
|
||||
// Each call to this runs scheduled load events.
|
||||
try page.dispatchLoad();
|
||||
try frame.dispatchLoad();
|
||||
|
||||
const http_active = http_client.http_active;
|
||||
const total_network_activity = http_active + http_client.intercepted;
|
||||
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||
page.notifyNetworkAlmostIdle();
|
||||
if (frame._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||
frame.notifyNetworkAlmostIdle();
|
||||
}
|
||||
if (page._notified_network_idle.check(total_network_activity == 0)) {
|
||||
page.notifyNetworkIdle();
|
||||
if (frame._notified_network_idle.check(total_network_activity == 0)) {
|
||||
frame.notifyNetworkIdle();
|
||||
}
|
||||
|
||||
switch (opts.until) {
|
||||
.done => {},
|
||||
.domcontentloaded => if (page._load_state == .load or page._load_state == .complete) {
|
||||
.domcontentloaded => if (frame._load_state == .load or frame._load_state == .complete) {
|
||||
return .done;
|
||||
},
|
||||
.load => if (page._load_state == .complete) {
|
||||
.load => if (frame._load_state == .complete) {
|
||||
return .done;
|
||||
},
|
||||
.networkidle => if (page._notified_network_idle == .done) {
|
||||
.networkidle => if (frame._notified_network_idle == .done) {
|
||||
return .done;
|
||||
},
|
||||
}
|
||||
@@ -231,7 +231,7 @@ fn _tick(self: *Runner, comptime is_cdp: bool, opts: TickOpts) !CDPTickResult {
|
||||
return .{ .ok = 0 };
|
||||
},
|
||||
.err => |err| {
|
||||
page._parse_state = .{ .raw_done = @errorName(err) };
|
||||
frame._parse_state = .{ .raw_done = @errorName(err) };
|
||||
return err;
|
||||
},
|
||||
.raw_done => {
|
||||
@@ -255,9 +255,9 @@ pub fn waitForSelector(self: *Runner, selector: [:0]const u8, timeout_ms: u32) !
|
||||
const parsed_selector = try Selector.parseLeaky(arena, selector);
|
||||
|
||||
while (true) {
|
||||
// self.page can change between ticks
|
||||
const page = self.page;
|
||||
if (try parsed_selector.query(page.document.asNode(), page)) |el| {
|
||||
// self.frame can change between ticks
|
||||
const frame = self.frame;
|
||||
if (try parsed_selector.query(frame.document.asNode(), frame)) |el| {
|
||||
return el;
|
||||
}
|
||||
|
||||
@@ -280,11 +280,11 @@ pub fn waitForScript(runner: *Runner, script: [:0]const u8, timeout_ms: u32) !vo
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
while (true) {
|
||||
const page = runner.page;
|
||||
const frame = runner.frame;
|
||||
|
||||
// Execute the script and check if it returns truthy
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
@@ -292,7 +292,7 @@ pub fn waitForScript(runner: *Runner, script: [:0]const u8, timeout_ms: u32) !vo
|
||||
defer try_catch.deinit();
|
||||
|
||||
const value = ls.local.exec(script, "wait_script") catch |err| {
|
||||
const caught = try_catch.caughtOrError(page.call_arena, err);
|
||||
const caught = try_catch.caughtOrError(frame.call_arena, err);
|
||||
log.err(.app, "wait script error", .{ .err = caught });
|
||||
return error.ScriptError;
|
||||
};
|
||||
@@ -322,35 +322,35 @@ test "Runner: no page" {
|
||||
}
|
||||
|
||||
test "Runner: waitForSelector timeout" {
|
||||
const page = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer page._session.removePage();
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
|
||||
var runner = try page._session.runner(.{});
|
||||
var runner = try frame._session.runner(.{});
|
||||
try testing.expectError(error.Timeout, runner.waitForSelector("#nope", 10));
|
||||
}
|
||||
|
||||
test "Runner: waitForSelector" {
|
||||
defer testing.reset();
|
||||
const page = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer page._session.removePage();
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
|
||||
var runner = try page._session.runner(.{});
|
||||
var runner = try frame._session.runner(.{});
|
||||
const el = try runner.waitForSelector("#sel1", 10);
|
||||
try testing.expectEqual("selector-1-content", try el.asNode().getTextContentAlloc(testing.arena_allocator));
|
||||
}
|
||||
|
||||
test "Runner: waitForScript timeout" {
|
||||
const page = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer page._session.removePage();
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
|
||||
var runner = try page._session.runner(.{});
|
||||
var runner = try frame._session.runner(.{});
|
||||
try testing.expectError(error.Timeout, runner.waitForScript("document.querySelector('#nope')", 10));
|
||||
}
|
||||
|
||||
test "Runner: waitForScript" {
|
||||
const page = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer page._session.removePage();
|
||||
const frame = try testing.pageTest("runner/runner1.html", .{});
|
||||
defer frame._session.removeFrame();
|
||||
|
||||
var runner = try page._session.runner(.{});
|
||||
var runner = try frame._session.runner(.{});
|
||||
try runner.waitForScript("document.querySelector('#sel1')", 10);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ const http = @import("../network/http.zig");
|
||||
|
||||
const js = @import("js/js.zig");
|
||||
const URL = @import("URL.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
|
||||
const Element = @import("webapi/Element.zig");
|
||||
|
||||
@@ -36,7 +36,7 @@ const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
const ScriptManager = @This();
|
||||
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
|
||||
// used to prevent recursive evaluation
|
||||
is_evaluating: bool,
|
||||
@@ -79,13 +79,13 @@ imported_modules: std.StringHashMapUnmanaged(ImportedModule),
|
||||
// importmap contains resolved urls.
|
||||
importmap: std.StringHashMapUnmanaged([:0]const u8),
|
||||
|
||||
// have we notified the page that all scripts are loaded (used to fire the "load"
|
||||
// have we notified the frame that all scripts are loaded (used to fire the "load"
|
||||
// event).
|
||||
page_notified_of_completion: bool,
|
||||
frame_notified_of_completion: bool,
|
||||
|
||||
pub fn init(allocator: Allocator, http_client: *HttpClient, page: *Page) ScriptManager {
|
||||
pub fn init(allocator: Allocator, http_client: *HttpClient, frame: *Frame) ScriptManager {
|
||||
return .{
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.async_scripts = .{},
|
||||
.defer_scripts = .{},
|
||||
.ready_scripts = .{},
|
||||
@@ -95,7 +95,7 @@ pub fn init(allocator: Allocator, http_client: *HttpClient, page: *Page) ScriptM
|
||||
.imported_modules = .empty,
|
||||
.client = http_client,
|
||||
.static_scripts_done = false,
|
||||
.page_notified_of_completion = false,
|
||||
.frame_notified_of_completion = false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ pub fn deinit(self: *ScriptManager) void {
|
||||
self.reset();
|
||||
|
||||
self.imported_modules.deinit(self.allocator);
|
||||
// we don't deinit self.importmap b/c we use the page's arena for its
|
||||
// we don't deinit self.importmap b/c we use the frame's arena for its
|
||||
// allocations.
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ pub fn reset(self: *ScriptManager) void {
|
||||
}
|
||||
self.imported_modules.clearRetainingCapacity();
|
||||
|
||||
// Our allocator is the page arena, it's been reset. We cannot use
|
||||
// Our allocator is the frame arena, it's been reset. We cannot use
|
||||
// clearAndRetainCapacity, since that space is no longer ours
|
||||
self.importmap = .empty;
|
||||
|
||||
@@ -137,7 +137,7 @@ fn clearList(list: *std.DoublyLinkedList) void {
|
||||
|
||||
fn getHeaders(self: *ScriptManager) !http.Headers {
|
||||
var headers = try self.client.newHeaders();
|
||||
try self.page.headersForRequest(&headers);
|
||||
try self.frame.headersForRequest(&headers);
|
||||
return headers;
|
||||
}
|
||||
|
||||
@@ -185,16 +185,16 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
||||
};
|
||||
|
||||
var handover = false;
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
const arena = try page.getArena(.large, "SM.addFromElement");
|
||||
const arena = try frame.getArena(.large, "SM.addFromElement");
|
||||
errdefer if (!handover) {
|
||||
page.releaseArena(arena);
|
||||
frame.releaseArena(arena);
|
||||
};
|
||||
|
||||
var source: Script.Source = undefined;
|
||||
var remote_url: ?[:0]const u8 = null;
|
||||
const base_url = page.base();
|
||||
const base_url = frame.base();
|
||||
if (element.getAttributeSafe(comptime .wrap("src"))) |src| {
|
||||
if (try parseDataURI(arena, src)) |data_uri| {
|
||||
source = .{ .@"inline" = data_uri };
|
||||
@@ -211,7 +211,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
||||
if (inline_source.len == 0) {
|
||||
// we haven't set script_element._executed = true yet, which is good.
|
||||
// If content is appended to the script, we will execute it then.
|
||||
page.releaseArena(arena);
|
||||
frame.releaseArena(arena);
|
||||
return;
|
||||
}
|
||||
source = .{ .@"inline" = inline_source };
|
||||
@@ -274,7 +274,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
log.debug(.http, "script queue", .{
|
||||
@@ -294,14 +294,14 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
||||
.url = url,
|
||||
.ctx = script,
|
||||
.method = .GET,
|
||||
.page_id = page.id,
|
||||
.frame_id = page._frame_id,
|
||||
.frame_id = frame._frame_id,
|
||||
.loader_id = frame._loader_id,
|
||||
.headers = try self.getHeaders(),
|
||||
.blocking = is_blocking,
|
||||
.cookie_jar = &page._session.cookie_jar,
|
||||
.cookie_origin = page.url,
|
||||
.cookie_jar = &frame._session.cookie_jar,
|
||||
.cookie_origin = frame.url,
|
||||
.resource_type = .script,
|
||||
.notification = page._session.notification,
|
||||
.notification = frame._session.notification,
|
||||
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
||||
.header_callback = Script.headerCallback,
|
||||
.data_callback = Script.dataCallback,
|
||||
@@ -338,7 +338,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
||||
self.is_evaluating = was_evaluating;
|
||||
script.deinit();
|
||||
}
|
||||
return script.eval(page);
|
||||
return script.eval(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,9 +368,9 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
||||
}
|
||||
errdefer _ = self.imported_modules.remove(url);
|
||||
|
||||
const page = self.page;
|
||||
const arena = try page.getArena(.large, "SM.preloadImport");
|
||||
errdefer page.releaseArena(arena);
|
||||
const frame = self.frame;
|
||||
const arena = try frame.getArena(.large, "SM.preloadImport");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const script = try arena.create(Script);
|
||||
script.* = .{
|
||||
@@ -389,7 +389,7 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
log.debug(.http, "script queue", .{
|
||||
@@ -410,13 +410,13 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
||||
.url = url,
|
||||
.ctx = script,
|
||||
.method = .GET,
|
||||
.page_id = page.id,
|
||||
.frame_id = page._frame_id,
|
||||
.frame_id = frame._frame_id,
|
||||
.loader_id = frame._loader_id,
|
||||
.headers = try self.getHeaders(),
|
||||
.cookie_jar = &page._session.cookie_jar,
|
||||
.cookie_origin = page.url,
|
||||
.cookie_jar = &frame._session.cookie_jar,
|
||||
.cookie_origin = frame.url,
|
||||
.resource_type = .script,
|
||||
.notification = page._session.notification,
|
||||
.notification = frame._session.notification,
|
||||
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
||||
.header_callback = Script.headerCallback,
|
||||
.data_callback = Script.dataCallback,
|
||||
@@ -469,9 +469,9 @@ pub fn waitForImport(self: *ScriptManager, url: [:0]const u8) !ModuleSource {
|
||||
}
|
||||
|
||||
pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.Callback, cb_data: *anyopaque, referrer: []const u8) !void {
|
||||
const page = self.page;
|
||||
const arena = try page.getArena(.large, "SM.getAsyncImport");
|
||||
errdefer page.releaseArena(arena);
|
||||
const frame = self.frame;
|
||||
const arena = try frame.getArena(.large, "SM.getAsyncImport");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const script = try arena.create(Script);
|
||||
script.* = .{
|
||||
@@ -491,7 +491,7 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
log.debug(.http, "script queue", .{
|
||||
@@ -515,14 +515,14 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
||||
self.client.request(.{
|
||||
.url = url,
|
||||
.method = .GET,
|
||||
.page_id = page.id,
|
||||
.frame_id = page._frame_id,
|
||||
.frame_id = frame._frame_id,
|
||||
.loader_id = frame._loader_id,
|
||||
.headers = try self.getHeaders(),
|
||||
.ctx = script,
|
||||
.resource_type = .script,
|
||||
.cookie_jar = &page._session.cookie_jar,
|
||||
.cookie_origin = page.url,
|
||||
.notification = page._session.notification,
|
||||
.cookie_jar = &frame._session.cookie_jar,
|
||||
.cookie_origin = frame.url,
|
||||
.notification = frame._session.notification,
|
||||
.start_callback = if (log.enabled(.http, .debug)) Script.startCallback else null,
|
||||
.header_callback = Script.headerCallback,
|
||||
.data_callback = Script.dataCallback,
|
||||
@@ -548,7 +548,7 @@ fn evaluate(self: *ScriptManager) void {
|
||||
return;
|
||||
}
|
||||
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
self.is_evaluating = true;
|
||||
defer self.is_evaluating = false;
|
||||
|
||||
@@ -557,7 +557,7 @@ fn evaluate(self: *ScriptManager) void {
|
||||
switch (script.mode) {
|
||||
.async => {
|
||||
defer script.deinit();
|
||||
script.eval(page);
|
||||
script.eval(frame);
|
||||
},
|
||||
.import_async => |ia| {
|
||||
if (script.status < 200 or script.status > 299) {
|
||||
@@ -595,21 +595,21 @@ fn evaluate(self: *ScriptManager) void {
|
||||
_ = self.defer_scripts.popFirst();
|
||||
script.deinit();
|
||||
}
|
||||
script.eval(page);
|
||||
script.eval(frame);
|
||||
}
|
||||
|
||||
// At this point all normal scripts and deferred scripts are done, PLUS
|
||||
// the page has signaled that it's done parsing HTML (static_scripts_done == true).
|
||||
// the frame has signaled that it's done parsing HTML (static_scripts_done == true).
|
||||
//
|
||||
|
||||
// When all scripts (normal and deferred) are done loading, the document
|
||||
// state changes (this ultimately triggers the DOMContentLoaded event).
|
||||
// Page makes this safe to call multiple times.
|
||||
page.documentIsLoaded();
|
||||
frame.documentIsLoaded();
|
||||
|
||||
if (self.async_scripts.first == null and self.page_notified_of_completion == false) {
|
||||
self.page_notified_of_completion = true;
|
||||
page.scriptsCompletedLoading();
|
||||
if (self.async_scripts.first == null and self.frame_notified_of_completion == false) {
|
||||
self.frame_notified_of_completion = true;
|
||||
frame.scriptsCompletedLoading();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ fn parseImportmap(self: *ScriptManager, script: *const Script) !void {
|
||||
|
||||
const imports = try std.json.parseFromSliceLeaky(
|
||||
Imports,
|
||||
self.page.arena,
|
||||
self.frame.arena,
|
||||
content,
|
||||
.{ .allocate = .alloc_always },
|
||||
);
|
||||
@@ -633,13 +633,13 @@ fn parseImportmap(self: *ScriptManager, script: *const Script) !void {
|
||||
// > base URL of the document containing the import map.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#importing_modules_using_import_maps
|
||||
const resolved_url = try URL.resolve(
|
||||
self.page.arena,
|
||||
self.page.base(),
|
||||
self.frame.arena,
|
||||
self.frame.base(),
|
||||
entry.value_ptr.*,
|
||||
.{},
|
||||
);
|
||||
|
||||
try self.importmap.put(self.page.arena, entry.key_ptr.*, resolved_url);
|
||||
try self.importmap.put(self.frame.arena, entry.key_ptr.*, resolved_url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,7 +700,7 @@ pub const Script = struct {
|
||||
};
|
||||
|
||||
fn deinit(self: *Script) void {
|
||||
self.manager.page.releaseArena(self.arena);
|
||||
self.manager.frame.releaseArena(self.arena);
|
||||
}
|
||||
|
||||
fn startCallback(response: HttpClient.Response) !void {
|
||||
@@ -843,7 +843,7 @@ pub const Script = struct {
|
||||
manager.evaluate();
|
||||
}
|
||||
|
||||
fn eval(self: *Script, page: *Page) void {
|
||||
fn eval(self: *Script, frame: *Frame) void {
|
||||
// never evaluated, source is passed back to v8, via callbacks.
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(self.mode != .import_async);
|
||||
@@ -852,21 +852,21 @@ pub const Script = struct {
|
||||
std.debug.assert(self.mode != .import);
|
||||
}
|
||||
|
||||
if (page.isGoingAway()) {
|
||||
// don't evaluate scripts for a dying page.
|
||||
if (frame.isGoingAway()) {
|
||||
// don't evaluate scripts for a dying frame.
|
||||
return;
|
||||
}
|
||||
|
||||
const script_element = self.script_element.?;
|
||||
|
||||
const previous_script = page.document._current_script;
|
||||
page.document._current_script = script_element;
|
||||
defer page.document._current_script = previous_script;
|
||||
const previous_script = frame.document._current_script;
|
||||
frame.document._current_script = script_element;
|
||||
defer frame.document._current_script = previous_script;
|
||||
|
||||
// Clear the document.write insertion point for this script
|
||||
const previous_write_insertion_point = page.document._write_insertion_point;
|
||||
page.document._write_insertion_point = null;
|
||||
defer page.document._write_insertion_point = previous_write_insertion_point;
|
||||
const previous_write_insertion_point = frame.document._write_insertion_point;
|
||||
frame.document._write_insertion_point = null;
|
||||
defer frame.document._write_insertion_point = previous_write_insertion_point;
|
||||
|
||||
// inline scripts aren't cached. remote ones are.
|
||||
const cacheable = self.source == .remote;
|
||||
@@ -880,7 +880,7 @@ pub const Script = struct {
|
||||
});
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const local = &ls.local;
|
||||
@@ -888,21 +888,21 @@ pub const Script = struct {
|
||||
// Handle importmap special case here: the content is a JSON containing
|
||||
// imports.
|
||||
if (self.kind == .importmap) {
|
||||
page._script_manager.parseImportmap(self) catch |err| {
|
||||
frame._script_manager.parseImportmap(self) catch |err| {
|
||||
log.err(.browser, "parse importmap script", .{
|
||||
.err = err,
|
||||
.src = url,
|
||||
.kind = self.kind,
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
self.executeCallback(comptime .wrap("error"), page);
|
||||
self.executeCallback(comptime .wrap("error"), frame);
|
||||
return;
|
||||
};
|
||||
self.executeCallback(comptime .wrap("load"), page);
|
||||
self.executeCallback(comptime .wrap("load"), frame);
|
||||
return;
|
||||
}
|
||||
|
||||
defer page._event_manager.clearIgnoreList();
|
||||
defer frame._event_manager.clearIgnoreList();
|
||||
|
||||
var try_catch: js.TryCatch = undefined;
|
||||
try_catch.init(local);
|
||||
@@ -914,7 +914,7 @@ pub const Script = struct {
|
||||
.javascript => _ = local.eval(content, url) catch break :blk false,
|
||||
.module => {
|
||||
// We don't care about waiting for the evaluation here.
|
||||
page.js.module(false, local, content, url, cacheable) catch break :blk false;
|
||||
frame.js.module(false, local, content, url, cacheable) catch break :blk false;
|
||||
},
|
||||
.importmap => unreachable, // handled before the try/catch.
|
||||
}
|
||||
@@ -927,29 +927,29 @@ pub const Script = struct {
|
||||
|
||||
defer {
|
||||
local.runMacrotasks(); // also runs microtasks
|
||||
_ = page.js.scheduler.run() catch |err| {
|
||||
log.err(.page, "scheduler", .{ .err = err });
|
||||
_ = frame.js.scheduler.run() catch |err| {
|
||||
log.err(.frame, "scheduler", .{ .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
if (success) {
|
||||
self.executeCallback(comptime .wrap("load"), page);
|
||||
self.executeCallback(comptime .wrap("load"), frame);
|
||||
return;
|
||||
}
|
||||
|
||||
const caught = try_catch.caughtOrError(page.call_arena, error.Unknown);
|
||||
const caught = try_catch.caughtOrError(frame.call_arena, error.Unknown);
|
||||
log.warn(.js, "eval script", .{
|
||||
.url = url,
|
||||
.caught = caught,
|
||||
.cacheable = cacheable,
|
||||
});
|
||||
|
||||
self.executeCallback(comptime .wrap("error"), page);
|
||||
self.executeCallback(comptime .wrap("error"), frame);
|
||||
}
|
||||
|
||||
fn executeCallback(self: *const Script, typ: String, page: *Page) void {
|
||||
fn executeCallback(self: *const Script, typ: String, frame: *Frame) void {
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const event = Event.initTrusted(typ, .{}, page) catch |err| {
|
||||
const event = Event.initTrusted(typ, .{}, frame) catch |err| {
|
||||
log.warn(.js, "script internal callback", .{
|
||||
.url = self.url,
|
||||
.type = typ,
|
||||
@@ -957,7 +957,7 @@ pub const Script = struct {
|
||||
});
|
||||
return;
|
||||
};
|
||||
page._event_manager.dispatchOpts(self.script_element.?.asNode().asEventTarget(), event, .{ .apply_ignore = true }) catch |err| {
|
||||
frame._event_manager.dispatchOpts(self.script_element.?.asNode().asEventTarget(), event, .{ .apply_ignore = true }) catch |err| {
|
||||
log.warn(.js, "script callback", .{
|
||||
.url = self.url,
|
||||
.type = typ,
|
||||
|
||||
@@ -28,26 +28,26 @@ const storage = @import("webapi/storage/storage.zig");
|
||||
const Navigation = @import("webapi/navigation/Navigation.zig");
|
||||
const History = @import("webapi/History.zig");
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
pub const Runner = @import("Runner.zig");
|
||||
const Browser = @import("Browser.zig");
|
||||
const Factory = @import("Factory.zig");
|
||||
const Notification = @import("../Notification.zig");
|
||||
const QueuedNavigation = Page.QueuedNavigation;
|
||||
const QueuedNavigation = Frame.QueuedNavigation;
|
||||
|
||||
const log = lp.log;
|
||||
const ArenaPool = App.ArenaPool;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
// You can create successively multiple pages for a session, but you must
|
||||
// deinit a page before running another one. It manages two distinct lifetimes.
|
||||
// You can create successively multiple frames for a session, but you must
|
||||
// deinit a frame before running another one. It manages two distinct lifetimes.
|
||||
//
|
||||
// The first is the lifetime of the Session itself, where pages are created and
|
||||
// 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 page hierarchy, i.e. \
|
||||
// the root page and all of its frames (and all of their frames.)
|
||||
// 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.)
|
||||
const Session = @This();
|
||||
|
||||
// These are the fields that remain intact for the duration of the Session
|
||||
@@ -59,12 +59,12 @@ storage_shed: storage.Shed,
|
||||
notification: *Notification,
|
||||
cookie_jar: storage.Cookie.Jar,
|
||||
|
||||
// These are the fields that get reset whenever the Session's page (the root) is reset.
|
||||
// These are the fields that get reset whenever the Session's frame (the root) is reset.
|
||||
factory: Factory,
|
||||
|
||||
page_arena: Allocator,
|
||||
frame_arena: Allocator,
|
||||
|
||||
// Origin map for same-origin context sharing. Scoped to the root page lifetime.
|
||||
// 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,
|
||||
@@ -75,7 +75,7 @@ identity: js.Identity = .{},
|
||||
// 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 page resets
|
||||
// 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),
|
||||
|
||||
@@ -87,28 +87,28 @@ globals: std.ArrayList(v8.Global) = .empty,
|
||||
// Lives at Session level so objects holding Temps can outlive individual Identities.
|
||||
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
|
||||
|
||||
// Shared resources for all pages in this session.
|
||||
// These live for the duration of the page tree (root + frames).
|
||||
// Shared resources for all frames in this session.
|
||||
// These live for the duration of the frame tree (root + frames).
|
||||
arena_pool: *ArenaPool,
|
||||
|
||||
page: ?Page,
|
||||
frame: ?Frame,
|
||||
|
||||
// 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(*Page),
|
||||
queued_navigation_2: std.ArrayList(*Page),
|
||||
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(*Page),
|
||||
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(*Page),
|
||||
queued_queued_navigation: std.ArrayList(*Frame),
|
||||
|
||||
page_id_gen: u32 = 0,
|
||||
frame_id_gen: u32 = 0,
|
||||
loader_id_gen: u32 = 0,
|
||||
|
||||
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
||||
const allocator = browser.app.allocator;
|
||||
@@ -117,17 +117,17 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
||||
const arena = try arena_pool.acquire(.small, "Session");
|
||||
errdefer arena_pool.release(arena);
|
||||
|
||||
const page_arena = try arena_pool.acquire(.large, "Session.page_arena");
|
||||
errdefer arena_pool.release(page_arena);
|
||||
const frame_arena = try arena_pool.acquire(.large, "Session.frame_arena");
|
||||
errdefer arena_pool.release(frame_arena);
|
||||
|
||||
self.* = .{
|
||||
.page = null,
|
||||
.frame = null,
|
||||
.arena = arena,
|
||||
.arena_pool = arena_pool,
|
||||
.page_arena = page_arena,
|
||||
.factory = Factory.init(page_arena),
|
||||
.frame_arena = frame_arena,
|
||||
.factory = Factory.init(frame_arena),
|
||||
.history = .{},
|
||||
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
||||
// The prototype (EventTarget) for Navigation is created when a Frame is created.
|
||||
.navigation = .{ ._proto = undefined },
|
||||
.storage_shed = .{},
|
||||
.browser = browser,
|
||||
@@ -143,52 +143,52 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Session) void {
|
||||
if (self.page != null) {
|
||||
self.removePage();
|
||||
if (self.frame != null) {
|
||||
self.removeFrame();
|
||||
}
|
||||
self.cookie_jar.deinit();
|
||||
self.fc_identity_pool.deinit();
|
||||
|
||||
self.storage_shed.deinit(self.browser.app.allocator);
|
||||
self.arena_pool.release(self.page_arena);
|
||||
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 Page is just returned as a convenience
|
||||
pub fn createPage(self: *Session) !*Page {
|
||||
lp.assert(self.page == null, "Session.createPage - page not null", .{});
|
||||
// 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", .{});
|
||||
|
||||
self.page = @as(Page, undefined);
|
||||
const page = &self.page.?;
|
||||
try Page.init(page, self.nextFrameId(), self, null);
|
||||
self.frame = @as(Frame, undefined);
|
||||
const frame = &self.frame.?;
|
||||
try Frame.init(frame, self.nextFrameId(), self, null);
|
||||
|
||||
// Creates a new NavigationEventTarget for this page.
|
||||
try self.navigation.onNewPage(page);
|
||||
// Creates a new NavigationEventTarget for this frame.
|
||||
try self.navigation.onNewFrame(frame);
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "create page", .{});
|
||||
log.debug(.browser, "create frame", .{});
|
||||
}
|
||||
// start JS env
|
||||
// Inform CDP the main page has been created such that additional context for other Worlds can be created as well
|
||||
self.notification.dispatch(.page_created, page);
|
||||
// Inform CDP the main frame has been created such that additional context for other Worlds can be created as well
|
||||
self.notification.dispatch(.frame_created, frame);
|
||||
|
||||
return page;
|
||||
return frame;
|
||||
}
|
||||
|
||||
pub fn removePage(self: *Session) void {
|
||||
// Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one
|
||||
self.notification.dispatch(.page_remove, .{});
|
||||
lp.assert(self.page != null, "Session.removePage - page is null", .{});
|
||||
pub fn removeFrame(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", .{});
|
||||
|
||||
self.page.?.deinit(false);
|
||||
self.page = null;
|
||||
self.frame.?.deinit(false);
|
||||
self.frame = null;
|
||||
|
||||
self.navigation.onRemovePage();
|
||||
self.resetPageResources();
|
||||
self.navigation.onRemoveFrame();
|
||||
self.resetFrameResources();
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "remove page", .{});
|
||||
log.debug(.browser, "remove frame", .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +235,9 @@ pub fn releaseOrigin(self: *Session, origin: *js.Origin) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset page_arena and factory for a clean slate.
|
||||
/// Called when root page is removed.
|
||||
fn resetPageResources(self: *Session) void {
|
||||
/// 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();
|
||||
@@ -281,48 +281,48 @@ fn resetPageResources(self: *Session) void {
|
||||
}
|
||||
|
||||
self.frame_id_gen = 0;
|
||||
self.arena_pool.reset(self.page_arena, 64 * 1024);
|
||||
self.factory = Factory.init(self.page_arena);
|
||||
self.arena_pool.reset(self.frame_arena, 64 * 1024);
|
||||
self.factory = Factory.init(self.frame_arena);
|
||||
}
|
||||
|
||||
pub fn replacePage(self: *Session) !*Page {
|
||||
pub fn replaceFrame(self: *Session) !*Frame {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.browser, "replace page", .{});
|
||||
log.debug(.browser, "replace frame", .{});
|
||||
}
|
||||
|
||||
lp.assert(self.page != null, "Session.replacePage null page", .{});
|
||||
lp.assert(self.page.?.parent == null, "Session.replacePage with parent", .{});
|
||||
lp.assert(self.frame != null, "Session.replaceFrame null frame", .{});
|
||||
lp.assert(self.frame.?.parent == null, "Session.replaceFrame with parent", .{});
|
||||
|
||||
var current = self.page.?;
|
||||
var current = self.frame.?;
|
||||
const frame_id = current._frame_id;
|
||||
current.deinit(true);
|
||||
|
||||
self.resetPageResources();
|
||||
self.resetFrameResources();
|
||||
|
||||
self.page = @as(Page, undefined);
|
||||
const page = &self.page.?;
|
||||
try Page.init(page, frame_id, self, null);
|
||||
return page;
|
||||
self.frame = @as(Frame, undefined);
|
||||
const frame = &self.frame.?;
|
||||
try Frame.init(frame, frame_id, self, null);
|
||||
return 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);
|
||||
}
|
||||
|
||||
pub fn findPageByFrameId(self: *Session, frame_id: u32) ?*Page {
|
||||
const page = self.currentPage() orelse return null;
|
||||
return findPageBy(page, "_frame_id", frame_id);
|
||||
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 findPageById(self: *Session, id: u32) ?*Page {
|
||||
const page = self.currentPage() orelse return null;
|
||||
return findPageBy(page, "id", 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 findPageBy(page: *Page, comptime field: []const u8, id: u32) ?*Page {
|
||||
if (@field(page, field) == id) return page;
|
||||
for (page.frames.items) |f| {
|
||||
if (findPageBy(f, field, id)) |found| {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -333,18 +333,18 @@ pub fn runner(self: *Session, opts: Runner.Opts) !Runner {
|
||||
return Runner.init(self, opts);
|
||||
}
|
||||
|
||||
pub fn scheduleNavigation(self: *Session, page: *Page) !void {
|
||||
pub fn scheduleNavigation(self: *Session, frame: *Frame) !void {
|
||||
const list = self.queued_navigation;
|
||||
|
||||
// Check if page is already queued
|
||||
// Check if frame is already queued
|
||||
for (list.items) |existing| {
|
||||
if (existing == page) {
|
||||
if (existing == frame) {
|
||||
// Already queued
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return list.append(self.arena, page);
|
||||
return list.append(self.arena, frame);
|
||||
}
|
||||
|
||||
pub fn processQueuedNavigation(self: *Session) !void {
|
||||
@@ -355,10 +355,10 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
self.queued_navigation = &self.queued_navigation_1;
|
||||
}
|
||||
|
||||
if (self.page.?._queued_navigation != null) {
|
||||
if (self.frame.?._queued_navigation != null) {
|
||||
// This is both an optimization and a simplification of sorts. If the
|
||||
// root page is navigating, then we don't need to process any other
|
||||
// navigation. Also, the navigation for the root page and for a frame
|
||||
// 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
|
||||
// is different enough that have two distinct code blocks is, imo,
|
||||
// better. Yes, there will be duplication.
|
||||
navigations.clearRetainingCapacity();
|
||||
@@ -369,20 +369,20 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
defer about_blank_queue.clearRetainingCapacity();
|
||||
|
||||
// First pass: process async navigations (non-about:blank)
|
||||
for (navigations.items) |page| {
|
||||
const qn = page._queued_navigation orelse {
|
||||
log.debug(.page, "skipped null queued nav", .{});
|
||||
for (navigations.items) |frame| {
|
||||
const qn = frame._queued_navigation orelse {
|
||||
log.debug(.frame, "skipped null queued nav", .{});
|
||||
continue;
|
||||
};
|
||||
|
||||
if (qn.is_about_blank) {
|
||||
// Defer about:blank to second pass
|
||||
try about_blank_queue.append(self.arena, page);
|
||||
try about_blank_queue.append(self.arena, frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
self.processFrameNavigation(page, qn) catch |err| {
|
||||
log.warn(.page, "frame navigation", .{ .url = qn.url, .err = err });
|
||||
self.processFrameNavigation(frame, qn) catch |err| {
|
||||
log.warn(.frame, "frame navigation", .{ .url = qn.url, .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -390,12 +390,12 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
|
||||
// Second pass: process synchronous navigations (about:blank)
|
||||
// These may trigger new navigations which go into queued_navigation
|
||||
for (about_blank_queue.items) |page| {
|
||||
const qn = page._queued_navigation orelse {
|
||||
log.debug(.page, "skipped null queued nav", .{});
|
||||
for (about_blank_queue.items) |frame| {
|
||||
const qn = frame._queued_navigation orelse {
|
||||
log.debug(.frame, "skipped null queued nav", .{});
|
||||
continue;
|
||||
};
|
||||
try self.processFrameNavigation(page, qn);
|
||||
try self.processFrameNavigation(frame, qn);
|
||||
}
|
||||
|
||||
// Safety: Remove any about:blank navigations that were queued during
|
||||
@@ -404,10 +404,10 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
const new_navigations = self.queued_navigation;
|
||||
var i: usize = 0;
|
||||
while (i < new_navigations.items.len) {
|
||||
const page = new_navigations.items[i];
|
||||
if (page._queued_navigation) |qn| {
|
||||
const frame = new_navigations.items[i];
|
||||
if (frame._queued_navigation) |qn| {
|
||||
if (qn.is_about_blank) {
|
||||
log.warn(.page, "recursive about blank", .{});
|
||||
log.warn(.frame, "recursive about blank", .{});
|
||||
_ = self.queued_navigation.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
@@ -416,75 +416,75 @@ pub fn processQueuedNavigation(self: *Session) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn processFrameNavigation(self: *Session, page: *Page, qn: *QueuedNavigation) !void {
|
||||
lp.assert(page.parent != null, "root queued navigation", .{});
|
||||
fn processFrameNavigation(self: *Session, frame: *Frame, qn: *QueuedNavigation) !void {
|
||||
lp.assert(frame.parent != null, "root queued navigation", .{});
|
||||
|
||||
const iframe = page.iframe.?;
|
||||
const parent = page.parent.?;
|
||||
const iframe = frame.iframe.?;
|
||||
const parent = frame.parent.?;
|
||||
|
||||
page._queued_navigation = null;
|
||||
frame._queued_navigation = null;
|
||||
defer self.releaseArena(qn.arena);
|
||||
|
||||
errdefer iframe._window = null;
|
||||
|
||||
const parent_notified = page._parent_notified;
|
||||
const parent_notified = frame._parent_notified;
|
||||
if (parent_notified) {
|
||||
// we already notified the parent that we had loaded
|
||||
parent._pending_loads += 1;
|
||||
}
|
||||
|
||||
const frame_id = page._frame_id;
|
||||
page.deinit(true);
|
||||
page.* = undefined;
|
||||
const frame_id = frame._frame_id;
|
||||
frame.deinit(true);
|
||||
frame.* = undefined;
|
||||
|
||||
try Page.init(page, frame_id, self, parent);
|
||||
try Frame.init(frame, frame_id, self, parent);
|
||||
errdefer {
|
||||
for (parent.frames.items, 0..) |frame, i| {
|
||||
if (frame == page) {
|
||||
parent.frames_sorted = false;
|
||||
_ = parent.frames.swapRemove(i);
|
||||
for (parent.child_frames.items, 0..) |f, i| {
|
||||
if (f == frame) {
|
||||
parent.child_frames_sorted = false;
|
||||
_ = parent.child_frames.swapRemove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parent_notified) {
|
||||
parent._pending_loads -= 1;
|
||||
}
|
||||
page.deinit(true);
|
||||
frame.deinit(true);
|
||||
}
|
||||
|
||||
page.iframe = iframe;
|
||||
iframe._window = page.window;
|
||||
frame.iframe = iframe;
|
||||
iframe._window = frame.window;
|
||||
|
||||
page.navigate(qn.url, qn.opts) catch |err| {
|
||||
frame.navigate(qn.url, qn.opts) catch |err| {
|
||||
log.err(.browser, "queued frame navigation error", .{ .err = err });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
fn processRootQueuedNavigation(self: *Session) !void {
|
||||
const current_page = &self.page.?;
|
||||
const frame_id = current_page._frame_id;
|
||||
const current_frame = &self.frame.?;
|
||||
const frame_id = current_frame._frame_id;
|
||||
|
||||
// create a copy before the page is cleared
|
||||
const qn = current_page._queued_navigation.?;
|
||||
current_page._queued_navigation = null;
|
||||
// create a copy before the frame is cleared
|
||||
const qn = current_frame._queued_navigation.?;
|
||||
current_frame._queued_navigation = null;
|
||||
|
||||
defer self.arena_pool.release(qn.arena);
|
||||
|
||||
self.removePage();
|
||||
self.removeFrame();
|
||||
|
||||
self.page = @as(Page, undefined);
|
||||
const new_page = &self.page.?;
|
||||
try Page.init(new_page, frame_id, self, null);
|
||||
self.frame = @as(Frame, undefined);
|
||||
const new_frame = &self.frame.?;
|
||||
try Frame.init(new_frame, frame_id, self, null);
|
||||
|
||||
// Creates a new NavigationEventTarget for this page.
|
||||
try self.navigation.onNewPage(new_page);
|
||||
// Creates a new NavigationEventTarget for this frame.
|
||||
try self.navigation.onNewFrame(new_frame);
|
||||
|
||||
// start JS env
|
||||
// Inform CDP the main page has been created such that additional context for other Worlds can be created as well
|
||||
self.notification.dispatch(.page_created, new_page);
|
||||
// Inform CDP the main frame has been created such that additional context for other Worlds can be created as well
|
||||
self.notification.dispatch(.frame_created, new_frame);
|
||||
|
||||
new_page.navigate(qn.url, qn.opts) catch |err| {
|
||||
new_frame.navigate(qn.url, qn.opts) catch |err| {
|
||||
log.err(.browser, "queued navigation error", .{ .err = err });
|
||||
return err;
|
||||
};
|
||||
@@ -496,15 +496,15 @@ pub fn nextFrameId(self: *Session) u32 {
|
||||
return id;
|
||||
}
|
||||
|
||||
pub fn nextPageId(self: *Session) u32 {
|
||||
const id = self.page_id_gen +% 1;
|
||||
self.page_id_gen = id;
|
||||
pub fn nextLoaderId(self: *Session) u32 {
|
||||
const id = self.loader_id_gen +% 1;
|
||||
self.loader_id_gen = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
// 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 page reset.
|
||||
// release on frame reset.
|
||||
pub const FinalizerCallback = struct {
|
||||
arena: Allocator,
|
||||
session: *Session,
|
||||
@@ -519,7 +519,7 @@ 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 page resets and
|
||||
// Allocated from Session.fc_identity_pool so it survives frame resets and
|
||||
// allows the weak callback to safely check the done flag.
|
||||
pub const Identity = struct {
|
||||
session: *Session,
|
||||
@@ -530,7 +530,7 @@ pub const FinalizerCallback = struct {
|
||||
done: bool = false,
|
||||
};
|
||||
|
||||
// Called during page reset to force cleanup regardless of identities.
|
||||
// Called during frame reset to force cleanup regardless of identities.
|
||||
fn deinit(self: *FinalizerCallback, session: *Session) void {
|
||||
// Mark all identities as done so stale V8 weak callbacks
|
||||
// won't find the wrong FC if resolved_ptr_id is reused.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
|
||||
const CssParser = @import("css/Parser.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
@@ -47,7 +47,7 @@ const StyleManager = @This();
|
||||
const Tag = Element.Tag;
|
||||
const RuleList = std.MultiArrayList(VisibilityRule);
|
||||
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
|
||||
arena: Allocator,
|
||||
|
||||
@@ -63,15 +63,15 @@ next_doc_order: u32 = 0,
|
||||
// When true, rules need to be rebuilt
|
||||
dirty: bool = false,
|
||||
|
||||
pub fn init(page: *Page) !StyleManager {
|
||||
pub fn init(frame: *Frame) !StyleManager {
|
||||
return .{
|
||||
.page = page,
|
||||
.arena = try page.getArena(.medium, "StyleManager"),
|
||||
.frame = frame,
|
||||
.arena = try frame.getArena(.medium, "StyleManager"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *StyleManager) void {
|
||||
self.page.releaseArena(self.arena);
|
||||
self.frame.releaseArena(self.arena);
|
||||
}
|
||||
|
||||
fn parseSheet(self: *StyleManager, sheet: *CSSStyleSheet) !void {
|
||||
@@ -170,7 +170,7 @@ fn rebuildIfDirty(self: *StyleManager) !void {
|
||||
const tag_rules_count = self.tag_rules.count();
|
||||
const other_rules_count = self.other_rules.len;
|
||||
|
||||
self.page._session.arena_pool.resetRetain(self.arena);
|
||||
self.frame._session.arena_pool.resetRetain(self.arena);
|
||||
|
||||
self.next_doc_order = 0;
|
||||
|
||||
@@ -186,7 +186,7 @@ fn rebuildIfDirty(self: *StyleManager) !void {
|
||||
self.other_rules = .{};
|
||||
try self.other_rules.ensureTotalCapacity(self.arena, other_rules_count);
|
||||
|
||||
const sheets = self.page.document._style_sheets orelse return;
|
||||
const sheets = self.frame.document._style_sheets orelse return;
|
||||
for (sheets._sheets.items) |sheet| {
|
||||
self.parseSheet(sheet) catch |err| {
|
||||
log.err(.browser, "StyleManager parseSheet", .{ .err = err });
|
||||
@@ -219,7 +219,7 @@ pub fn isHidden(self: *StyleManager, el: *Element, cache: ?*VisibilityCache, opt
|
||||
|
||||
// Store in cache
|
||||
if (cache) |c| {
|
||||
c.put(self.page.call_arena, elem, hidden) catch {};
|
||||
c.put(self.frame.call_arena, elem, hidden) catch {};
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
@@ -272,7 +272,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
|
||||
// Check inline styles FIRST - they use INLINE_PRIORITY so no stylesheet can beat them
|
||||
if (options.check_display) {
|
||||
if (getInlineStyleProperty(el, comptime .wrap("display"), self.page)) |property| {
|
||||
if (getInlineStyleProperty(el, comptime .wrap("display"), self.frame)) |property| {
|
||||
if (property._value.eql(comptime .wrap("none"))) {
|
||||
return true; // Early exit for hiding value
|
||||
}
|
||||
@@ -285,7 +285,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
}
|
||||
|
||||
if (options.check_visibility) {
|
||||
if (getInlineStyleProperty(el, comptime .wrap("visibility"), self.page)) |property| {
|
||||
if (getInlineStyleProperty(el, comptime .wrap("visibility"), self.frame)) |property| {
|
||||
if (property._value.eql(comptime .wrap("hidden")) or property._value.eql(comptime .wrap("collapse"))) {
|
||||
return true;
|
||||
}
|
||||
@@ -300,7 +300,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
}
|
||||
|
||||
if (options.check_opacity) {
|
||||
if (getInlineStyleProperty(el, comptime .wrap("opacity"), self.page)) |property| {
|
||||
if (getInlineStyleProperty(el, comptime .wrap("opacity"), self.frame)) |property| {
|
||||
if (property._value.eql(comptime .wrap("0"))) {
|
||||
return true;
|
||||
}
|
||||
@@ -324,7 +324,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
opacity_zero: *?bool,
|
||||
opacity_priority: *u64,
|
||||
el: *Element,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
|
||||
fn checkRules(ctx: @This(), rules: *const RuleList) void {
|
||||
if (ctx.display_priority.* == INLINE_PRIORITY and
|
||||
@@ -351,7 +351,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
|
||||
if (dominated) continue;
|
||||
|
||||
if (matchesSelector(ctx.el, selector, ctx.page)) {
|
||||
if (matchesSelector(ctx.el, selector, ctx.frame)) {
|
||||
// Update best priorities
|
||||
if (props.display_none != null and p > ctx.display_priority.*) {
|
||||
ctx.display_none.* = props.display_none;
|
||||
@@ -377,7 +377,7 @@ fn isElementHidden(self: *StyleManager, el: *Element, options: CheckVisibilityOp
|
||||
.opacity_zero = &opacity_zero,
|
||||
.opacity_priority = &opacity_priority,
|
||||
.el = el,
|
||||
.page = self.page,
|
||||
.frame = self.frame,
|
||||
};
|
||||
|
||||
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||
@@ -425,7 +425,7 @@ pub fn hasPointerEventsNone(self: *StyleManager, el: *Element, cache: ?*PointerE
|
||||
const pe_none = self.elementHasPointerEventsNone(elem);
|
||||
|
||||
if (cache) |c| {
|
||||
c.put(self.page.call_arena, elem, pe_none) catch {};
|
||||
c.put(self.frame.call_arena, elem, pe_none) catch {};
|
||||
}
|
||||
|
||||
if (pe_none) {
|
||||
@@ -439,10 +439,10 @@ pub fn hasPointerEventsNone(self: *StyleManager, el: *Element, cache: ?*PointerE
|
||||
|
||||
/// Check if a single element (not ancestors) has pointer-events:none.
|
||||
fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
// Check inline style first
|
||||
if (getInlineStyleProperty(el, .wrap("pointer-events"), page)) |property| {
|
||||
if (getInlineStyleProperty(el, .wrap("pointer-events"), frame)) |property| {
|
||||
if (property._value.eql(comptime .wrap("none"))) {
|
||||
return true;
|
||||
}
|
||||
@@ -454,7 +454,7 @@ fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
||||
|
||||
// Helper to check a single rule
|
||||
const checkRules = struct {
|
||||
fn check(rules: *const RuleList, res: *?bool, current_priority: *u64, elem: *Element, p: *Page) void {
|
||||
fn check(rules: *const RuleList, res: *?bool, current_priority: *u64, elem: *Element, p: *Frame) void {
|
||||
if (current_priority.* == INLINE_PRIORITY) return;
|
||||
|
||||
const priorities = rules.items(.priority);
|
||||
@@ -475,7 +475,7 @@ fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
||||
|
||||
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
|
||||
if (self.id_rules.get(id)) |rules| {
|
||||
checkRules(&rules, &result, &best_priority, el, page);
|
||||
checkRules(&rules, &result, &best_priority, el, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,16 +483,16 @@ fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
|
||||
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
|
||||
while (it.next()) |class| {
|
||||
if (self.class_rules.get(class)) |rules| {
|
||||
checkRules(&rules, &result, &best_priority, el, page);
|
||||
checkRules(&rules, &result, &best_priority, el, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.tag_rules.get(el.getTag())) |rules| {
|
||||
checkRules(&rules, &result, &best_priority, el, page);
|
||||
checkRules(&rules, &result, &best_priority, el, frame);
|
||||
}
|
||||
|
||||
checkRules(&self.other_rules, &result, &best_priority, el, page);
|
||||
checkRules(&self.other_rules, &result, &best_priority, el, frame);
|
||||
|
||||
return result orelse false;
|
||||
}
|
||||
@@ -686,9 +686,9 @@ fn countCompoundSpecificity(compound: Selector.Compound, ids: *u32, classes: *u3
|
||||
}
|
||||
}
|
||||
|
||||
fn matchesSelector(el: *Element, selector: Selector.Selector, page: *Page) bool {
|
||||
fn matchesSelector(el: *Element, selector: Selector.Selector, frame: *Frame) bool {
|
||||
const node = el.asNode();
|
||||
return SelectorList.matches(node, selector, node, page);
|
||||
return SelectorList.matches(node, selector, node, frame);
|
||||
}
|
||||
|
||||
const VisibilityProperties = struct {
|
||||
@@ -723,8 +723,8 @@ const CheckVisibilityOptions = struct {
|
||||
// Inline styles always win over stylesheets - use max u64 as sentinel
|
||||
const INLINE_PRIORITY: u64 = std.math.maxInt(u64);
|
||||
|
||||
fn getInlineStyleProperty(el: *Element, property_name: String, page: *Page) ?*CSSStyleProperty {
|
||||
const style = el.getOrCreateStyle(page) catch |err| {
|
||||
fn getInlineStyleProperty(el: *Element, property_name: String, frame: *Frame) ?*CSSStyleProperty {
|
||||
const style = el.getOrCreateStyle(frame) catch |err| {
|
||||
log.err(.browser, "StyleManager getOrCreateStyle", .{ .err = err });
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -1309,7 +1309,7 @@ test "URL: resolve with encoding" {
|
||||
},
|
||||
// Relative paths with encoding
|
||||
.{
|
||||
.base = "https://example.com/dir/page.html",
|
||||
.base = "https://example.com/dir/frame.html",
|
||||
.path = "../other dir/file.html",
|
||||
.expected = "https://example.com/other%20dir/file.html",
|
||||
},
|
||||
|
||||
@@ -23,23 +23,23 @@ const Element = @import("webapi/Element.zig");
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const MouseEvent = @import("webapi/event/MouseEvent.zig");
|
||||
const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const Session = @import("Session.zig");
|
||||
const Selector = @import("webapi/selector/Selector.zig");
|
||||
|
||||
fn dispatchInputAndChangeEvents(el: *Element, page: *Page) !void {
|
||||
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, page);
|
||||
page._event_manager.dispatch(el.asEventTarget(), input_evt) catch |err| {
|
||||
fn dispatchInputAndChangeEvents(el: *Element, frame: *Frame) !void {
|
||||
const input_evt: *Event = try .initTrusted(comptime .wrap("input"), .{ .bubbles = true }, frame);
|
||||
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 }, page);
|
||||
page._event_manager.dispatch(el.asEventTarget(), change_evt) catch |err| {
|
||||
const change_evt: *Event = try .initTrusted(comptime .wrap("change"), .{ .bubbles = true }, frame);
|
||||
frame._event_manager.dispatch(el.asEventTarget(), change_evt) catch |err| {
|
||||
lp.log.err(.app, "dispatch change event failed", .{ .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
pub fn click(node: *DOMNode, page: *Page) !void {
|
||||
pub fn click(node: *DOMNode, frame: *Frame) !void {
|
||||
const el = node.is(Element) orelse return error.InvalidNodeType;
|
||||
|
||||
const mouse_event: *MouseEvent = try .initTrusted(comptime .wrap("click"), .{
|
||||
@@ -48,52 +48,52 @@ pub fn click(node: *DOMNode, page: *Page) !void {
|
||||
.composed = true,
|
||||
.clientX = 0,
|
||||
.clientY = 0,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
page._event_manager.dispatch(el.asEventTarget(), mouse_event.asEvent()) catch |err| {
|
||||
frame._event_manager.dispatch(el.asEventTarget(), mouse_event.asEvent()) catch |err| {
|
||||
lp.log.err(.app, "click failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hover(node: *DOMNode, page: *Page) !void {
|
||||
pub fn hover(node: *DOMNode, frame: *Frame) !void {
|
||||
const el = node.is(Element) orelse return error.InvalidNodeType;
|
||||
|
||||
const mouseover_event: *MouseEvent = try .initTrusted(comptime .wrap("mouseover"), .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
page._event_manager.dispatch(el.asEventTarget(), mouseover_event.asEvent()) catch |err| {
|
||||
frame._event_manager.dispatch(el.asEventTarget(), mouseover_event.asEvent()) catch |err| {
|
||||
lp.log.err(.app, "hover mouseover failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
|
||||
const mouseenter_event: *MouseEvent = try .initTrusted(comptime .wrap("mouseenter"), .{
|
||||
.composed = true,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
page._event_manager.dispatch(el.asEventTarget(), mouseenter_event.asEvent()) catch |err| {
|
||||
frame._event_manager.dispatch(el.asEventTarget(), mouseenter_event.asEvent()) catch |err| {
|
||||
lp.log.err(.app, "hover mouseenter failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn press(node: ?*DOMNode, key: []const u8, page: *Page) !void {
|
||||
pub fn press(node: ?*DOMNode, key: []const u8, frame: *Frame) !void {
|
||||
const target = if (node) |n|
|
||||
(n.is(Element) orelse return error.InvalidNodeType).asEventTarget()
|
||||
else
|
||||
page.document.asNode().asEventTarget();
|
||||
frame.document.asNode().asEventTarget();
|
||||
|
||||
const keydown_event: *KeyboardEvent = try .initTrusted(comptime .wrap("keydown"), .{
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.key = key,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
page._event_manager.dispatch(target, keydown_event.asEvent()) catch |err| {
|
||||
frame._event_manager.dispatch(target, keydown_event.asEvent()) catch |err| {
|
||||
lp.log.err(.app, "press keydown failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
@@ -103,27 +103,27 @@ pub fn press(node: ?*DOMNode, key: []const u8, page: *Page) !void {
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
.key = key,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
page._event_manager.dispatch(target, keyup_event.asEvent()) catch |err| {
|
||||
frame._event_manager.dispatch(target, keyup_event.asEvent()) catch |err| {
|
||||
lp.log.err(.app, "press keyup failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn selectOption(node: *DOMNode, value: []const u8, page: *Page) !void {
|
||||
pub fn selectOption(node: *DOMNode, value: []const u8, frame: *Frame) !void {
|
||||
const el = node.is(Element) orelse return error.InvalidNodeType;
|
||||
const select = el.is(Element.Html.Select) orelse return error.InvalidNodeType;
|
||||
|
||||
select.setValue(value, page) catch |err| {
|
||||
select.setValue(value, frame) catch |err| {
|
||||
lp.log.err(.app, "select setValue failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
|
||||
try dispatchInputAndChangeEvents(el, page);
|
||||
try dispatchInputAndChangeEvents(el, frame);
|
||||
}
|
||||
|
||||
pub fn setChecked(node: *DOMNode, checked: bool, page: *Page) !void {
|
||||
pub fn setChecked(node: *DOMNode, checked: bool, frame: *Frame) !void {
|
||||
const el = node.is(Element) orelse return error.InvalidNodeType;
|
||||
const input = el.is(Element.Html.Input) orelse return error.InvalidNodeType;
|
||||
|
||||
@@ -131,7 +131,7 @@ pub fn setChecked(node: *DOMNode, checked: bool, page: *Page) !void {
|
||||
return error.InvalidNodeType;
|
||||
}
|
||||
|
||||
input.setChecked(checked, page) catch |err| {
|
||||
input.setChecked(checked, frame) catch |err| {
|
||||
lp.log.err(.app, "setChecked failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
@@ -141,34 +141,34 @@ pub fn setChecked(node: *DOMNode, checked: bool, page: *Page) !void {
|
||||
.bubbles = true,
|
||||
.cancelable = true,
|
||||
.composed = true,
|
||||
}, page);
|
||||
}, frame);
|
||||
|
||||
page._event_manager.dispatch(el.asEventTarget(), click_event.asEvent()) catch |err| {
|
||||
frame._event_manager.dispatch(el.asEventTarget(), click_event.asEvent()) catch |err| {
|
||||
lp.log.err(.app, "dispatch click event failed", .{ .err = err });
|
||||
};
|
||||
|
||||
try dispatchInputAndChangeEvents(el, page);
|
||||
try dispatchInputAndChangeEvents(el, frame);
|
||||
}
|
||||
|
||||
pub fn fill(node: *DOMNode, text: []const u8, page: *Page) !void {
|
||||
pub fn fill(node: *DOMNode, text: []const u8, frame: *Frame) !void {
|
||||
const el = node.is(Element) orelse return error.InvalidNodeType;
|
||||
|
||||
el.focus(page) catch |err| {
|
||||
el.focus(frame) catch |err| {
|
||||
lp.log.err(.app, "fill focus failed", .{ .err = err });
|
||||
};
|
||||
|
||||
if (el.is(Element.Html.Input)) |input| {
|
||||
input.setValue(text, page) catch |err| {
|
||||
input.setValue(text, frame) catch |err| {
|
||||
lp.log.err(.app, "fill input failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
} else if (el.is(Element.Html.TextArea)) |textarea| {
|
||||
textarea.setValue(text, page) catch |err| {
|
||||
textarea.setValue(text, frame) catch |err| {
|
||||
lp.log.err(.app, "fill textarea failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
} else if (el.is(Element.Html.Select)) |select| {
|
||||
select.setValue(text, page) catch |err| {
|
||||
select.setValue(text, frame) catch |err| {
|
||||
lp.log.err(.app, "fill select failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
@@ -176,32 +176,32 @@ pub fn fill(node: *DOMNode, text: []const u8, page: *Page) !void {
|
||||
return error.InvalidNodeType;
|
||||
}
|
||||
|
||||
try dispatchInputAndChangeEvents(el, page);
|
||||
try dispatchInputAndChangeEvents(el, frame);
|
||||
}
|
||||
|
||||
pub fn scroll(node: ?*DOMNode, x: ?i32, y: ?i32, page: *Page) !void {
|
||||
pub fn scroll(node: ?*DOMNode, x: ?i32, y: ?i32, frame: *Frame) !void {
|
||||
if (node) |n| {
|
||||
const el = n.is(Element) orelse return error.InvalidNodeType;
|
||||
|
||||
if (x) |val| {
|
||||
el.setScrollLeft(val, page) catch |err| {
|
||||
el.setScrollLeft(val, frame) catch |err| {
|
||||
lp.log.err(.app, "setScrollLeft failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
}
|
||||
if (y) |val| {
|
||||
el.setScrollTop(val, page) catch |err| {
|
||||
el.setScrollTop(val, frame) catch |err| {
|
||||
lp.log.err(.app, "setScrollTop failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
}
|
||||
|
||||
const scroll_evt: *Event = try .initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, page);
|
||||
page._event_manager.dispatch(el.asEventTarget(), scroll_evt) catch |err| {
|
||||
const scroll_evt: *Event = try .initTrusted(comptime .wrap("scroll"), .{ .bubbles = true }, frame);
|
||||
frame._event_manager.dispatch(el.asEventTarget(), scroll_evt) catch |err| {
|
||||
lp.log.err(.app, "dispatch scroll event failed", .{ .err = err });
|
||||
};
|
||||
} else {
|
||||
page.window.scrollTo(.{ .x = x orelse 0 }, y, page) catch |err| {
|
||||
frame.window.scrollTo(.{ .x = x orelse 0 }, y, frame) catch |err| {
|
||||
lp.log.err(.app, "scroll failed", .{ .err = err });
|
||||
return error.ActionFailed;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const Node = @import("webapi/Node.zig");
|
||||
const Slot = @import("webapi/element/html/Slot.zig");
|
||||
const IFrame = @import("webapi/element/html/IFrame.zig");
|
||||
@@ -48,7 +48,7 @@ pub const Opts = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub fn root(doc: *Node.Document, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
|
||||
pub fn root(doc: *Node.Document, opts: Opts, writer: *std.Io.Writer, frame: *Frame) !void {
|
||||
if (doc.is(Node.Document.HTMLDocument)) |html_doc| {
|
||||
blk: {
|
||||
// Ideally we just render the doctype which is part of the document
|
||||
@@ -64,20 +64,20 @@ pub fn root(doc: *Node.Document, opts: Opts, writer: *std.Io.Writer, page: *Page
|
||||
|
||||
if (opts.with_base) {
|
||||
const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode();
|
||||
const base = try doc.createElement("base", null, page);
|
||||
try base.setAttributeSafe(comptime .wrap("base"), .wrap(page.base()), page);
|
||||
_ = try parent.insertBefore(base.asNode(), parent.firstChild(), page);
|
||||
const base = try doc.createElement("base", null, frame);
|
||||
try base.setAttributeSafe(comptime .wrap("base"), .wrap(frame.base()), frame);
|
||||
_ = try parent.insertBefore(base.asNode(), parent.firstChild(), frame);
|
||||
}
|
||||
}
|
||||
|
||||
return deep(doc.asNode(), opts, writer, page);
|
||||
return deep(doc.asNode(), opts, writer, frame);
|
||||
}
|
||||
|
||||
pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) error{WriteFailed}!void {
|
||||
return _deep(node, opts, false, writer, page);
|
||||
pub fn deep(node: *Node, opts: Opts, writer: *std.Io.Writer, frame: *Frame) error{WriteFailed}!void {
|
||||
return _deep(node, opts, false, writer, frame);
|
||||
}
|
||||
|
||||
fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Writer, page: *Page) error{WriteFailed}!void {
|
||||
fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Writer, frame: *Frame) error{WriteFailed}!void {
|
||||
switch (node._type) {
|
||||
.cdata => |cd| {
|
||||
if (node.is(Node.CData.Comment)) |_| {
|
||||
@@ -119,13 +119,13 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
|
||||
if (opts.shadow == .rendered) {
|
||||
if (el.is(Slot)) |slot| {
|
||||
try dumpSlotContent(slot, opts, writer, page);
|
||||
try dumpSlotContent(slot, opts, writer, frame);
|
||||
return writer.writeAll("</slot>");
|
||||
}
|
||||
}
|
||||
if (opts.shadow != .skip) {
|
||||
if (page._element_shadow_roots.get(el)) |shadow| {
|
||||
try children(shadow.asNode(), opts, writer, page);
|
||||
if (frame._element_shadow_roots.get(el)) |shadow| {
|
||||
try children(shadow.asNode(), opts, writer, frame);
|
||||
// In rendered mode, light DOM is only shown through slots, not directly
|
||||
if (opts.shadow == .rendered) {
|
||||
// Skip rendering light DOM children
|
||||
@@ -140,21 +140,21 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
}
|
||||
|
||||
if (opts.with_frames and el.is(IFrame) != null) {
|
||||
const frame = el.as(IFrame);
|
||||
if (frame.getContentDocument()) |doc| {
|
||||
// A frame's document should always ahave a page, but
|
||||
const iframe = el.as(IFrame);
|
||||
if (iframe.getContentDocument()) |doc| {
|
||||
// A frame's document should always ahave a frame, but
|
||||
// I'm not willing to crash a release build on that assertion.
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(doc._page != null);
|
||||
std.debug.assert(doc._frame != null);
|
||||
}
|
||||
if (doc._page) |frame_page| {
|
||||
if (doc._frame) |f| {
|
||||
try writer.writeByte('\n');
|
||||
root(doc, opts, writer, frame_page) catch return error.WriteFailed;
|
||||
root(doc, opts, writer, f) catch return error.WriteFailed;
|
||||
try writer.writeByte('\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try children(node, opts, writer, page);
|
||||
try children(node, opts, writer, frame);
|
||||
}
|
||||
|
||||
if (!isVoidElement(el)) {
|
||||
@@ -163,7 +163,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
try writer.writeByte('>');
|
||||
}
|
||||
},
|
||||
.document => try children(node, opts, writer, page),
|
||||
.document => try children(node, opts, writer, frame),
|
||||
.document_type => |dt| {
|
||||
try writer.writeAll("<!DOCTYPE ");
|
||||
try writer.writeAll(dt.getName());
|
||||
@@ -187,7 +187,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
}
|
||||
try writer.writeAll(">\n");
|
||||
},
|
||||
.document_fragment => try children(node, opts, writer, page),
|
||||
.document_fragment => try children(node, opts, writer, frame),
|
||||
.attribute => {
|
||||
// Not called normally, but can be called via XMLSerializer.serializeToString
|
||||
// in which case it should return an empty string
|
||||
@@ -196,10 +196,10 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
}
|
||||
}
|
||||
|
||||
pub fn children(parent: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
|
||||
pub fn children(parent: *Node, opts: Opts, writer: *std.Io.Writer, frame: *Frame) !void {
|
||||
var it = parent.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
try deep(child, opts, writer, page);
|
||||
try deep(child, opts, writer, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,15 +243,15 @@ pub fn toJSON(node: *Node, writer: *std.json.Stringify) !void {
|
||||
try writer.endObject();
|
||||
}
|
||||
|
||||
fn dumpSlotContent(slot: *Slot, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
|
||||
const assigned = slot.assignedNodes(null, page) catch return;
|
||||
fn dumpSlotContent(slot: *Slot, opts: Opts, writer: *std.Io.Writer, frame: *Frame) !void {
|
||||
const assigned = slot.assignedNodes(null, frame) catch return;
|
||||
|
||||
if (assigned.len > 0) {
|
||||
for (assigned) |assigned_node| {
|
||||
try _deep(assigned_node, opts, true, writer, page);
|
||||
try _deep(assigned_node, opts, true, writer, frame);
|
||||
}
|
||||
} else {
|
||||
try children(slot.asNode(), opts, writer, page);
|
||||
try children(slot.asNode(), opts, writer, frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const TreeWalker = @import("webapi/TreeWalker.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
const Node = @import("webapi/Node.zig");
|
||||
@@ -157,7 +157,7 @@ pub fn registerNodes(forms_data: []FormInfo, registry: anytype) !void {
|
||||
pub fn collectForms(
|
||||
arena: Allocator,
|
||||
root: *Node,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) ![]FormInfo {
|
||||
var forms: std.ArrayList(FormInfo) = .empty;
|
||||
|
||||
@@ -166,7 +166,7 @@ pub fn collectForms(
|
||||
const form = node.is(Element.Html.Form) orelse continue;
|
||||
const el = form.asElement();
|
||||
|
||||
const fields = try collectFormFields(arena, form, page);
|
||||
const fields = try collectFormFields(arena, form, frame);
|
||||
if (fields.len == 0) continue;
|
||||
|
||||
const action_attr = el.getAttributeSafe(comptime .wrap("action"));
|
||||
@@ -186,11 +186,11 @@ pub fn collectForms(
|
||||
fn collectFormFields(
|
||||
arena: Allocator,
|
||||
form: *Element.Html.Form,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) ![]FormField {
|
||||
var fields: std.ArrayList(FormField) = .empty;
|
||||
|
||||
var elements = try form.getElements(page);
|
||||
var elements = try form.getElements(frame);
|
||||
var it = try elements.iterator();
|
||||
while (it.next()) |el| {
|
||||
const node = el.asNode();
|
||||
@@ -231,7 +231,7 @@ fn collectFormFields(
|
||||
}
|
||||
|
||||
if (el.is(Element.Html.Select)) |select| {
|
||||
const options = try collectSelectOptions(arena, node, page);
|
||||
const options = try collectSelectOptions(arena, node, frame);
|
||||
|
||||
try fields.append(arena, .{
|
||||
.node = node,
|
||||
@@ -240,7 +240,7 @@ fn collectFormFields(
|
||||
.input_type = null,
|
||||
.required = el.getAttributeSafe(comptime .wrap("required")) != null,
|
||||
.disabled = is_disabled,
|
||||
.value = select.getValue(page),
|
||||
.value = select.getValue(frame),
|
||||
.placeholder = null,
|
||||
.options = options,
|
||||
});
|
||||
@@ -256,7 +256,7 @@ fn collectFormFields(
|
||||
fn collectSelectOptions(
|
||||
arena: Allocator,
|
||||
select_node: *Node,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) ![]SelectOption {
|
||||
var options: std.ArrayList(SelectOption) = .empty;
|
||||
const Option = Element.Html.Option;
|
||||
@@ -267,8 +267,8 @@ fn collectSelectOptions(
|
||||
const option = el.is(Option) orelse continue;
|
||||
|
||||
try options.append(arena, .{
|
||||
.value = option.getValue(page),
|
||||
.text = option.getText(page),
|
||||
.value = option.getValue(frame),
|
||||
.text = option.getText(frame),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -278,18 +278,18 @@ fn collectSelectOptions(
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
fn testForms(html: []const u8) ![]FormInfo {
|
||||
const page = try testing.test_session.createPage();
|
||||
const frame = try testing.test_session.createFrame();
|
||||
|
||||
const doc = page.window._document;
|
||||
const div = try doc.createElement("div", null, page);
|
||||
try page.parseHtmlAsChildren(div.asNode(), html);
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
try frame.parseHtmlAsChildren(div.asNode(), html);
|
||||
|
||||
return collectForms(page.call_arena, div.asNode(), page);
|
||||
return collectForms(frame.call_arena, div.asNode(), frame);
|
||||
}
|
||||
|
||||
test "browser.forms: login form" {
|
||||
defer testing.reset();
|
||||
defer testing.test_session.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
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.removePage();
|
||||
defer testing.test_session.removeFrame();
|
||||
const forms = try testForms(
|
||||
\\<form>
|
||||
\\ <input type="text" name="q">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const URL = @import("URL.zig");
|
||||
const TreeWalker = @import("webapi/TreeWalker.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
@@ -143,11 +143,11 @@ pub fn registerNodes(elements: []InteractiveElement, registry: anytype) !void {
|
||||
pub fn collectInteractiveElements(
|
||||
root: *Node,
|
||||
arena: Allocator,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) ![]InteractiveElement {
|
||||
// Pre-build a map of event_target pointer → event type names,
|
||||
// so classify and getListenerTypes are both O(1) per element.
|
||||
const listener_targets = try buildListenerTargetMap(page, arena);
|
||||
const listener_targets = try buildListenerTargetMap(frame, arena);
|
||||
|
||||
var css_cache: Element.PointerEventsCache = .empty;
|
||||
|
||||
@@ -164,7 +164,7 @@ pub fn collectInteractiveElements(
|
||||
else => {},
|
||||
}
|
||||
|
||||
const itype = classifyInteractivity(page, el, html_el, listener_targets, &css_cache) orelse continue;
|
||||
const itype = classifyInteractivity(frame, el, html_el, listener_targets, &css_cache) orelse continue;
|
||||
|
||||
const listener_types = getListenerTypes(
|
||||
el.asEventTarget(),
|
||||
@@ -183,7 +183,7 @@ pub fn collectInteractiveElements(
|
||||
.id = el.getAttributeSafe(comptime .wrap("id")),
|
||||
.class = el.getAttributeSafe(comptime .wrap("class")),
|
||||
.href = if (el.getAttributeSafe(comptime .wrap("href"))) |href|
|
||||
URL.resolve(arena, page.base(), href, .{ .encoding = page.charset }) catch href
|
||||
URL.resolve(arena, frame.base(), href, .{ .encoding = frame.charset }) catch href
|
||||
else
|
||||
null,
|
||||
.input_type = getInputType(el),
|
||||
@@ -201,11 +201,11 @@ pub const ListenerTargetMap = std.AutoHashMapUnmanaged(usize, std.ArrayList([]co
|
||||
/// Pre-build a map from event_target pointer → list of event type names.
|
||||
/// This lets both classifyInteractivity (O(1) "has any?") and
|
||||
/// getListenerTypes (O(1) "which ones?") avoid re-iterating per element.
|
||||
pub fn buildListenerTargetMap(page: *Page, arena: Allocator) !ListenerTargetMap {
|
||||
pub fn buildListenerTargetMap(frame: *Frame, arena: Allocator) !ListenerTargetMap {
|
||||
var map = ListenerTargetMap{};
|
||||
|
||||
// addEventListener registrations
|
||||
var it = page._event_manager.base.lookup.iterator();
|
||||
var it = frame._event_manager.base.lookup.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const list = entry.value_ptr.*;
|
||||
if (list.first != null) {
|
||||
@@ -216,7 +216,7 @@ pub fn buildListenerTargetMap(page: *Page, arena: Allocator) !ListenerTargetMap
|
||||
}
|
||||
|
||||
// Inline handlers (onclick, onmousedown, etc.)
|
||||
var attr_it = page._event_target_attr_listeners.iterator();
|
||||
var attr_it = frame._event_target_attr_listeners.iterator();
|
||||
while (attr_it.next()) |entry| {
|
||||
const gop = try map.getOrPut(arena, @intFromPtr(entry.key_ptr.target));
|
||||
if (!gop.found_existing) gop.value_ptr.* = .empty;
|
||||
@@ -228,13 +228,13 @@ pub fn buildListenerTargetMap(page: *Page, arena: Allocator) !ListenerTargetMap
|
||||
}
|
||||
|
||||
pub fn classifyInteractivity(
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
el: *Element,
|
||||
html_el: *Element.Html,
|
||||
listener_targets: ListenerTargetMap,
|
||||
cache: ?*Element.PointerEventsCache,
|
||||
) ?InteractivityType {
|
||||
if (el.hasPointerEventsNone(cache, page)) return null;
|
||||
if (el.hasPointerEventsNone(cache, frame)) return null;
|
||||
|
||||
// 1. Native interactive by tag
|
||||
switch (el.getTag()) {
|
||||
@@ -452,14 +452,14 @@ fn getListenerTypes(target: *EventTarget, listener_targets: ListenerTargetMap) [
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
fn testInteractive(html: []const u8) ![]InteractiveElement {
|
||||
const page = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
|
||||
const doc = page.window._document;
|
||||
const div = try doc.createElement("div", null, page);
|
||||
try page.parseHtmlAsChildren(div.asNode(), html);
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
try frame.parseHtmlAsChildren(div.asNode(), html);
|
||||
|
||||
return collectInteractiveElements(div.asNode(), page.call_arena, page);
|
||||
return collectInteractiveElements(div.asNode(), frame.call_arena, frame);
|
||||
}
|
||||
|
||||
test "browser.interactive: button" {
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const string = @import("../../string.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
|
||||
|
||||
@@ -448,7 +448,7 @@ fn tupleFieldName(comptime i: usize) [:0]const u8 {
|
||||
}
|
||||
|
||||
fn isPage(comptime T: type) bool {
|
||||
return T == *Page or T == *const Page;
|
||||
return T == *Frame or T == *const Frame;
|
||||
}
|
||||
|
||||
fn isSession(comptime T: type) bool {
|
||||
@@ -462,7 +462,7 @@ fn isExecution(comptime T: type) bool {
|
||||
fn getGlobalArg(comptime T: type, ctx: *Context) T {
|
||||
if (comptime isPage(T)) {
|
||||
return switch (ctx.global) {
|
||||
.page => |page| page,
|
||||
.frame => |frame| frame,
|
||||
.worker => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const Origin = @import("Origin.zig");
|
||||
const Scheduler = @import("Scheduler.zig");
|
||||
const Execution = @import("Execution.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
const ScriptManager = @import("../ScriptManager.zig");
|
||||
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
|
||||
@@ -41,26 +41,26 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
const Context = @This();
|
||||
|
||||
pub const GlobalScope = union(enum) {
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
worker: *WorkerGlobalScope,
|
||||
|
||||
pub fn base(self: GlobalScope) [:0]const u8 {
|
||||
return switch (self) {
|
||||
.page => |page| page.base(),
|
||||
.frame => |frame| frame.base(),
|
||||
.worker => |worker| worker.base(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getJs(self: GlobalScope) *Context {
|
||||
return switch (self) {
|
||||
.page => |page| page.js,
|
||||
.frame => |frame| frame.js,
|
||||
.worker => |worker| worker.js,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setJs(self: GlobalScope, ctx: *Context) void {
|
||||
switch (self) {
|
||||
.page => |page| page.js = ctx,
|
||||
.frame => |frame| frame.js = ctx,
|
||||
.worker => |worker| worker.js = ctx,
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ templates: []*const v8.FunctionTemplate,
|
||||
arena: Allocator,
|
||||
|
||||
// The call_arena for this context. For main world contexts this is
|
||||
// page.call_arena. For isolated world contexts this is a separate arena
|
||||
// frame.call_arena. For isolated world contexts this is a separate arena
|
||||
// owned by the IsolatedWorld.
|
||||
call_arena: Allocator,
|
||||
|
||||
@@ -114,7 +114,7 @@ origin: *Origin,
|
||||
identity: *js.Identity,
|
||||
|
||||
// Allocator to use for identity map operations. For main world contexts this is
|
||||
// session.page_arena, for isolated worlds it's the isolated world's arena.
|
||||
// session.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
|
||||
@@ -131,7 +131,7 @@ module_cache: std.StringHashMapUnmanaged(ModuleEntry) = .empty,
|
||||
// necessary to lookup/store the dependent module in the module_cache.
|
||||
module_identifier: std.AutoHashMapUnmanaged(u32, [:0]const u8) = .empty,
|
||||
|
||||
// the page's script manager
|
||||
// the frame's script manager
|
||||
script_manager: ?*ScriptManager,
|
||||
|
||||
// Our macrotasks
|
||||
@@ -227,9 +227,9 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
|
||||
const isolate = env.isolate;
|
||||
|
||||
if (comptime IS_DEBUG) {
|
||||
// A page starts off with an opaque origin. After navigation, setOrigin
|
||||
// A frame starts off with an opaque origin. After navigation, setOrigin
|
||||
// is called. This is the only time setOrigin should be called for that
|
||||
// page. Therefore, when setOrigin is called, the previous origin should
|
||||
// frame. Therefore, when setOrigin is called, the previous origin should
|
||||
// have been opaque and its rc should have been 1.
|
||||
lp.assert(self.origin.rc == 1, "Ref opaque origin", .{ .rc = self.origin.rc });
|
||||
}
|
||||
@@ -252,11 +252,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.page_arena, global);
|
||||
return self.session.globals.append(self.session.frame_arena, global);
|
||||
}
|
||||
|
||||
pub fn trackTemp(self: *Context, global: v8.Global) !void {
|
||||
return self.session.temps.put(self.session.page_arena, global.data_ptr, global);
|
||||
return self.session.temps.put(self.session.frame_arena, global.data_ptr, global);
|
||||
}
|
||||
|
||||
pub const IdentityResult = struct {
|
||||
@@ -294,10 +294,10 @@ pub fn toLocal(self: *Context, global: anytype) js.Local.ToLocalReturnType(@Type
|
||||
return l.toLocal(global);
|
||||
}
|
||||
|
||||
pub fn getIncumbent(self: *Context) *Page {
|
||||
pub fn getIncumbent(self: *Context) *Frame {
|
||||
const ctx = fromC(v8.v8__Isolate__GetIncumbentContext(self.env.isolate.handle).?).?;
|
||||
return switch (ctx.global) {
|
||||
.page => |page| page,
|
||||
.frame => |frame| frame,
|
||||
.worker => unreachable,
|
||||
};
|
||||
}
|
||||
@@ -860,7 +860,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
|
||||
// The microtask is tied to the isolate, not the context
|
||||
// it can be resolved while another context is active
|
||||
// (Which seems crazy to me). If that happens, then
|
||||
// another page was loaded and we MUST ignore this
|
||||
// another frame was loaded and we MUST ignore this
|
||||
// (most of the fields in state are not valid)
|
||||
return;
|
||||
}
|
||||
@@ -903,7 +903,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
|
||||
}
|
||||
|
||||
// Used to make temporarily enter and exit a context, updating and restoring
|
||||
// page.js:
|
||||
// frame.js:
|
||||
// var hs: js.HandleScope = undefined;
|
||||
// const entered = ctx.enter(&hs);
|
||||
// defer entered.exit();
|
||||
@@ -920,7 +920,7 @@ pub fn enter(self: *Context, hs: *js.HandleScope) Entered {
|
||||
}
|
||||
|
||||
const Entered = struct {
|
||||
// the context we should restore on the page/worker
|
||||
// the context we should restore on the frame/worker
|
||||
original: *Context,
|
||||
|
||||
// the handle of the entered context
|
||||
@@ -941,7 +941,7 @@ pub fn queueMutationDelivery(self: *Context) !void {
|
||||
self.enqueueMicrotask(struct {
|
||||
fn run(ctx: *Context) void {
|
||||
switch (ctx.global) {
|
||||
.page => |page| page.deliverMutations(),
|
||||
.frame => |frame| frame.deliverMutations(),
|
||||
.worker => unreachable,
|
||||
}
|
||||
}
|
||||
@@ -952,7 +952,7 @@ pub fn queueIntersectionChecks(self: *Context) !void {
|
||||
self.enqueueMicrotask(struct {
|
||||
fn run(ctx: *Context) void {
|
||||
switch (ctx.global) {
|
||||
.page => |page| page.performScheduledIntersectionChecks(),
|
||||
.frame => |frame| frame.performScheduledIntersectionChecks(),
|
||||
.worker => unreachable,
|
||||
}
|
||||
}
|
||||
@@ -963,7 +963,7 @@ pub fn queueIntersectionDelivery(self: *Context) !void {
|
||||
self.enqueueMicrotask(struct {
|
||||
fn run(ctx: *Context) void {
|
||||
switch (ctx.global) {
|
||||
.page => |page| page.deliverIntersections(),
|
||||
.frame => |frame| frame.deliverIntersections(),
|
||||
.worker => unreachable,
|
||||
}
|
||||
}
|
||||
@@ -974,7 +974,7 @@ pub fn queueSlotchangeDelivery(self: *Context) !void {
|
||||
self.enqueueMicrotask(struct {
|
||||
fn run(ctx: *Context) void {
|
||||
switch (ctx.global) {
|
||||
.page => |page| page.deliverSlotchangeEvents(),
|
||||
.frame => |frame| frame.deliverSlotchangeEvents(),
|
||||
.worker => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const Snapshot = @import("Snapshot.zig");
|
||||
const Inspector = @import("Inspector.zig");
|
||||
|
||||
const App = @import("../../App.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Window = @import("../webapi/Window.zig");
|
||||
const WorkerGlobalScope = @import("../webapi/WorkerGlobalScope.zig");
|
||||
|
||||
@@ -74,8 +74,8 @@ context_id: usize,
|
||||
|
||||
// Maps origin -> shared Origin contains, for v8 values shared across
|
||||
// same-origin Contexts. There's a mismatch here between our JS model and our
|
||||
// Browser model. Origins only live as long as the root page of a session exists.
|
||||
// It would be wrong/dangerous to re-use an Origin across root page navigations.
|
||||
// Browser model. Origins only live as long as the root frame of a session exists.
|
||||
// It would be wrong/dangerous to re-use an Origin across root frame navigations.
|
||||
|
||||
// Global handles that need to be freed on deinit
|
||||
eternal_function_templates: []v8.Eternal,
|
||||
@@ -217,8 +217,8 @@ pub const ContextParams = struct {
|
||||
debug_name: []const u8 = "Context",
|
||||
};
|
||||
|
||||
pub fn createContext(self: *Env, page: *Page, params: ContextParams) !*Context {
|
||||
return self._createContext(page, params);
|
||||
pub fn createContext(self: *Env, frame: *Frame, params: ContextParams) !*Context {
|
||||
return self._createContext(frame, params);
|
||||
}
|
||||
|
||||
pub fn createWorkerContext(self: *Env, worker: *WorkerGlobalScope, params: ContextParams) !*Context {
|
||||
@@ -227,7 +227,7 @@ pub fn createWorkerContext(self: *Env, worker: *WorkerGlobalScope, params: Conte
|
||||
|
||||
fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context {
|
||||
const T = @TypeOf(global);
|
||||
const is_page = T == *Page;
|
||||
const is_frame = T == *Frame;
|
||||
|
||||
const context_arena = try self.app.arena_pool.acquire(.medium, params.debug_name);
|
||||
errdefer self.app.arena_pool.release(context_arena);
|
||||
@@ -242,7 +242,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
errdefer v8.v8__MicrotaskQueue__DELETE(microtask_queue);
|
||||
|
||||
// Restore the context from the snapshot (0 = Page, 1 = Worker)
|
||||
const snapshot_index: u32 = if (comptime is_page) 0 else 1;
|
||||
const snapshot_index: u32 = if (comptime is_frame) 0 else 1;
|
||||
const v8_context = v8.v8__Context__FromSnapshot__Config(isolate.handle, snapshot_index, &.{
|
||||
.global_template = null,
|
||||
.global_object = null,
|
||||
@@ -259,7 +259,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
// Store our TAO inside the internal field of the global object. This
|
||||
// maps the v8::Object -> Zig instance.
|
||||
const tao = try params.identity_arena.create(@import("TaggedOpaque.zig"));
|
||||
tao.* = if (comptime is_page) .{
|
||||
tao.* = if (comptime is_frame) .{
|
||||
.value = @ptrCast(global.window),
|
||||
.prototype_chain = (&Window.JsApi.Meta.prototype_chain).ptr,
|
||||
.prototype_len = @intCast(Window.JsApi.Meta.prototype_chain.len),
|
||||
@@ -282,7 +282,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
const context = try context_arena.create(Context);
|
||||
context.* = .{
|
||||
.env = self,
|
||||
.global = if (comptime is_page) .{ .page = global } else .{ .worker = global },
|
||||
.global = if (comptime is_frame) .{ .frame = global } else .{ .worker = global },
|
||||
.origin = origin,
|
||||
.id = context_id,
|
||||
.session = session,
|
||||
@@ -292,7 +292,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
.templates = self.templates,
|
||||
.call_arena = params.call_arena,
|
||||
.microtask_queue = microtask_queue,
|
||||
.script_manager = if (comptime is_page) &global._script_manager else null,
|
||||
.script_manager = if (comptime is_frame) &global._script_manager else null,
|
||||
.scheduler = .init(context_arena),
|
||||
.identity = params.identity,
|
||||
.identity_arena = params.identity_arena,
|
||||
@@ -312,7 +312,7 @@ fn _createContext(self: *Env, global: anytype, params: ContextParams) !*Context
|
||||
|
||||
// Register in the identity map. Multiple contexts can be created for the
|
||||
// same global (via CDP), so we only register the first one.
|
||||
const identity_ptr = if (comptime is_page) @intFromPtr(global.window) else @intFromPtr(global);
|
||||
const identity_ptr = if (comptime is_frame) @intFromPtr(global.window) else @intFromPtr(global);
|
||||
const gop = try params.identity.identity_map.getOrPut(params.identity_arena, identity_ptr);
|
||||
if (gop.found_existing == false) {
|
||||
var global_global: v8.Global = undefined;
|
||||
@@ -380,7 +380,7 @@ pub fn runMacrotasks(self: *Env) !void {
|
||||
// I hate this comptime check as much as you do. But we have tests
|
||||
// which rely on short execution before shutdown. In real world, it's
|
||||
// underterministic whether a timer will or won't run before the
|
||||
// page shutsdown. But for tests, we need to run them to their end.
|
||||
// frame shutsdown. But for tests, we need to run them to their end.
|
||||
if (ctx.scheduler.hasReadyTasks() == false) {
|
||||
continue;
|
||||
}
|
||||
@@ -506,11 +506,11 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v
|
||||
|
||||
const no_handler = promise_event == v8.kPromiseRejectWithNoHandler;
|
||||
switch (ctx.global) {
|
||||
.page => |page| {
|
||||
page.window.unhandledPromiseRejection(no_handler, .{
|
||||
.frame => |frame| {
|
||||
frame.window.unhandledPromiseRejection(no_handler, .{
|
||||
.local = &local,
|
||||
.handle = &message_handle,
|
||||
}, page) catch |err| {
|
||||
}, frame) catch |err| {
|
||||
log.warn(.browser, "unhandled rejection handler", .{ .err = err, .target = "window" });
|
||||
};
|
||||
},
|
||||
@@ -558,10 +558,10 @@ const PrivateSymbols = struct {
|
||||
const testing = @import("../../testing.zig");
|
||||
test "Env: Worker context " {
|
||||
const session = testing.test_session;
|
||||
const page = try session.createPage();
|
||||
defer session.removePage();
|
||||
const frame = try session.createFrame();
|
||||
defer session.removeFrame();
|
||||
|
||||
const worker = try @import("../webapi/Worker.zig").init("http://localhost:9582/src/browser/tests/testing.js", &page.js.execution);
|
||||
const worker = try @import("../webapi/Worker.zig").init("http://localhost:9582/src/browser/tests/testing.js", &frame.js.execution);
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
worker._worker_scope.js.localScope(&ls);
|
||||
@@ -571,13 +571,13 @@ test "Env: Worker context " {
|
||||
try testing.expectEqual(true, (try ls.local.exec("typeof WorkerGlobalScope !== 'undefined'", null)).isTrue());
|
||||
}
|
||||
|
||||
test "Env: Page context" {
|
||||
test "Env: Frame context" {
|
||||
const session = testing.test_session;
|
||||
const page = try session.createPage();
|
||||
defer session.removePage();
|
||||
const frame = try session.createFrame();
|
||||
defer session.removeFrame();
|
||||
|
||||
// Page already has a context created, use it directly
|
||||
const ctx = page.js;
|
||||
// Frame already has a context created, use it directly
|
||||
const ctx = frame.js;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
ctx.localScope(&ls);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
//! Execution context for worker-compatible APIs.
|
||||
//!
|
||||
//! This provides a common interface for APIs that work in both Window and Worker
|
||||
//! contexts. Instead of taking `*Page` (which is DOM-specific), these APIs take
|
||||
//! contexts. Instead of taking `*Frame` (which is DOM-specific), these APIs take
|
||||
//! `*Execution` which abstracts the common infrastructure.
|
||||
//!
|
||||
//! The bridge constructs an Execution on-the-fly from the current context,
|
||||
|
||||
@@ -34,7 +34,7 @@ const log = lp.log;
|
||||
const CallOpts = Caller.CallOpts;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
// Where js.Context has a lifetime tied to the page, and holds the
|
||||
// Where js.Context has a lifetime tied to the frame, and holds the
|
||||
// v8::Global<v8::Context>, this has a much shorter lifetime and holds a
|
||||
// v8::Local<v8::Context>. In V8, you need a Local<v8::Context> or get anything
|
||||
// done, but the local only exists for the lifetime of the HandleScope it was
|
||||
@@ -271,11 +271,11 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
|
||||
const finalizer_ptr_id = finalizer.ptr_id;
|
||||
|
||||
const session = ctx.session;
|
||||
const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.page_arena, finalizer_ptr_id);
|
||||
const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.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 page reset if v8 doesn't finalize.
|
||||
// so that we can cleanup on frame reset if v8 doesn't finalize.
|
||||
errdefer _ = session.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);
|
||||
@@ -336,15 +336,15 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
|
||||
}
|
||||
|
||||
if (@typeInfo(ptr.child) == .@"struct" and @hasDecl(ptr.child, "runtimeGenericWrap")) {
|
||||
const page = switch (self.ctx.global) {
|
||||
.page => |p| p,
|
||||
const frame = switch (self.ctx.global) {
|
||||
.frame => |f| f,
|
||||
.worker => {
|
||||
// No Worker-related API currently uses this, so haven't
|
||||
// added support for it
|
||||
unreachable;
|
||||
},
|
||||
};
|
||||
const wrap = try value.runtimeGenericWrap(page);
|
||||
const wrap = try value.runtimeGenericWrap(frame);
|
||||
return self.zigValueToJs(wrap, opts);
|
||||
}
|
||||
|
||||
@@ -424,15 +424,15 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
|
||||
// zig fmt: on
|
||||
|
||||
if (@hasDecl(T, "runtimeGenericWrap")) {
|
||||
const page = switch (self.ctx.global) {
|
||||
.page => |p| p,
|
||||
const frame = switch (self.ctx.global) {
|
||||
.frame => |f| f,
|
||||
.worker => {
|
||||
// No Worker-related API currently uses this, so haven't
|
||||
// added support for it
|
||||
unreachable;
|
||||
},
|
||||
};
|
||||
const wrap = try value.runtimeGenericWrap(page);
|
||||
const wrap = try value.runtimeGenericWrap(frame);
|
||||
return self.zigValueToJs(wrap, opts);
|
||||
}
|
||||
|
||||
@@ -1226,7 +1226,7 @@ fn resolveT(comptime T: type, value: *T) Resolved {
|
||||
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
|
||||
const identity_finalizer: *Session.FinalizerCallback.Identity = @ptrCast(@alignCast(ptr));
|
||||
|
||||
// Identity is allocated from pool, so it's valid even after page reset.
|
||||
// Identity is allocated from pool, so it's valid even after frame reset.
|
||||
const session = identity_finalizer.session;
|
||||
const resolved_ptr_id = identity_finalizer.resolved_ptr_id;
|
||||
defer session.fc_identity_pool.destroy(identity_finalizer);
|
||||
@@ -1237,7 +1237,7 @@ fn resolveT(comptime T: type, value: *T) Resolved {
|
||||
v8.v8__Global__Reset(&global);
|
||||
}
|
||||
|
||||
// If done, FC was already cleaned up during page reset. The
|
||||
// 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 (identity_finalizer.done) return;
|
||||
@@ -1474,7 +1474,7 @@ pub fn throw(self: *const Local, err: []const u8) js.Exception {
|
||||
}
|
||||
|
||||
// Convert a Global (or optional Global) to a Local (or optional Local).
|
||||
// Meant to be used from either page.js.toLocal, where the context must have an
|
||||
// Meant to be used from either frame.js.toLocal, where the context must have an
|
||||
// non-null local (orelse panic), or from a LocalScope
|
||||
pub fn toLocal(self: *const Local, global: anytype) ToLocalReturnType(@TypeOf(global)) {
|
||||
const T = @TypeOf(global);
|
||||
@@ -1561,12 +1561,12 @@ fn createFinalizerCallback(
|
||||
// initiated or not), we need to create a Local.Scope:
|
||||
//
|
||||
// var ls: js.Local.Scope = udnefined;
|
||||
// page.js.localScope(&ls);
|
||||
// frame.js.localScope(&ls);
|
||||
// defer ls.deinit();
|
||||
// // can use ls.local as needed.
|
||||
//
|
||||
// Note: Zig code that is 100% guaranteed to be v8-initiated can get a local via:
|
||||
// page.js.local.?
|
||||
// frame.js.local.?
|
||||
pub const Scope = struct {
|
||||
local: Local,
|
||||
handle_scope: js.HandleScope,
|
||||
|
||||
@@ -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.page_arena) };
|
||||
return .{ .str = try self.toSSOWithAlloc(self.local.ctx.session.frame_arena) };
|
||||
}
|
||||
return self.toSSOWithAlloc(self.local.call_arena);
|
||||
}
|
||||
|
||||
@@ -328,12 +328,23 @@ pub fn structuredCloneTo(self: Value, target: *const js.Local) !Value {
|
||||
|
||||
// Called by V8 to report serialization errors. The exception should already be thrown.
|
||||
fn throwDataCloneError(_: ?*anyopaque, _: ?*const v8.String) callconv(.c) void {}
|
||||
|
||||
// Called when V8 encounters a SharedArrayBuffer. We don't support sharing them across
|
||||
// contexts, so throw a DataCloneError and return false. V8's WriteJSArrayBuffer calls
|
||||
// RETURN_VALUE_IF_EXCEPTION after this, so throwing prevents the fatal FromJust call.
|
||||
fn getSharedArrayBufferId(_: ?*anyopaque, isolate: ?*v8.Isolate, _: ?*const v8.SharedArrayBuffer, _: ?*u32) callconv(.c) bool {
|
||||
const iso = isolate orelse return false;
|
||||
const message = v8.v8__String__NewFromUtf8(iso, "SharedArrayBuffer cannot be cloned.", v8.kNormal, -1);
|
||||
const error_value = v8.v8__Exception__Error(message) orelse return false;
|
||||
_ = v8.v8__Isolate__ThrowException(iso, error_value);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const size, const data = blk: {
|
||||
const serializer = v8.v8__ValueSerializer__New(v8_isolate, &.{
|
||||
.data = null,
|
||||
.get_shared_array_buffer_id = null,
|
||||
.get_shared_array_buffer_id = SerializerDelegate.getSharedArrayBufferId,
|
||||
.write_host_object = SerializerDelegate.writeHostObject,
|
||||
.throw_data_clone_error = SerializerDelegate.throwDataCloneError,
|
||||
}) orelse return error.JsException;
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const v8 = js.v8;
|
||||
@@ -161,7 +161,7 @@ pub const Function = struct {
|
||||
var params = @typeInfo(T).@"fn".params;
|
||||
for (params[1..]) |p| { // start at 1, skip self
|
||||
const PT = p.type.?;
|
||||
if (PT == *Page or PT == *const Page) {
|
||||
if (PT == *Frame or PT == *const Frame) {
|
||||
break;
|
||||
}
|
||||
if (@typeInfo(PT) == .optional) {
|
||||
@@ -428,9 +428,9 @@ pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8
|
||||
|
||||
// Only Page contexts have document.getElementById lookup
|
||||
switch (local.ctx.global) {
|
||||
.page => |page| {
|
||||
const document = page.document;
|
||||
if (document.getElementById(property, page)) |el| {
|
||||
.frame => |frame| {
|
||||
const document = frame.document;
|
||||
if (document.getElementById(property, frame)) |el| {
|
||||
const js_val = local.zigValueToJs(el, .{}) catch return 0;
|
||||
var pc = Caller.PropertyCallbackInfo{ .handle = handle.? };
|
||||
pc.getReturnValue().set(js_val);
|
||||
@@ -916,6 +916,7 @@ pub const WorkerJsApis = flattenTypes(&.{
|
||||
@import("../webapi/net/URLSearchParams.zig"),
|
||||
@import("../webapi/encoding/TextEncoder.zig"),
|
||||
@import("../webapi/encoding/TextDecoder.zig"),
|
||||
@import("../webapi/Blob.zig"),
|
||||
@import("../webapi/File.zig"),
|
||||
@import("../webapi/Console.zig"),
|
||||
@import("../webapi/Crypto.zig"),
|
||||
@@ -923,17 +924,19 @@ pub const WorkerJsApis = flattenTypes(&.{
|
||||
@import("../webapi/net/Headers.zig"),
|
||||
@import("../webapi/net/Request.zig"),
|
||||
@import("../webapi/net/Response.zig"),
|
||||
@import("../webapi/streams/TransformStream.zig"),
|
||||
@import("../webapi/streams/ReadableStream.zig"),
|
||||
@import("../webapi/streams/ReadableStreamDefaultReader.zig"),
|
||||
@import("../webapi/streams/ReadableStreamDefaultController.zig"),
|
||||
@import("../webapi/streams/WritableStream.zig"),
|
||||
@import("../webapi/streams/WritableStreamDefaultWriter.zig"),
|
||||
@import("../webapi/streams/WritableStreamDefaultController.zig"),
|
||||
@import("../webapi/encoding/TextEncoderStream.zig"),
|
||||
@import("../webapi/encoding/TextDecoderStream.zig"),
|
||||
// @import("../webapi/URL.zig"),
|
||||
// @import("../webapi/Blob.zig"),
|
||||
// @import("../webapi/Performance.zig"),
|
||||
// @import("../webapi/AbortSignal.zig"),
|
||||
// @import("../webapi/AbortController.zig"),
|
||||
// @import("../webapi/streams/ReadableStream.zig"),
|
||||
// @import("../webapi/streams/ReadableStreamDefaultReader.zig"),
|
||||
// @import("../webapi/streams/ReadableStreamDefaultController.zig"),
|
||||
// @import("../webapi/streams/WritableStream.zig"),
|
||||
// @import("../webapi/streams/WritableStreamDefaultWriter.zig"),
|
||||
// @import("../webapi/streams/WritableStreamDefaultController.zig"),
|
||||
});
|
||||
|
||||
// Master list of ALL JS APIs across all contexts.
|
||||
|
||||
@@ -20,22 +20,22 @@ const std = @import("std");
|
||||
|
||||
const Element = @import("webapi/Element.zig");
|
||||
const Node = @import("webapi/Node.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const Selector = @import("webapi/selector/Selector.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Collect all links (href attributes from anchor tags) under `root`.
|
||||
/// Returns a slice of strings allocated with `arena`.
|
||||
pub fn collectLinks(arena: Allocator, root: *Node, page: *Page) ![]const []const u8 {
|
||||
pub fn collectLinks(arena: Allocator, root: *Node, frame: *Frame) ![]const []const u8 {
|
||||
var links: std.ArrayList([]const u8) = .empty;
|
||||
|
||||
if (Selector.querySelectorAll(root, "a[href]", page)) |list| {
|
||||
defer list.deinit(page._session);
|
||||
if (Selector.querySelectorAll(root, "a[href]", frame)) |list| {
|
||||
defer list.deinit(frame._session);
|
||||
|
||||
for (list._nodes) |node| {
|
||||
if (node.is(Element.Html.Anchor)) |anchor| {
|
||||
const href = anchor.getHref(page) catch |err| {
|
||||
const href = anchor.getHref(frame) catch |err| {
|
||||
@import("../lightpanda.zig").log.err(.app, "resolve href failed", .{ .err = err });
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const URL = @import("URL.zig");
|
||||
const TreeWalker = @import("webapi/TreeWalker.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
@@ -131,7 +131,7 @@ fn analyzeContent(root: *Node) ContentInfo {
|
||||
const Context = struct {
|
||||
state: State,
|
||||
writer: *std.Io.Writer,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
|
||||
fn ensureNewline(self: *Context) !void {
|
||||
if (!self.state.last_char_was_newline) {
|
||||
@@ -278,8 +278,8 @@ const Context = struct {
|
||||
}
|
||||
try self.writer.writeAll("](");
|
||||
if (el.getAttributeSafe(comptime .wrap("src"))) |src| {
|
||||
const page = self.page;
|
||||
const absolute_src = URL.resolve(page.call_arena, page.base(), src, .{ .encoding = page.charset }) catch src;
|
||||
const frame = self.frame;
|
||||
const absolute_src = URL.resolve(frame.call_arena, frame.base(), src, .{ .encoding = frame.charset }) catch src;
|
||||
try self.writer.writeAll(absolute_src);
|
||||
}
|
||||
try self.writer.writeAll(")");
|
||||
@@ -287,14 +287,14 @@ const Context = struct {
|
||||
return;
|
||||
},
|
||||
.anchor => {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
const info = analyzeContent(el.asNode());
|
||||
const label = getAnchorLabel(el);
|
||||
const href_raw = el.getAttributeSafe(comptime .wrap("href"));
|
||||
|
||||
if (!info.has_visible and label == null and href_raw == null) return;
|
||||
|
||||
const href = if (href_raw) |h| URL.resolve(page.call_arena, page.base(), h, .{ .encoding = page.charset }) catch h else null;
|
||||
const href = if (href_raw) |h| URL.resolve(frame.call_arena, frame.base(), h, .{ .encoding = frame.charset }) catch h else null;
|
||||
|
||||
if (info.has_block) {
|
||||
try self.renderChildren(el.asNode());
|
||||
@@ -459,12 +459,12 @@ const Context = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn dump(node: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) !void {
|
||||
pub fn dump(node: *Node, opts: Opts, writer: *std.Io.Writer, frame: *Frame) !void {
|
||||
_ = opts;
|
||||
var ctx: Context = .{
|
||||
.state = .{},
|
||||
.writer = writer,
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
};
|
||||
try ctx.render(node);
|
||||
if (!ctx.state.last_char_was_newline) {
|
||||
@@ -474,18 +474,18 @@ pub fn dump(node: *Node, opts: Opts, writer: *std.Io.Writer, page: *Page) !void
|
||||
|
||||
fn testMarkdownHTML(html: []const u8, expected: []const u8) !void {
|
||||
const testing = @import("../testing.zig");
|
||||
const page = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
page.url = "http://localhost/";
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
frame.url = "http://localhost/";
|
||||
|
||||
const doc = page.window._document;
|
||||
const doc = frame.window._document;
|
||||
|
||||
const div = try doc.createElement("div", null, page);
|
||||
try page.parseHtmlAsChildren(div.asNode(), html);
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
try frame.parseHtmlAsChildren(div.asNode(), html);
|
||||
|
||||
var aw: std.Io.Writer.Allocating = .init(testing.allocator);
|
||||
defer aw.deinit();
|
||||
try dump(div.asNode(), .{}, &aw.writer, page);
|
||||
try dump(div.asNode(), .{}, &aw.writer, frame);
|
||||
|
||||
try testing.expectString(expected, aw.written());
|
||||
}
|
||||
@@ -677,13 +677,13 @@ test "browser.markdown: skip empty links" {
|
||||
|
||||
test "browser.markdown: resolve links" {
|
||||
const testing = @import("../testing.zig");
|
||||
const page = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
page.url = "https://example.com/a/index.html";
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
frame.url = "https://example.com/a/index.html";
|
||||
|
||||
const doc = page.window._document;
|
||||
const div = try doc.createElement("div", null, page);
|
||||
try page.parseHtmlAsChildren(div.asNode(),
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
try frame.parseHtmlAsChildren(div.asNode(),
|
||||
\\<a href="b">Link</a>
|
||||
\\<img src="../c.png" alt="Img">
|
||||
\\<a href="/my page">Space</a>
|
||||
@@ -691,7 +691,7 @@ test "browser.markdown: resolve links" {
|
||||
|
||||
var aw: std.Io.Writer.Allocating = .init(testing.allocator);
|
||||
defer aw.deinit();
|
||||
try dump(div.asNode(), .{}, &aw.writer, page);
|
||||
try dump(div.asNode(), .{}, &aw.writer, frame);
|
||||
|
||||
try testing.expectString(
|
||||
\\[Link](https://example.com/a/b)
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const h5e = @import("html5ever.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Node = @import("../webapi/Node.zig");
|
||||
const Element = @import("../webapi/Element.zig");
|
||||
|
||||
@@ -41,16 +41,16 @@ pub const ParsedNode = struct {
|
||||
|
||||
const Parser = @This();
|
||||
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
err: ?Error,
|
||||
container: ParsedNode,
|
||||
arena: Allocator,
|
||||
strings: std.StringHashMapUnmanaged(void),
|
||||
|
||||
pub fn init(arena: Allocator, node: *Node, page: *Page) Parser {
|
||||
pub fn init(arena: Allocator, node: *Node, frame: *Frame) Parser {
|
||||
return .{
|
||||
.err = null,
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.strings = .empty,
|
||||
.arena = arena,
|
||||
.container = ParsedNode{
|
||||
@@ -179,10 +179,10 @@ pub const Streaming = struct {
|
||||
parser: Parser,
|
||||
handle: ?*anyopaque,
|
||||
|
||||
pub fn init(arena: Allocator, node: *Node, page: *Page) Streaming {
|
||||
pub fn init(arena: Allocator, node: *Node, frame: *Frame) Streaming {
|
||||
return .{
|
||||
.handle = null,
|
||||
.parser = Parser.init(arena, node, page),
|
||||
.parser = Parser.init(arena, node, frame),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ fn popCallback(ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void {
|
||||
}
|
||||
|
||||
fn _popCallback(self: *Parser, node: *Node) !void {
|
||||
try self.page.nodeComplete(node);
|
||||
try self.frame.nodeComplete(node);
|
||||
}
|
||||
|
||||
fn createElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque {
|
||||
@@ -271,11 +271,11 @@ fn _createElementCallbackWithDefaultnamespace(ctx: *anyopaque, data: *anyopaque,
|
||||
};
|
||||
}
|
||||
fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator, default_namespace: Element.Namespace) !*anyopaque {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
const name = qname.local.slice();
|
||||
const namespace_string = qname.ns.slice();
|
||||
const namespace = if (namespace_string.len == 0) default_namespace else Element.Namespace.parse(namespace_string);
|
||||
const node = try page.createElementNS(namespace, name, attributes);
|
||||
const node = try frame.createElementNS(namespace, name, attributes);
|
||||
|
||||
const pn = try self.arena.create(ParsedNode);
|
||||
pn.* = .{
|
||||
@@ -293,8 +293,8 @@ fn createCommentCallback(ctx: *anyopaque, str: h5e.StringSlice) callconv(.c) ?*a
|
||||
};
|
||||
}
|
||||
fn _createCommentCallback(self: *Parser, str: []const u8) !*anyopaque {
|
||||
const page = self.page;
|
||||
const node = try page.createComment(str);
|
||||
const frame = self.frame;
|
||||
const node = try frame.createComment(str);
|
||||
const pn = try self.arena.create(ParsedNode);
|
||||
pn.* = .{
|
||||
.data = null,
|
||||
@@ -311,8 +311,8 @@ fn createProcessingInstruction(ctx: *anyopaque, target: h5e.StringSlice, data: h
|
||||
};
|
||||
}
|
||||
fn _createProcessingInstruction(self: *Parser, target: []const u8, data: []const u8) !*anyopaque {
|
||||
const page = self.page;
|
||||
const node = try page.createProcessingInstruction(target, data);
|
||||
const frame = self.frame;
|
||||
const node = try frame.createProcessingInstruction(target, data);
|
||||
const pn = try self.arena.create(ParsedNode);
|
||||
pn.* = .{
|
||||
.data = null,
|
||||
@@ -328,19 +328,19 @@ fn appendDoctypeToDocument(ctx: *anyopaque, name: h5e.StringSlice, public_id: h5
|
||||
};
|
||||
}
|
||||
fn _appendDoctypeToDocument(self: *Parser, name: []const u8, public_id: []const u8, system_id: []const u8) !void {
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
// Create the DocumentType node
|
||||
const DocumentType = @import("../webapi/DocumentType.zig");
|
||||
const doctype = try page._factory.node(DocumentType{
|
||||
const doctype = try frame._factory.node(DocumentType{
|
||||
._proto = undefined,
|
||||
._name = try page.dupeString(name),
|
||||
._public_id = try page.dupeString(public_id),
|
||||
._system_id = try page.dupeString(system_id),
|
||||
._name = try frame.dupeString(name),
|
||||
._public_id = try frame.dupeString(public_id),
|
||||
._system_id = try frame.dupeString(system_id),
|
||||
});
|
||||
|
||||
// Append it to the document
|
||||
try page.appendNew(self.container.node, .{ .node = doctype.asNode() });
|
||||
try frame.appendNew(self.container.node, .{ .node = doctype.asNode() });
|
||||
}
|
||||
|
||||
fn addAttrsIfMissingCallback(ctx: *anyopaque, target_ref: *anyopaque, attributes: h5e.AttributeIterator) callconv(.c) void {
|
||||
@@ -351,14 +351,14 @@ fn addAttrsIfMissingCallback(ctx: *anyopaque, target_ref: *anyopaque, attributes
|
||||
}
|
||||
fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.AttributeIterator) !void {
|
||||
const element = node.as(Element);
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
const attr_list = try element.getOrCreateAttributeList(page);
|
||||
const attr_list = try element.getOrCreateAttributeList(frame);
|
||||
while (attributes.next()) |attr| {
|
||||
const name = attr.name.local.slice();
|
||||
const value = attr.value.slice();
|
||||
// putNew only adds if the attribute doesn't already exist
|
||||
try attr_list.putNew(name, value, page);
|
||||
try attr_list.putNew(name, value, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,11 +412,11 @@ fn _appendCallback(self: *Parser, parent: *Node, node_or_text: h5e.NodeOrText) !
|
||||
if (comptime IS_DEBUG) {
|
||||
unreachable;
|
||||
}
|
||||
self.page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent.isConnected() });
|
||||
self.frame.removeNode(previous_parent, child, .{ .will_be_reconnected = parent.isConnected() });
|
||||
}
|
||||
try self.page.appendNew(parent, .{ .node = child });
|
||||
try self.frame.appendNew(parent, .{ .node = child });
|
||||
},
|
||||
.text => |txt| try self.page.appendNew(parent, .{ .text = txt }),
|
||||
.text => |txt| try self.frame.appendNew(parent, .{ .text = txt }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ fn removeFromParentCallback(ctx: *anyopaque, target_ref: *anyopaque) callconv(.c
|
||||
}
|
||||
fn _removeFromParentCallback(self: *Parser, node: *Node) !void {
|
||||
const parent = node.parentNode() orelse return;
|
||||
_ = try parent.removeChild(node, self.page);
|
||||
_ = try parent.removeChild(node, self.frame);
|
||||
}
|
||||
|
||||
fn reparentChildrenCallback(ctx: *anyopaque, node_ref: *anyopaque, new_parent_ref: *anyopaque) callconv(.c) void {
|
||||
@@ -438,7 +438,7 @@ fn reparentChildrenCallback(ctx: *anyopaque, node_ref: *anyopaque, new_parent_re
|
||||
};
|
||||
}
|
||||
fn _reparentChildrenCallback(self: *Parser, node: *Node, new_parent: *Node) !void {
|
||||
try self.page.appendAllChildren(node, new_parent);
|
||||
try self.frame.appendAllChildren(node, new_parent);
|
||||
}
|
||||
|
||||
fn appendBeforeSiblingCallback(ctx: *anyopaque, sibling_ref: *anyopaque, node_or_text: h5e.NodeOrText) callconv(.c) void {
|
||||
@@ -456,13 +456,13 @@ fn _appendBeforeSiblingCallback(self: *Parser, sibling: *Node, node_or_text: h5e
|
||||
// A custom element constructor may have inserted the node into the
|
||||
// DOM before the parser officially places it (e.g. via foster
|
||||
// parenting). Detach it first so insertNodeRelative's assertion holds.
|
||||
self.page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent.isConnected() });
|
||||
self.frame.removeNode(previous_parent, child, .{ .will_be_reconnected = parent.isConnected() });
|
||||
}
|
||||
break :blk child;
|
||||
},
|
||||
.text => |txt| try self.page.createTextNode(txt),
|
||||
.text => |txt| try self.frame.createTextNode(txt),
|
||||
};
|
||||
try self.page.insertNodeRelative(parent, node, .{ .before = sibling }, .{});
|
||||
try self.frame.insertNodeRelative(parent, node, .{ .before = sibling }, .{});
|
||||
}
|
||||
|
||||
fn appendBasedOnParentNodeCallback(ctx: *anyopaque, element_ref: *anyopaque, prev_element_ref: *anyopaque, node_or_text: h5e.NodeOrText) callconv(.c) void {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const URL = @import("URL.zig");
|
||||
const TreeWalker = @import("webapi/TreeWalker.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
@@ -132,11 +132,11 @@ fn writeProperties(jw: anytype, properties: []const Property) !void {
|
||||
try jw.endObject();
|
||||
}
|
||||
|
||||
/// Extract all structured data from the page.
|
||||
/// Extract all structured data from the frame.
|
||||
pub fn collectStructuredData(
|
||||
root: *Node,
|
||||
arena: Allocator,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !StructuredData {
|
||||
var json_ld: std.ArrayList([]const u8) = .empty;
|
||||
var open_graph: std.ArrayList(Property) = .empty;
|
||||
@@ -175,7 +175,7 @@ pub fn collectStructuredData(
|
||||
},
|
||||
.meta => collectMeta(el, &open_graph, &twitter_card, &meta, arena) catch {},
|
||||
.title => try collectTitle(node, arena, &meta),
|
||||
.link => try collectLink(el, arena, page, &links, &alternate),
|
||||
.link => try collectLink(el, arena, frame, &links, &alternate),
|
||||
// Skip body subtree for non-JSON-LD — all other metadata is in <head>.
|
||||
// JSON-LD can appear in <body> so we don't skip the whole body.
|
||||
else => {},
|
||||
@@ -282,13 +282,13 @@ fn collectTitle(
|
||||
fn collectLink(
|
||||
el: *Element,
|
||||
arena: Allocator,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
links: *std.ArrayList(Property),
|
||||
alternate: *std.ArrayList(AlternateLink),
|
||||
) !void {
|
||||
const rel = el.getAttributeSafe(comptime .wrap("rel")) orelse return;
|
||||
const raw_href = el.getAttributeSafe(comptime .wrap("href")) orelse return;
|
||||
const href = URL.resolve(arena, page.base(), raw_href, .{ .encoding = page.charset }) catch raw_href;
|
||||
const href = URL.resolve(arena, frame.base(), raw_href, .{ .encoding = frame.charset }) catch raw_href;
|
||||
|
||||
if (std.ascii.eqlIgnoreCase(rel, "alternate")) {
|
||||
try alternate.append(arena, .{
|
||||
@@ -318,14 +318,14 @@ fn collectLink(
|
||||
const testing = @import("../testing.zig");
|
||||
|
||||
fn testStructuredData(html: []const u8) !StructuredData {
|
||||
const page = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
|
||||
const doc = page.window._document;
|
||||
const div = try doc.createElement("div", null, page);
|
||||
try page.parseHtmlAsChildren(div.asNode(), html);
|
||||
const doc = frame.window._document;
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
try frame.parseHtmlAsChildren(div.asNode(), html);
|
||||
|
||||
return collectStructuredData(div.asNode(), page.call_arena, page);
|
||||
return collectStructuredData(div.asNode(), frame.call_arena, frame);
|
||||
}
|
||||
|
||||
fn findProperty(props: []const Property, key: []const u8) ?[]const u8 {
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
let byTagNameAll = document.getElementsByTagName('*');
|
||||
// If you add a script block (or change the HTML in any other way on this
|
||||
// page), this test will break. Adjust it accordingly.
|
||||
// frame), this test will break. Adjust it accordingly.
|
||||
testing.expectEqual(12, byTagNameAll.length);
|
||||
testing.expectEqual('html', byTagNameAll.item(0).localName);
|
||||
testing.expectEqual('SCRIPT', byTagNameAll.item(11).tagName);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
// This is pretty straightforward, with the only complexity coming from "eventually"
|
||||
// assertions, which are assertions we lazily check in `getStatus()`. We
|
||||
// do this because, by the time `getStatus()`, `page.wait()` will have been called
|
||||
// do this because, by the time `getStatus()`, `frame.wait()` will have been called
|
||||
// and any timer (setTimeout, requestAnimation, MutationObserver, etc...) will
|
||||
// have been evaluated. Test which use/test these behavior will use `eventually`.
|
||||
(() => {
|
||||
@@ -87,7 +87,7 @@
|
||||
}
|
||||
|
||||
// Set expectations to happen at some point in the future. Necessary for
|
||||
// testing callbacks which will only be executed after page.wait is called.
|
||||
// testing callbacks which will only be executed after frame.wait is called.
|
||||
function eventually(fn) {
|
||||
// capture the current state (script id, stack) so that, when we do run this
|
||||
// we can display more meaningful details on failure.
|
||||
|
||||
@@ -302,7 +302,7 @@ pub const tool_defs = [_]ToolDef{
|
||||
};
|
||||
|
||||
pub const ToolError = error{
|
||||
PageNotLoaded,
|
||||
FrameNotLoaded,
|
||||
InvalidParams,
|
||||
NodeNotFound,
|
||||
NavigationFailed,
|
||||
@@ -327,7 +327,7 @@ pub const UrlParams = struct {
|
||||
waitUntil: ?lp.Config.WaitUntil = null,
|
||||
};
|
||||
|
||||
const NodeAndPage = struct { node: *DOMNode, page: *lp.Page };
|
||||
const NodeAndPage = struct { node: *DOMNode, page: *lp.Frame };
|
||||
|
||||
pub const Action = enum {
|
||||
goto,
|
||||
@@ -438,7 +438,7 @@ fn execTree(session: *lp.Session, registry: *CDPNode.Registry, arena: std.mem.Al
|
||||
const st = lp.SemanticTree{
|
||||
.dom_node = root_node,
|
||||
.registry = registry,
|
||||
.page = page,
|
||||
.frame = page,
|
||||
.arena = arena,
|
||||
.prune = true,
|
||||
.max_depth = args.maxDepth orelse std.math.maxInt(u32) - 1,
|
||||
@@ -453,7 +453,7 @@ fn execNodeDetails(session: *lp.Session, registry: *CDPNode.Registry, arena: std
|
||||
const Params = struct { backendNodeId: CDPNode.Id };
|
||||
const args = try parseArgsOrErr(Params, arena, arguments) orelse return ToolError.InvalidParams;
|
||||
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
|
||||
const node = registry.lookup_by_id.get(args.backendNodeId) orelse
|
||||
return ToolError.NodeNotFound;
|
||||
@@ -558,7 +558,7 @@ fn execClick(session: *lp.Session, registry: *CDPNode.Registry, arena: std.mem.A
|
||||
runner.wait(.{ .ms = 10000, .until = .done }) catch return ToolError.NavigationFailed;
|
||||
}
|
||||
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
const page_title = page.getTitle() catch null;
|
||||
if (args.selector) |sel| {
|
||||
return std.fmt.allocPrint(arena, "Clicked element (selector: {s}). Page url: {s}, title: {s}", .{
|
||||
@@ -617,7 +617,7 @@ fn execScroll(session: *lp.Session, registry: *CDPNode.Registry, arena: std.mem.
|
||||
y: ?i32 = null,
|
||||
};
|
||||
const args = try parseArgsOrDefault(Params, arena, arguments);
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
|
||||
var target_node: ?*DOMNode = null;
|
||||
if (args.backendNodeId) |node_id| {
|
||||
@@ -646,7 +646,7 @@ fn execWaitForSelector(session: *lp.Session, registry: *CDPNode.Registry, arena:
|
||||
};
|
||||
const args = try parseArgsOrErr(Params, arena, arguments) orelse return ToolError.InvalidParams;
|
||||
|
||||
_ = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
_ = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
|
||||
const timeout_ms = args.timeout orelse 5000;
|
||||
|
||||
@@ -697,7 +697,7 @@ fn execPress(session: *lp.Session, registry: *CDPNode.Registry, arena: std.mem.A
|
||||
};
|
||||
const args = try parseArgsOrErr(Params, arena, arguments) orelse return ToolError.InvalidParams;
|
||||
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
|
||||
var target_node: ?*DOMNode = null;
|
||||
if (args.backendNodeId) |node_id| {
|
||||
@@ -716,7 +716,7 @@ fn execPress(session: *lp.Session, registry: *CDPNode.Registry, arena: std.mem.A
|
||||
runner.wait(.{ .ms = 10000, .until = .done }) catch return ToolError.NavigationFailed;
|
||||
}
|
||||
|
||||
const current_page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const current_page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
const page_title = current_page.getTitle() catch null;
|
||||
return std.fmt.allocPrint(arena, "Pressed key '{s}'. Page url: {s}, title: {s}", .{
|
||||
args.key,
|
||||
@@ -801,7 +801,7 @@ fn execFindElement(session: *lp.Session, registry: *CDPNode.Registry, arena: std
|
||||
|
||||
if (args.role == null and args.name == null) return ToolError.InvalidParams;
|
||||
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
|
||||
const elements = lp.interactive.collectInteractiveElements(page.document.asNode(), arena, page) catch
|
||||
return ToolError.InternalError;
|
||||
@@ -841,7 +841,7 @@ fn execConsoleLogs(
|
||||
session: *lp.Session,
|
||||
arena: std.mem.Allocator,
|
||||
) ToolError![]const u8 {
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
const messages = page.drainConsoleMessages();
|
||||
if (messages.len == 0) return "No console messages.";
|
||||
|
||||
@@ -854,7 +854,7 @@ fn execConsoleLogs(
|
||||
}
|
||||
|
||||
fn execGetUrl(session: *lp.Session) ToolError![]const u8 {
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
return page.url;
|
||||
}
|
||||
|
||||
@@ -874,19 +874,19 @@ fn execGetCookies(session: *lp.Session, arena: std.mem.Allocator) ToolError![]co
|
||||
return aw.written();
|
||||
}
|
||||
|
||||
fn ensurePage(session: *lp.Session, registry: *CDPNode.Registry, url: ?[:0]const u8, timeout: ?u32, waitUntil: ?lp.Config.WaitUntil) ToolError!*lp.Page {
|
||||
fn ensurePage(session: *lp.Session, registry: *CDPNode.Registry, url: ?[:0]const u8, timeout: ?u32, waitUntil: ?lp.Config.WaitUntil) ToolError!*lp.Frame {
|
||||
if (url) |u| {
|
||||
try performGoto(session, registry, u, timeout, waitUntil);
|
||||
}
|
||||
return session.currentPage() orelse ToolError.PageNotLoaded;
|
||||
return session.currentFrame() orelse ToolError.FrameNotLoaded;
|
||||
}
|
||||
|
||||
fn performGoto(session: *lp.Session, registry: *CDPNode.Registry, url: [:0]const u8, timeout: ?u32, waitUntil: ?lp.Config.WaitUntil) ToolError!void {
|
||||
if (session.page != null) {
|
||||
if (session.frame != null) {
|
||||
registry.reset();
|
||||
session.removePage();
|
||||
session.removeFrame();
|
||||
}
|
||||
const page = session.createPage() catch return ToolError.NavigationFailed;
|
||||
const page = session.createFrame() catch return ToolError.NavigationFailed;
|
||||
_ = page.navigate(url, .{
|
||||
.reason = .address_bar,
|
||||
.kind = .{ .push = null },
|
||||
@@ -900,13 +900,13 @@ fn performGoto(session: *lp.Session, registry: *CDPNode.Registry, url: [:0]const
|
||||
}
|
||||
|
||||
fn resolveNodeAndPage(session: *lp.Session, registry: *CDPNode.Registry, node_id: CDPNode.Id) ToolError!NodeAndPage {
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
const node = registry.lookup_by_id.get(node_id) orelse return ToolError.NodeNotFound;
|
||||
return .{ .node = node.dom, .page = page };
|
||||
}
|
||||
|
||||
fn resolveBySelector(session: *lp.Session, selector: []const u8) ToolError!NodeAndPage {
|
||||
const page = session.currentPage() orelse return ToolError.PageNotLoaded;
|
||||
const page = session.currentFrame() orelse return ToolError.FrameNotLoaded;
|
||||
const element = Selector.querySelector(page.document.asNode(), selector, page) catch return ToolError.InvalidParams;
|
||||
const node = (element orelse return ToolError.NodeNotFound).asNode();
|
||||
return .{ .node = node, .page = page };
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const AbortSignal = @import("AbortSignal.zig");
|
||||
|
||||
const AbortController = @This();
|
||||
|
||||
_signal: *AbortSignal,
|
||||
|
||||
pub fn init(page: *Page) !*AbortController {
|
||||
const signal = try AbortSignal.init(page);
|
||||
return page._factory.create(AbortController{
|
||||
pub fn init(frame: *Frame) !*AbortController {
|
||||
const signal = try AbortSignal.init(frame);
|
||||
return frame._factory.create(AbortController{
|
||||
._signal = signal,
|
||||
});
|
||||
}
|
||||
@@ -37,8 +37,8 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
|
||||
return self._signal;
|
||||
}
|
||||
|
||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, frame: *Frame) !void {
|
||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 Event = @import("Event.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
@@ -34,8 +34,8 @@ _aborted: bool = false,
|
||||
_reason: Reason = .undefined,
|
||||
_on_abort: ?js.Function.Global = null,
|
||||
|
||||
pub fn init(page: *Page) !*AbortSignal {
|
||||
return page._factory.eventTarget(AbortSignal{
|
||||
pub fn init(frame: *Frame) !*AbortSignal {
|
||||
return frame._factory.eventTarget(AbortSignal{
|
||||
._proto = undefined,
|
||||
});
|
||||
}
|
||||
@@ -60,7 +60,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, frame: *Frame) !void {
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
if (reason_) |reason| {
|
||||
switch (reason) {
|
||||
.js_val => |js_val| self._reason = .{ .js_val = js_val },
|
||||
.string => |str| self._reason = .{ .string = try page.dupeString(str) },
|
||||
.string => |str| self._reason = .{ .string = try frame.dupeString(str) },
|
||||
.undefined => self._reason = reason,
|
||||
}
|
||||
} else {
|
||||
@@ -80,27 +80,27 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||
|
||||
// Dispatch abort event
|
||||
const target = self.asEventTarget();
|
||||
if (page._event_manager.hasDirectListeners(target, "abort", self._on_abort)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, page);
|
||||
try page._event_manager.dispatchDirect(target, event, self._on_abort, .{ .context = "abort signal" });
|
||||
if (frame._event_manager.hasDirectListeners(target, "abort", self._on_abort)) {
|
||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, frame);
|
||||
try frame._event_manager.dispatchDirect(target, event, self._on_abort, .{ .context = "abort signal" });
|
||||
}
|
||||
}
|
||||
|
||||
// Static method to create an already-aborted signal
|
||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||
const signal = try init(page);
|
||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||
pub fn createAborted(reason_: ?js.Value.Global, frame: *Frame) !*AbortSignal {
|
||||
const signal = try init(frame);
|
||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, frame);
|
||||
return signal;
|
||||
}
|
||||
|
||||
pub fn createTimeout(delay: u32, page: *Page) !*AbortSignal {
|
||||
const callback = try page.arena.create(TimeoutCallback);
|
||||
pub fn createTimeout(delay: u32, frame: *Frame) !*AbortSignal {
|
||||
const callback = try frame.arena.create(TimeoutCallback);
|
||||
callback.* = .{
|
||||
.page = page,
|
||||
.signal = try init(page),
|
||||
.frame = frame,
|
||||
.signal = try init(frame),
|
||||
};
|
||||
|
||||
try page.js.scheduler.add(callback, TimeoutCallback.run, delay, .{
|
||||
try frame.js.scheduler.add(callback, TimeoutCallback.run, delay, .{
|
||||
.name = "AbortSignal.timeout",
|
||||
});
|
||||
|
||||
@@ -111,8 +111,8 @@ const ThrowIfAborted = union(enum) {
|
||||
exception: js.Exception,
|
||||
undefined: void,
|
||||
};
|
||||
pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted {
|
||||
const local = page.js.local.?;
|
||||
pub fn throwIfAborted(self: *const AbortSignal, frame: *Frame) !ThrowIfAborted {
|
||||
const local = frame.js.local.?;
|
||||
|
||||
if (self._aborted) {
|
||||
const exception = switch (self._reason) {
|
||||
@@ -132,12 +132,12 @@ const Reason = union(enum) {
|
||||
};
|
||||
|
||||
const TimeoutCallback = struct {
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
signal: *AbortSignal,
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
||||
self.signal.abort(.{ .string = "TimeoutError" }, self.page) catch |err| {
|
||||
self.signal.abort(.{ .string = "TimeoutError" }, self.frame) catch |err| {
|
||||
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||
};
|
||||
return null;
|
||||
|
||||
@@ -35,10 +35,10 @@ pub const _prototype_root = true;
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_type: Type,
|
||||
_page_id: u32,
|
||||
_arena: Allocator,
|
||||
_end_offset: u32,
|
||||
_start_offset: u32,
|
||||
_frame_loader_id: u32,
|
||||
_end_container: *Node,
|
||||
_start_container: *Node,
|
||||
|
||||
@@ -50,8 +50,8 @@ pub fn acquireRef(self: *AbstractRange) void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *AbstractRange, session: *Session) void {
|
||||
if (session.findPageById(self._page_id)) |page| {
|
||||
page._live_ranges.remove(&self._range_link);
|
||||
if (session.findFrameByLoaderId(self._frame_loader_id)) |frame| {
|
||||
frame._live_ranges.remove(&self._range_link);
|
||||
}
|
||||
session.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Session = @import("../Session.zig");
|
||||
|
||||
const Mime = @import("../Mime.zig");
|
||||
|
||||
const Writer = std.Io.Writer;
|
||||
const Execution = js.Execution;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// https://w3c.github.io/FileAPI/#blob-section
|
||||
@@ -61,9 +61,9 @@ 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, page: *Page) !*Blob {
|
||||
const arena = try page.getArena(.large, "Blob");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(parts_: ?[]const js.Value, opts_: ?InitOptions, session: *Session) !*Blob {
|
||||
const arena = try session.getArena(.large, "Blob");
|
||||
errdefer session.releaseArena(arena);
|
||||
|
||||
const opts: InitOptions = opts_ orelse .{};
|
||||
const mime = try validateMimeType(arena, opts.type, false);
|
||||
@@ -259,29 +259,29 @@ fn writePartWithEndings(part: []const u8, use_native_endings: bool, writer: *Wri
|
||||
|
||||
/// Returns a Promise that resolves with the contents of the blob
|
||||
/// as binary data contained in an ArrayBuffer.
|
||||
pub fn arrayBuffer(self: *const Blob, page: *Page) !js.Promise {
|
||||
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = self._slice });
|
||||
pub fn arrayBuffer(self: *const Blob, exec: *Execution) !js.Promise {
|
||||
return exec.context.local.?.resolvePromise(js.ArrayBuffer{ .values = self._slice });
|
||||
}
|
||||
|
||||
const ReadableStream = @import("streams/ReadableStream.zig");
|
||||
/// Returns a ReadableStream which upon reading returns the data
|
||||
/// contained within the Blob.
|
||||
pub fn stream(self: *const Blob, page: *Page) !*ReadableStream {
|
||||
return ReadableStream.initWithData(self._slice, page);
|
||||
pub fn stream(self: *const Blob, exec: *Execution) !*ReadableStream {
|
||||
return ReadableStream.initWithData(self._slice, exec);
|
||||
}
|
||||
|
||||
/// Returns a Promise that resolves with a string containing
|
||||
/// the contents of the blob, interpreted as UTF-8.
|
||||
pub fn text(self: *const Blob, page: *Page) !js.Promise {
|
||||
return page.js.local.?.resolvePromise(self._slice);
|
||||
pub fn text(self: *const Blob, exec: *Execution) !js.Promise {
|
||||
return exec.context.local.?.resolvePromise(self._slice);
|
||||
}
|
||||
|
||||
/// Extension to Blob; works on Firefox and Safari.
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/Blob/bytes
|
||||
/// Returns a Promise that resolves with a Uint8Array containing
|
||||
/// the contents of the blob as an array of bytes.
|
||||
pub fn bytes(self: *const Blob, page: *Page) !js.Promise {
|
||||
return page.js.local.?.resolvePromise(js.TypedArray(u8){ .values = self._slice });
|
||||
pub fn bytes(self: *const Blob, exec: *Execution) !js.Promise {
|
||||
return exec.context.local.?.resolvePromise(js.TypedArray(u8){ .values = self._slice });
|
||||
}
|
||||
|
||||
/// Returns a new Blob object which contains data
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 Node = @import("Node.zig");
|
||||
pub const Text = @import("cdata/Text.zig");
|
||||
@@ -226,26 +226,26 @@ pub fn render(self: *const CData, writer: *std.io.Writer, opts: RenderOpts) !boo
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn setData(self: *CData, value: ?[]const u8, page: *Page) !void {
|
||||
pub fn setData(self: *CData, value: ?[]const u8, frame: *Frame) !void {
|
||||
const old_value = self._data;
|
||||
|
||||
if (value) |v| {
|
||||
self._data = try page.dupeSSO(v);
|
||||
self._data = try frame.dupeSSO(v);
|
||||
} else {
|
||||
self._data = .empty;
|
||||
}
|
||||
|
||||
page.characterDataChange(self.asNode(), old_value);
|
||||
frame.characterDataChange(self.asNode(), old_value);
|
||||
}
|
||||
|
||||
/// JS bridge wrapper for `data` setter.
|
||||
/// Per spec, setting .data runs replaceData(0, this.length, value),
|
||||
/// which includes live range updates.
|
||||
/// Handles [LegacyNullToEmptyString]: null → "" per spec.
|
||||
pub fn _setData(self: *CData, value: js.Value, page: *Page) !void {
|
||||
pub fn _setData(self: *CData, value: js.Value, frame: *Frame) !void {
|
||||
const new_value: []const u8 = if (value.isNull()) "" else try value.toZig([]const u8);
|
||||
const length = self.getLength();
|
||||
try self.replaceData(0, length, new_value, page);
|
||||
try self.replaceData(0, length, new_value, frame);
|
||||
}
|
||||
|
||||
pub fn format(self: *const CData, writer: *std.io.Writer) !void {
|
||||
@@ -277,70 +277,70 @@ pub fn isEqualNode(self: *const CData, other: *const CData) bool {
|
||||
return self._data.eql(other._data);
|
||||
}
|
||||
|
||||
pub fn appendData(self: *CData, data: []const u8, page: *Page) !void {
|
||||
pub fn appendData(self: *CData, data: []const u8, frame: *Frame) !void {
|
||||
// Per DOM spec, appendData(data) is replaceData(length, 0, data).
|
||||
const length = self.getLength();
|
||||
try self.replaceData(length, 0, data, page);
|
||||
try self.replaceData(length, 0, data, frame);
|
||||
}
|
||||
|
||||
pub fn deleteData(self: *CData, offset: usize, count: usize, page: *Page) !void {
|
||||
pub fn deleteData(self: *CData, offset: usize, count: usize, frame: *Frame) !void {
|
||||
const end_utf16 = std.math.add(usize, offset, count) catch std.math.maxInt(usize);
|
||||
const range = try utf16RangeToUtf8(self._data.str(), offset, end_utf16);
|
||||
|
||||
// Update live ranges per DOM spec replaceData steps (deleteData = replaceData with data="")
|
||||
const length = self.getLength();
|
||||
const effective_count: u32 = @intCast(@min(count, length - offset));
|
||||
page.updateRangesForCharacterDataReplace(self.asNode(), @intCast(offset), effective_count, 0);
|
||||
frame.updateRangesForCharacterDataReplace(self.asNode(), @intCast(offset), effective_count, 0);
|
||||
|
||||
const old_data = self._data;
|
||||
const old_value = old_data.str();
|
||||
if (range.start == 0) {
|
||||
self._data = try page.dupeSSO(old_value[range.end..]);
|
||||
self._data = try frame.dupeSSO(old_value[range.end..]);
|
||||
} else if (range.end >= old_value.len) {
|
||||
self._data = try page.dupeSSO(old_value[0..range.start]);
|
||||
self._data = try frame.dupeSSO(old_value[0..range.start]);
|
||||
} else {
|
||||
// Deleting from middle - concat prefix and suffix
|
||||
self._data = try String.concat(page.arena, &.{
|
||||
self._data = try String.concat(frame.arena, &.{
|
||||
old_value[0..range.start],
|
||||
old_value[range.end..],
|
||||
});
|
||||
}
|
||||
page.characterDataChange(self.asNode(), old_data);
|
||||
frame.characterDataChange(self.asNode(), old_data);
|
||||
}
|
||||
|
||||
pub fn insertData(self: *CData, offset: usize, data: []const u8, page: *Page) !void {
|
||||
pub fn insertData(self: *CData, offset: usize, data: []const u8, frame: *Frame) !void {
|
||||
const byte_offset = try utf16OffsetToUtf8(self._data.str(), offset);
|
||||
|
||||
// Update live ranges per DOM spec replaceData steps (insertData = replaceData with count=0)
|
||||
page.updateRangesForCharacterDataReplace(self.asNode(), @intCast(offset), 0, @intCast(utf16Len(data)));
|
||||
frame.updateRangesForCharacterDataReplace(self.asNode(), @intCast(offset), 0, @intCast(utf16Len(data)));
|
||||
|
||||
const old_value = self._data;
|
||||
const existing = old_value.str();
|
||||
self._data = try String.concat(page.arena, &.{
|
||||
self._data = try String.concat(frame.arena, &.{
|
||||
existing[0..byte_offset],
|
||||
data,
|
||||
existing[byte_offset..],
|
||||
});
|
||||
page.characterDataChange(self.asNode(), old_value);
|
||||
frame.characterDataChange(self.asNode(), old_value);
|
||||
}
|
||||
|
||||
pub fn replaceData(self: *CData, offset: usize, count: usize, data: []const u8, page: *Page) !void {
|
||||
pub fn replaceData(self: *CData, offset: usize, count: usize, data: []const u8, frame: *Frame) !void {
|
||||
const end_utf16 = std.math.add(usize, offset, count) catch std.math.maxInt(usize);
|
||||
const range = try utf16RangeToUtf8(self._data.str(), offset, end_utf16);
|
||||
|
||||
// Update live ranges per DOM spec replaceData steps
|
||||
const length = self.getLength();
|
||||
const effective_count: u32 = @intCast(@min(count, length - offset));
|
||||
page.updateRangesForCharacterDataReplace(self.asNode(), @intCast(offset), effective_count, @intCast(utf16Len(data)));
|
||||
frame.updateRangesForCharacterDataReplace(self.asNode(), @intCast(offset), effective_count, @intCast(utf16Len(data)));
|
||||
|
||||
const old_value = self._data;
|
||||
const existing = old_value.str();
|
||||
self._data = try String.concat(page.arena, &.{
|
||||
self._data = try String.concat(frame.arena, &.{
|
||||
existing[0..range.start],
|
||||
data,
|
||||
existing[range.end..],
|
||||
});
|
||||
page.characterDataChange(self.asNode(), old_value);
|
||||
frame.characterDataChange(self.asNode(), old_value);
|
||||
}
|
||||
|
||||
pub fn substringData(self: *const CData, offset: usize, count: usize) ![]const u8 {
|
||||
@@ -349,49 +349,49 @@ pub fn substringData(self: *const CData, offset: usize, count: usize) ![]const u
|
||||
return self._data.str()[range.start..range.end];
|
||||
}
|
||||
|
||||
pub fn remove(self: *CData, page: *Page) !void {
|
||||
pub fn remove(self: *CData, frame: *Frame) !void {
|
||||
const node = self.asNode();
|
||||
const parent = node.parentNode() orelse return;
|
||||
_ = try parent.removeChild(node, page);
|
||||
_ = try parent.removeChild(node, frame);
|
||||
}
|
||||
|
||||
pub fn before(self: *CData, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn before(self: *CData, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
const node = self.asNode();
|
||||
const parent = node.parentNode() orelse return;
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(page);
|
||||
_ = try parent.insertBefore(child, node, page);
|
||||
const child = try node_or_text.toNode(frame);
|
||||
_ = try parent.insertBefore(child, node, frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn after(self: *CData, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn after(self: *CData, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
const node = self.asNode();
|
||||
const parent = node.parentNode() orelse return;
|
||||
const viable_next = Node.NodeOrText.viableNextSibling(node, nodes);
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(page);
|
||||
_ = try parent.insertBefore(child, viable_next, page);
|
||||
const child = try node_or_text.toNode(frame);
|
||||
_ = try parent.insertBefore(child, viable_next, frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replaceWith(self: *CData, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn replaceWith(self: *CData, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
const ref_node = self.asNode();
|
||||
const parent = ref_node.parentNode() orelse return;
|
||||
|
||||
var rm_ref_node = true;
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(page);
|
||||
const child = try node_or_text.toNode(frame);
|
||||
if (child == ref_node) {
|
||||
rm_ref_node = false;
|
||||
continue;
|
||||
}
|
||||
_ = try parent.insertBefore(child, ref_node, page);
|
||||
_ = try parent.insertBefore(child, ref_node, frame);
|
||||
}
|
||||
|
||||
if (rm_ref_node) {
|
||||
_ = try parent.removeChild(ref_node, page);
|
||||
_ = try parent.removeChild(ref_node, frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const CSS = @This();
|
||||
_pad: bool = false,
|
||||
@@ -40,7 +40,7 @@ pub fn parseDimension(value: []const u8) ?f64 {
|
||||
|
||||
/// Escapes a CSS identifier string
|
||||
/// https://drafts.csswg.org/cssom/#the-css.escape()-method
|
||||
pub fn escape(_: *const CSS, value: []const u8, page: *Page) ![]const u8 {
|
||||
pub fn escape(_: *const CSS, value: []const u8, frame: *Frame) ![]const u8 {
|
||||
if (value.len == 0) {
|
||||
return "";
|
||||
}
|
||||
@@ -65,7 +65,7 @@ pub fn escape(_: *const CSS, value: []const u8, page: *Page) ![]const u8 {
|
||||
return value;
|
||||
}
|
||||
|
||||
const result = try page.call_arena.alloc(u8, out_len);
|
||||
const result = try frame.call_arena.alloc(u8, out_len);
|
||||
var pos: usize = 0;
|
||||
|
||||
if (needsEscape(true, first)) {
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 logger = lp.log;
|
||||
|
||||
const Console = @This();
|
||||
@@ -149,12 +149,12 @@ fn timestamp() u64 {
|
||||
return @import("../../datetime.zig").timestamp(.monotonic);
|
||||
}
|
||||
|
||||
// Forwards page-context console output to the Page's message buffer (read by
|
||||
// Forwards frame-context console output to the Frame's message buffer (read by
|
||||
// the `consoleLogs` tool / CDP Runtime.consoleAPICalled). Worker contexts are
|
||||
// dropped — no buffer is attached there.
|
||||
fn appendMessage(exec: *js.Execution, level: Page.ConsoleMessage.Level, values: []js.Value) void {
|
||||
fn appendMessage(exec: *js.Execution, level: Frame.ConsoleMessage.Level, values: []js.Value) void {
|
||||
switch (exec.context.global) {
|
||||
.page => |p| p.appendConsoleMessage(level, values),
|
||||
.frame => |f| f.appendConsoleMessage(level, values),
|
||||
.worker => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 Node = @import("Node.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -38,7 +38,7 @@ const DefineOptions = struct {
|
||||
extends: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Function, options_: ?DefineOptions, page: *Page) !void {
|
||||
pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Function, options_: ?DefineOptions, frame: *Frame) !void {
|
||||
const options = options_ orelse DefineOptions{};
|
||||
|
||||
try validateName(name);
|
||||
@@ -55,15 +55,15 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
break :blk tag;
|
||||
} else null;
|
||||
|
||||
const gop = try self._definitions.getOrPut(page.arena, name);
|
||||
const gop = try self._definitions.getOrPut(frame.arena, name);
|
||||
if (gop.found_existing) {
|
||||
// Yes, this is the correct error to return when trying to redefine a name
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
const owned_name = try page.dupeString(name);
|
||||
const owned_name = try frame.dupeString(name);
|
||||
|
||||
const definition = try page._factory.create(CustomElementDefinition{
|
||||
const definition = try frame._factory.create(CustomElementDefinition{
|
||||
.name = owned_name,
|
||||
.constructor = try constructor.persist(),
|
||||
.extends = extends_tag,
|
||||
@@ -75,8 +75,8 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
var js_arr = observed_attrs.toArray();
|
||||
for (0..js_arr.len()) |i| {
|
||||
const attr_val = js_arr.get(@intCast(i)) catch continue;
|
||||
const attr_name = attr_val.toStringSliceWithAlloc(page.arena) catch continue;
|
||||
definition.observed_attributes.put(page.arena, attr_name, {}) catch continue;
|
||||
const attr_name = attr_val.toStringSliceWithAlloc(frame.arena) catch continue;
|
||||
definition.observed_attributes.put(frame.arena, attr_name, {}) catch continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,8 +86,8 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
|
||||
// Upgrade any undefined custom elements with this name
|
||||
var idx: usize = 0;
|
||||
while (idx < page._undefined_custom_elements.items.len) {
|
||||
const custom = page._undefined_custom_elements.items[idx];
|
||||
while (idx < frame._undefined_custom_elements.items.len) {
|
||||
const custom = frame._undefined_custom_elements.items[idx];
|
||||
if (!custom._tag_name.eqlSlice(name)) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -98,16 +98,16 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu
|
||||
continue;
|
||||
}
|
||||
|
||||
upgradeCustomElement(custom, definition, page) catch {
|
||||
_ = page._undefined_custom_elements.swapRemove(idx);
|
||||
upgradeCustomElement(custom, definition, frame) catch {
|
||||
_ = frame._undefined_custom_elements.swapRemove(idx);
|
||||
continue;
|
||||
};
|
||||
|
||||
_ = page._undefined_custom_elements.swapRemove(idx);
|
||||
_ = frame._undefined_custom_elements.swapRemove(idx);
|
||||
}
|
||||
|
||||
if (self._when_defined.fetchRemove(name)) |entry| {
|
||||
page.js.toLocal(entry.value).resolve("whenDefined", constructor);
|
||||
frame.js.toLocal(entry.value).resolve("whenDefined", constructor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,12 +116,12 @@ pub fn get(self: *CustomElementRegistry, name: []const u8) ?js.Function.Global {
|
||||
return definition.constructor;
|
||||
}
|
||||
|
||||
pub fn upgrade(self: *CustomElementRegistry, root: *Node, page: *Page) !void {
|
||||
try upgradeNode(self, root, page);
|
||||
pub fn upgrade(self: *CustomElementRegistry, root: *Node, frame: *Frame) !void {
|
||||
try upgradeNode(self, root, frame);
|
||||
}
|
||||
|
||||
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) !js.Promise {
|
||||
const local = page.js.local.?;
|
||||
pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, frame: *Frame) !js.Promise {
|
||||
const local = frame.js.local.?;
|
||||
if (self._definitions.get(name)) |definition| {
|
||||
return local.resolvePromise(definition.constructor);
|
||||
}
|
||||
@@ -130,12 +130,12 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page)
|
||||
error.SyntaxError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }),
|
||||
};
|
||||
|
||||
const gop = try self._when_defined.getOrPut(page.arena, name);
|
||||
const gop = try self._when_defined.getOrPut(frame.arena, name);
|
||||
if (gop.found_existing) {
|
||||
return local.toLocal(gop.value_ptr.*).promise();
|
||||
}
|
||||
errdefer _ = self._when_defined.remove(name);
|
||||
const owned_name = try page.dupeString(name);
|
||||
const owned_name = try frame.dupeString(name);
|
||||
|
||||
const resolver = local.createPromiseResolver();
|
||||
gop.key_ptr.* = owned_name;
|
||||
@@ -144,20 +144,20 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page)
|
||||
return resolver.promise();
|
||||
}
|
||||
|
||||
fn upgradeNode(self: *CustomElementRegistry, node: *Node, page: *Page) !void {
|
||||
fn upgradeNode(self: *CustomElementRegistry, node: *Node, frame: *Frame) !void {
|
||||
if (node.is(Element)) |element| {
|
||||
try upgradeElement(self, element, page);
|
||||
try upgradeElement(self, element, frame);
|
||||
}
|
||||
|
||||
var it = node.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
try upgradeNode(self, child, page);
|
||||
try upgradeNode(self, child, frame);
|
||||
}
|
||||
}
|
||||
|
||||
fn upgradeElement(self: *CustomElementRegistry, element: *Element, page: *Page) !void {
|
||||
fn upgradeElement(self: *CustomElementRegistry, element: *Element, frame: *Frame) !void {
|
||||
const custom = element.is(Custom) orelse {
|
||||
return Custom.checkAndAttachBuiltIn(element, page);
|
||||
return Custom.checkAndAttachBuiltIn(element, frame);
|
||||
};
|
||||
|
||||
if (custom._definition != null) return;
|
||||
@@ -165,10 +165,10 @@ fn upgradeElement(self: *CustomElementRegistry, element: *Element, page: *Page)
|
||||
const name = custom._tag_name.str();
|
||||
const definition = self._definitions.get(name) orelse return;
|
||||
|
||||
try upgradeCustomElement(custom, definition, page);
|
||||
try upgradeCustomElement(custom, definition, frame);
|
||||
}
|
||||
|
||||
pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinition, page: *Page) !void {
|
||||
pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinition, frame: *Frame) !void {
|
||||
custom._definition = definition;
|
||||
|
||||
// Reset callback flags since this is a fresh upgrade
|
||||
@@ -176,12 +176,12 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
|
||||
custom._disconnected_callback_invoked = false;
|
||||
|
||||
const node = custom.asNode();
|
||||
const prev_upgrading = page._upgrading_element;
|
||||
page._upgrading_element = node;
|
||||
defer page._upgrading_element = prev_upgrading;
|
||||
const prev_upgrading = frame._upgrading_element;
|
||||
frame._upgrading_element = node;
|
||||
defer frame._upgrading_element = prev_upgrading;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
@@ -195,12 +195,12 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
|
||||
while (attr_it.next()) |attr| {
|
||||
const name = attr._name;
|
||||
if (definition.isAttributeObserved(name)) {
|
||||
custom.invokeAttributeChangedCallback(name, null, attr._value, page);
|
||||
custom.invokeAttributeChangedCallback(name, null, attr._value, frame);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.isConnected()) {
|
||||
custom.invokeConnectedCallback(page);
|
||||
custom.invokeConnectedCallback(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Node = @import("Node.zig");
|
||||
const Document = @import("Document.zig");
|
||||
const DocumentType = @import("DocumentType.zig");
|
||||
@@ -27,60 +27,60 @@ const DocumentType = @import("DocumentType.zig");
|
||||
const DOMImplementation = @This();
|
||||
_pad: bool = false,
|
||||
|
||||
pub fn createDocumentType(_: *const DOMImplementation, qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, page: *Page) !*DocumentType {
|
||||
return DocumentType.init(qualified_name, public_id, system_id, page);
|
||||
pub fn createDocumentType(_: *const DOMImplementation, qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, frame: *Frame) !*DocumentType {
|
||||
return DocumentType.init(qualified_name, public_id, system_id, frame);
|
||||
}
|
||||
|
||||
pub fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, page: *Page) !*Document {
|
||||
const document = (try page._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument();
|
||||
pub fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, frame: *Frame) !*Document {
|
||||
const document = (try frame._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument();
|
||||
document._ready_state = .complete;
|
||||
document._url = "about:blank";
|
||||
|
||||
{
|
||||
const doctype = try page._factory.node(DocumentType{
|
||||
const doctype = try frame._factory.node(DocumentType{
|
||||
._proto = undefined,
|
||||
._name = "html",
|
||||
._public_id = "",
|
||||
._system_id = "",
|
||||
});
|
||||
_ = try document.asNode().appendChild(doctype.asNode(), page);
|
||||
_ = try document.asNode().appendChild(doctype.asNode(), frame);
|
||||
}
|
||||
|
||||
const html_node = try page.createElementNS(.html, "html", null);
|
||||
_ = try document.asNode().appendChild(html_node, page);
|
||||
const html_node = try frame.createElementNS(.html, "html", null);
|
||||
_ = try document.asNode().appendChild(html_node, frame);
|
||||
|
||||
const head_node = try page.createElementNS(.html, "head", null);
|
||||
_ = try html_node.appendChild(head_node, page);
|
||||
const head_node = try frame.createElementNS(.html, "head", null);
|
||||
_ = try html_node.appendChild(head_node, frame);
|
||||
|
||||
if (title) |t| {
|
||||
const title_node = try page.createElementNS(.html, "title", null);
|
||||
_ = try head_node.appendChild(title_node, page);
|
||||
const text_node = try page.createTextNode(t.value);
|
||||
_ = try title_node.appendChild(text_node, page);
|
||||
const title_node = try frame.createElementNS(.html, "title", null);
|
||||
_ = try head_node.appendChild(title_node, frame);
|
||||
const text_node = try frame.createTextNode(t.value);
|
||||
_ = try title_node.appendChild(text_node, frame);
|
||||
}
|
||||
|
||||
const body_node = try page.createElementNS(.html, "body", null);
|
||||
_ = try html_node.appendChild(body_node, page);
|
||||
const body_node = try frame.createElementNS(.html, "body", null);
|
||||
_ = try html_node.appendChild(body_node, frame);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
pub fn createDocument(_: *const DOMImplementation, namespace_: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, page: *Page) !*Document {
|
||||
pub fn createDocument(_: *const DOMImplementation, namespace_: ?[]const u8, qualified_name: ?[]const u8, doctype: ?*DocumentType, frame: *Frame) !*Document {
|
||||
// Create XML Document
|
||||
const document = (try page._factory.document(Node.Document.XMLDocument{ ._proto = undefined })).asDocument();
|
||||
const document = (try frame._factory.document(Node.Document.XMLDocument{ ._proto = undefined })).asDocument();
|
||||
document._url = "about:blank";
|
||||
|
||||
// Append doctype if provided
|
||||
if (doctype) |dt| {
|
||||
_ = try document.asNode().appendChild(dt.asNode(), page);
|
||||
_ = try document.asNode().appendChild(dt.asNode(), frame);
|
||||
}
|
||||
|
||||
// Create and append root element if qualified_name provided
|
||||
if (qualified_name) |qname| {
|
||||
if (qname.len > 0) {
|
||||
const namespace = if (namespace_) |ns| Node.Element.Namespace.parse(ns) else .xml;
|
||||
const root = try page.createElementNS(namespace, qname, null);
|
||||
_ = try document.asNode().appendChild(root, page);
|
||||
const root = try frame.createElementNS(namespace, qname, null);
|
||||
_ = try document.asNode().appendChild(root, frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
const NodeFilter = @import("NodeFilter.zig");
|
||||
@@ -33,9 +33,9 @@ _reference_node: *Node,
|
||||
_pointer_before_reference_node: bool,
|
||||
_active: bool = false,
|
||||
|
||||
pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||
pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, frame: *Frame) !*DOMNodeIterator {
|
||||
const node_filter = try NodeFilter.init(filter);
|
||||
return page._factory.create(DOMNodeIterator{
|
||||
return frame._factory.create(DOMNodeIterator{
|
||||
._root = root,
|
||||
._filter = node_filter,
|
||||
._reference_node = root,
|
||||
@@ -64,7 +64,7 @@ pub fn getFilter(self: *const DOMNodeIterator) ?FilterOpts {
|
||||
return self._filter._original_filter;
|
||||
}
|
||||
|
||||
pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
pub fn nextNode(self: *DOMNodeIterator, frame: *Frame) !?*Node {
|
||||
if (self._active) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -78,7 +78,7 @@ pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
while (true) {
|
||||
if (before_node) {
|
||||
before_node = false;
|
||||
const result = try self.filterNode(node, page);
|
||||
const result = try self.filterNode(node, frame);
|
||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._reference_node = node;
|
||||
self._pointer_before_reference_node = false;
|
||||
@@ -92,7 +92,7 @@ pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
}
|
||||
node = next.?;
|
||||
|
||||
const result = try self.filterNode(node, page);
|
||||
const result = try self.filterNode(node, frame);
|
||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._reference_node = node;
|
||||
self._pointer_before_reference_node = false;
|
||||
@@ -102,7 +102,7 @@ pub fn nextNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
pub fn previousNode(self: *DOMNodeIterator, frame: *Frame) !?*Node {
|
||||
if (self._active) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -115,7 +115,7 @@ pub fn previousNode(self: *DOMNodeIterator, page: *Page) !?*Node {
|
||||
|
||||
while (true) {
|
||||
if (!before_node) {
|
||||
const result = try self.filterNode(node, page);
|
||||
const result = try self.filterNode(node, frame);
|
||||
if (result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._reference_node = node;
|
||||
self._pointer_before_reference_node = true;
|
||||
@@ -138,7 +138,7 @@ pub fn detach(_: *const DOMNodeIterator) void {
|
||||
// no-op legacy
|
||||
}
|
||||
|
||||
fn filterNode(self: *const DOMNodeIterator, node: *Node, page: *Page) !i32 {
|
||||
fn filterNode(self: *const DOMNodeIterator, node: *Node, frame: *Frame) !i32 {
|
||||
// First check whatToShow
|
||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
@@ -147,7 +147,7 @@ fn filterNode(self: *const DOMNodeIterator, node: *Node, page: *Page) !i32 {
|
||||
// Then check the filter callback
|
||||
// For NodeIterator, REJECT and SKIP are equivalent - both skip the node
|
||||
// but continue with its descendants
|
||||
const result = try self._filter.acceptNode(node, page.js.local.?);
|
||||
const result = try self._filter.acceptNode(node, frame.js.local.?);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Parser = @import("../parser/Parser.zig");
|
||||
|
||||
const HTMLDocument = @import("HTMLDocument.zig");
|
||||
@@ -40,7 +40,7 @@ pub fn parseFromString(
|
||||
_: *const DOMParser,
|
||||
html: []const u8,
|
||||
mime_type: []const u8,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*Document {
|
||||
const target_mime = std.meta.stringToEnum(enum {
|
||||
@"text/html",
|
||||
@@ -50,13 +50,13 @@ pub fn parseFromString(
|
||||
@"image/svg+xml",
|
||||
}, mime_type) orelse return error.NotSupported;
|
||||
|
||||
const arena = try page.getArena(.medium, "DOMParser.parseFromString");
|
||||
defer page.releaseArena(arena);
|
||||
const arena = try frame.getArena(.medium, "DOMParser.parseFromString");
|
||||
defer frame.releaseArena(arena);
|
||||
|
||||
return switch (target_mime) {
|
||||
.@"text/html" => {
|
||||
// Create a new HTMLDocument
|
||||
const doc = try page._factory.document(HTMLDocument{
|
||||
const doc = try frame._factory.document(HTMLDocument{
|
||||
._proto = undefined,
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ pub fn parseFromString(
|
||||
}
|
||||
|
||||
// Parse HTML into the document
|
||||
var parser = Parser.init(arena, doc.asNode(), page);
|
||||
var parser = Parser.init(arena, doc.asNode(), frame);
|
||||
parser.parse(normalized);
|
||||
|
||||
if (parser.err) |pe| {
|
||||
@@ -77,19 +77,19 @@ pub fn parseFromString(
|
||||
},
|
||||
else => {
|
||||
// Create a new XMLDocument.
|
||||
const doc = try page._factory.document(XMLDocument{
|
||||
const doc = try frame._factory.document(XMLDocument{
|
||||
._proto = undefined,
|
||||
});
|
||||
|
||||
// Parse XML into XMLDocument.
|
||||
const doc_node = doc.asNode();
|
||||
var parser = Parser.init(arena, doc_node, page);
|
||||
var parser = Parser.init(arena, doc_node, frame);
|
||||
parser.parseXML(html);
|
||||
|
||||
if (parser.err != null or doc_node.firstChild() == null) {
|
||||
// Return a document with a <parsererror> element per spec.
|
||||
const err_doc = try page._factory.document(XMLDocument{ ._proto = undefined });
|
||||
var err_parser = Parser.init(arena, err_doc.asNode(), page);
|
||||
const err_doc = try frame._factory.document(XMLDocument{ ._proto = undefined });
|
||||
var err_parser = Parser.init(arena, err_doc.asNode(), frame);
|
||||
err_parser.parseXML("<parsererror xmlns=\"http://www.mozilla.org/newlayout/xml/parsererror.xml\">error</parsererror>");
|
||||
return err_doc.asDocument();
|
||||
}
|
||||
@@ -99,7 +99,7 @@ pub fn parseFromString(
|
||||
// If first node is a `ProcessingInstruction`, skip it.
|
||||
if (first_child.getNodeType() == 7) {
|
||||
// We're sure that firstChild exist, this cannot fail.
|
||||
_ = try doc_node.removeChild(first_child, page);
|
||||
_ = try doc_node.removeChild(first_child, frame);
|
||||
}
|
||||
|
||||
return doc.asDocument();
|
||||
|
||||
@@ -20,15 +20,15 @@ const DOMRect = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
_x: f64,
|
||||
_y: f64,
|
||||
_width: f64,
|
||||
_height: f64,
|
||||
|
||||
pub fn init(x: f64, y: f64, width: f64, height: f64, page: *Page) !*DOMRect {
|
||||
return page._factory.create(DOMRect{
|
||||
pub fn init(x: f64, y: f64, width: f64, height: f64, frame: *Frame) !*DOMRect {
|
||||
return frame._factory.create(DOMRect{
|
||||
._x = x,
|
||||
._y = y,
|
||||
._width = width,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
const NodeFilter = @import("NodeFilter.zig");
|
||||
@@ -31,9 +31,9 @@ _what_to_show: u32,
|
||||
_filter: NodeFilter,
|
||||
_current: *Node,
|
||||
|
||||
pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, page: *Page) !*DOMTreeWalker {
|
||||
pub fn init(root: *Node, what_to_show: u32, filter: ?FilterOpts, frame: *Frame) !*DOMTreeWalker {
|
||||
const node_filter = try NodeFilter.init(filter);
|
||||
return page._factory.create(DOMTreeWalker{
|
||||
return frame._factory.create(DOMTreeWalker{
|
||||
._root = root,
|
||||
._current = root,
|
||||
._filter = node_filter,
|
||||
@@ -62,13 +62,13 @@ pub fn setCurrentNode(self: *DOMTreeWalker, node: *Node) void {
|
||||
}
|
||||
|
||||
// Navigation methods
|
||||
pub fn parentNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn parentNode(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self._current._parent;
|
||||
while (node) |n| {
|
||||
if (n == self._root._parent) {
|
||||
return null;
|
||||
}
|
||||
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(n, frame) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
@@ -77,11 +77,11 @@ pub fn parentNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn firstChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn firstChild(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self._current.firstChild();
|
||||
|
||||
while (node) |n| {
|
||||
const filter_result = try self.acceptNode(n, page);
|
||||
const filter_result = try self.acceptNode(n, frame);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
@@ -117,11 +117,11 @@ pub fn firstChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn lastChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn lastChild(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self._current.lastChild();
|
||||
|
||||
while (node) |n| {
|
||||
const filter_result = try self.acceptNode(n, page);
|
||||
const filter_result = try self.acceptNode(n, frame);
|
||||
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
@@ -157,10 +157,10 @@ pub fn lastChild(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn previousSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn previousSibling(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self.previousSiblingOrNull(self._current);
|
||||
while (node) |n| {
|
||||
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(n, frame) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
@@ -169,10 +169,10 @@ pub fn previousSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn nextSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn nextSibling(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self.nextSiblingOrNull(self._current);
|
||||
while (node) |n| {
|
||||
if (try self.acceptNode(n, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(n, frame) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = n;
|
||||
return n;
|
||||
}
|
||||
@@ -181,7 +181,7 @@ pub fn nextSibling(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn previousNode(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self._current;
|
||||
while (node != self._root) {
|
||||
var sibling = self.previousSiblingOrNull(node);
|
||||
@@ -189,7 +189,7 @@ pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
node = sib;
|
||||
|
||||
// Check if this sibling is rejected before descending into it
|
||||
const sib_result = try self.acceptNode(node, page);
|
||||
const sib_result = try self.acceptNode(node, frame);
|
||||
if (sib_result == NodeFilter.FILTER_REJECT) {
|
||||
// Skip this sibling and its descendants entirely
|
||||
sibling = self.previousSiblingOrNull(node);
|
||||
@@ -204,7 +204,7 @@ pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
while (child) |c| {
|
||||
if (!self.isInSubtree(c)) break;
|
||||
|
||||
const filter_result = try self.acceptNode(c, page);
|
||||
const filter_result = try self.acceptNode(c, frame);
|
||||
if (filter_result == NodeFilter.FILTER_REJECT) {
|
||||
// Skip this child and try its previous sibling
|
||||
child = self.previousSiblingOrNull(c);
|
||||
@@ -220,7 +220,7 @@ pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
node = child.?;
|
||||
}
|
||||
|
||||
if (try self.acceptNode(node, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(node, frame) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
}
|
||||
@@ -232,7 +232,7 @@ pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
}
|
||||
|
||||
const parent = node._parent orelse return null;
|
||||
if (try self.acceptNode(parent, page) == NodeFilter.FILTER_ACCEPT) {
|
||||
if (try self.acceptNode(parent, frame) == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = parent;
|
||||
return parent;
|
||||
}
|
||||
@@ -241,14 +241,14 @@ pub fn previousNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn nextNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
pub fn nextNode(self: *DOMTreeWalker, frame: *Frame) !?*Node {
|
||||
var node = self._current;
|
||||
|
||||
while (true) {
|
||||
// Try children first (depth-first)
|
||||
if (node.firstChild()) |child| {
|
||||
node = child;
|
||||
const filter_result = try self.acceptNode(node, page);
|
||||
const filter_result = try self.acceptNode(node, frame);
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
@@ -271,7 +271,7 @@ pub fn nextNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
|
||||
if (node.nextSibling()) |sibling| {
|
||||
node = sibling;
|
||||
const filter_result = try self.acceptNode(node, page);
|
||||
const filter_result = try self.acceptNode(node, frame);
|
||||
if (filter_result == NodeFilter.FILTER_ACCEPT) {
|
||||
self._current = node;
|
||||
return node;
|
||||
@@ -293,7 +293,7 @@ pub fn nextNode(self: *DOMTreeWalker, page: *Page) !?*Node {
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
fn acceptNode(self: *const DOMTreeWalker, node: *Node, page: *Page) !i32 {
|
||||
fn acceptNode(self: *const DOMTreeWalker, node: *Node, frame: *Frame) !i32 {
|
||||
// First check whatToShow
|
||||
if (!NodeFilter.shouldShow(node, self._what_to_show)) {
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
@@ -303,7 +303,7 @@ fn acceptNode(self: *const DOMTreeWalker, node: *Node, page: *Page) !i32 {
|
||||
// For TreeWalker, REJECT means reject node and its descendants
|
||||
// SKIP means skip node but check its descendants
|
||||
// ACCEPT means accept the node
|
||||
return try self._filter.acceptNode(node, page.js.local.?);
|
||||
return try self._filter.acceptNode(node, frame.js.local.?);
|
||||
}
|
||||
|
||||
fn isInSubtree(self: *const DOMTreeWalker, node: *Node) bool {
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 URL = @import("../URL.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
@@ -47,7 +47,7 @@ const Document = @This();
|
||||
|
||||
_type: Type,
|
||||
_proto: *Node,
|
||||
_page: ?*Page = null,
|
||||
_frame: ?*Frame = null,
|
||||
_location: ?*Location = null,
|
||||
_url: ?[:0]const u8 = null, // URL for documents created via DOMImplementation (about:blank)
|
||||
_ready_state: ReadyState = .loading,
|
||||
@@ -118,8 +118,8 @@ pub fn asEventTarget(self: *Document) *@import("EventTarget.zig") {
|
||||
return self._proto.asEventTarget();
|
||||
}
|
||||
|
||||
pub fn getURL(self: *const Document, page: *const Page) [:0]const u8 {
|
||||
return self._url orelse page.url;
|
||||
pub fn getURL(self: *const Document, frame: *const Frame) [:0]const u8 {
|
||||
return self._url orelse frame.url;
|
||||
}
|
||||
|
||||
pub fn getContentType(self: *const Document) []const u8 {
|
||||
@@ -130,66 +130,66 @@ pub fn getContentType(self: *const Document) []const u8 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getDomain(_: *const Document, page: *const Page) []const u8 {
|
||||
return URL.getHostname(page.url);
|
||||
pub fn getDomain(_: *const Document, frame: *const Frame) []const u8 {
|
||||
return URL.getHostname(frame.url);
|
||||
}
|
||||
|
||||
const CreateElementOptions = struct {
|
||||
is: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
||||
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, frame: *Frame) !*Element {
|
||||
try validateElementName(name);
|
||||
const ns: Element.Namespace, const normalized_name = blk: {
|
||||
if (self._type == .html) {
|
||||
break :blk .{ .html, std.ascii.lowerString(&page.buf, name) };
|
||||
break :blk .{ .html, std.ascii.lowerString(&frame.buf, name) };
|
||||
}
|
||||
// Generic and XML documents create elements with null namespace
|
||||
break :blk .{ .null, name };
|
||||
};
|
||||
// HTML documents are case-insensitive - lowercase the tag name
|
||||
|
||||
const node = try page.createElementNS(ns, normalized_name, null);
|
||||
const node = try frame.createElementNS(ns, normalized_name, null);
|
||||
const element = node.as(Element);
|
||||
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(node, self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
|
||||
const options = options_ orelse return element;
|
||||
if (options.is) |is_value| {
|
||||
try element.setAttribute(comptime .wrap("is"), .wrap(is_value), page);
|
||||
try Element.Html.Custom.checkAndAttachBuiltIn(element, page);
|
||||
try element.setAttribute(comptime .wrap("is"), .wrap(is_value), frame);
|
||||
try Element.Html.Custom.checkAndAttachBuiltIn(element, frame);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element {
|
||||
pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8, frame: *Frame) !*Element {
|
||||
try validateElementName(name);
|
||||
const ns = Element.Namespace.parse(namespace);
|
||||
// Per spec, createElementNS does NOT lowercase (unlike createElement).
|
||||
const node = try page.createElementNS(ns, name, null);
|
||||
const node = try frame.createElementNS(ns, name, null);
|
||||
|
||||
// Store original URI for unknown namespaces so lookupNamespaceURI can return it
|
||||
if (ns == .unknown) {
|
||||
if (namespace) |uri| {
|
||||
const duped = try page.dupeString(uri);
|
||||
try page._element_namespace_uris.put(page.arena, node.as(Element), duped);
|
||||
const duped = try frame.dupeString(uri);
|
||||
try frame._element_namespace_uris.put(frame.arena, node.as(Element), duped);
|
||||
}
|
||||
}
|
||||
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(node, self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node.as(Element);
|
||||
}
|
||||
|
||||
pub fn createAttribute(_: *const Document, name: String.Global, page: *Page) !?*Element.Attribute {
|
||||
pub fn createAttribute(_: *const Document, name: String.Global, frame: *Frame) !?*Element.Attribute {
|
||||
try Element.Attribute.validateAttributeName(name.str);
|
||||
return page._factory.node(Element.Attribute{
|
||||
return frame._factory.node(Element.Attribute{
|
||||
._proto = undefined,
|
||||
._name = name.str,
|
||||
._value = String.empty,
|
||||
@@ -197,13 +197,13 @@ pub fn createAttribute(_: *const Document, name: String.Global, page: *Page) !?*
|
||||
});
|
||||
}
|
||||
|
||||
pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: String.Global, page: *Page) !?*Element.Attribute {
|
||||
pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: String.Global, frame: *Frame) !?*Element.Attribute {
|
||||
if (std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml") == false) {
|
||||
log.warn(.not_implemented, "document.createAttributeNS", .{ .namespace = namespace });
|
||||
}
|
||||
|
||||
try Element.Attribute.validateAttributeName(name.str);
|
||||
return page._factory.node(Element.Attribute{
|
||||
return frame._factory.node(Element.Attribute{
|
||||
._proto = undefined,
|
||||
._name = name.str,
|
||||
._value = String.empty,
|
||||
@@ -211,7 +211,7 @@ pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: String
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
|
||||
pub fn getElementById(self: *Document, id: []const u8, frame: *Frame) ?*Element {
|
||||
if (id.len == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -229,8 +229,8 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
|
||||
// we ignore this error to keep getElementById easy to call
|
||||
// if it really failed, then we're out of memory and nothing's
|
||||
// going to work like it should anyways.
|
||||
const owned_id = page.dupeString(id) catch return null;
|
||||
self._elements_by_id.put(page.arena, owned_id, el) catch return null;
|
||||
const owned_id = frame.dupeString(id) catch return null;
|
||||
self._elements_by_id.put(frame.arena, owned_id, el) catch return null;
|
||||
return el;
|
||||
}
|
||||
}
|
||||
@@ -239,26 +239,26 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getElementsByTagName(self: *Document, tag_name: []const u8, page: *Page) !Node.GetElementsByTagNameResult {
|
||||
return self.asNode().getElementsByTagName(tag_name, page);
|
||||
pub fn getElementsByTagName(self: *Document, tag_name: []const u8, frame: *Frame) !Node.GetElementsByTagNameResult {
|
||||
return self.asNode().getElementsByTagName(tag_name, frame);
|
||||
}
|
||||
|
||||
pub fn getElementsByTagNameNS(self: *Document, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
|
||||
return self.asNode().getElementsByTagNameNS(namespace, local_name, page);
|
||||
pub fn getElementsByTagNameNS(self: *Document, namespace: ?[]const u8, local_name: []const u8, frame: *Frame) !collections.NodeLive(.tag_name_ns) {
|
||||
return self.asNode().getElementsByTagNameNS(namespace, local_name, frame);
|
||||
}
|
||||
|
||||
pub fn getElementsByClassName(self: *Document, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
|
||||
return self.asNode().getElementsByClassName(class_name, page);
|
||||
pub fn getElementsByClassName(self: *Document, class_name: []const u8, frame: *Frame) !collections.NodeLive(.class_name) {
|
||||
return self.asNode().getElementsByClassName(class_name, frame);
|
||||
}
|
||||
|
||||
pub fn getElementsByName(self: *Document, name: []const u8, page: *Page) !collections.NodeLive(.name) {
|
||||
const arena = page.arena;
|
||||
pub fn getElementsByName(self: *Document, name: []const u8, frame: *Frame) !collections.NodeLive(.name) {
|
||||
const arena = frame.arena;
|
||||
const filter = try arena.dupe(u8, name);
|
||||
return collections.NodeLive(.name).init(self.asNode(), filter, page);
|
||||
return collections.NodeLive(.name).init(self.asNode(), filter, frame);
|
||||
}
|
||||
|
||||
pub fn getChildren(self: *Document, page: *Page) !collections.NodeLive(.child_elements) {
|
||||
return collections.NodeLive(.child_elements).init(self.asNode(), {}, page);
|
||||
pub fn getChildren(self: *Document, frame: *Frame) !collections.NodeLive(.child_elements) {
|
||||
return collections.NodeLive(.child_elements).init(self.asNode(), {}, frame);
|
||||
}
|
||||
|
||||
pub fn getDocumentElement(self: *Document) ?*Element {
|
||||
@@ -276,135 +276,135 @@ pub fn getSelection(self: *Document) *Selection {
|
||||
return &self._selection;
|
||||
}
|
||||
|
||||
pub fn querySelector(self: *Document, input: String, page: *Page) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), input.str(), page);
|
||||
pub fn querySelector(self: *Document, input: String, frame: *Frame) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), input.str(), frame);
|
||||
}
|
||||
|
||||
pub fn querySelectorAll(self: *Document, input: String, page: *Page) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input.str(), page);
|
||||
pub fn querySelectorAll(self: *Document, input: String, frame: *Frame) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input.str(), frame);
|
||||
}
|
||||
|
||||
pub fn getImplementation(self: *Document, page: *Page) !*DOMImplementation {
|
||||
pub fn getImplementation(self: *Document, frame: *Frame) !*DOMImplementation {
|
||||
if (self._implementation) |impl| return impl;
|
||||
const impl = try page._factory.create(DOMImplementation{});
|
||||
const impl = try frame._factory.create(DOMImplementation{});
|
||||
self._implementation = impl;
|
||||
return impl;
|
||||
}
|
||||
|
||||
pub fn createDocumentFragment(self: *Document, page: *Page) !*Node.DocumentFragment {
|
||||
const frag = try Node.DocumentFragment.init(page);
|
||||
pub fn createDocumentFragment(self: *Document, frame: *Frame) !*Node.DocumentFragment {
|
||||
const frag = try Node.DocumentFragment.init(frame);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(frag.asNode(), self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(frag.asNode(), self);
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
|
||||
pub fn createComment(self: *Document, data: []const u8, page: *Page) !*Node {
|
||||
const node = try page.createComment(data);
|
||||
pub fn createComment(self: *Document, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = try frame.createComment(data);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(node, self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createTextNode(self: *Document, data: []const u8, page: *Page) !*Node {
|
||||
const node = try page.createTextNode(data);
|
||||
pub fn createTextNode(self: *Document, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = try frame.createTextNode(data);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(node, self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createCDATASection(self: *Document, data: []const u8, page: *Page) !*Node {
|
||||
pub fn createCDATASection(self: *Document, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = switch (self._type) {
|
||||
.html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
||||
.xml => try page.createCDATASection(data),
|
||||
.generic => try page.createCDATASection(data),
|
||||
.xml => try frame.createCDATASection(data),
|
||||
.generic => try frame.createCDATASection(data),
|
||||
};
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(node, self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createProcessingInstruction(self: *Document, target: []const u8, data: []const u8, page: *Page) !*Node {
|
||||
const node = try page.createProcessingInstruction(target, data);
|
||||
pub fn createProcessingInstruction(self: *Document, target: []const u8, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = try frame.createProcessingInstruction(target, data);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != page.document) {
|
||||
try page.setNodeOwnerDocument(node, self);
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
const Range = @import("Range.zig");
|
||||
pub fn createRange(_: *const Document, page: *Page) !*Range {
|
||||
return Range.init(page);
|
||||
pub fn createRange(_: *const Document, frame: *Frame) !*Range {
|
||||
return Range.init(frame);
|
||||
}
|
||||
|
||||
pub fn createEvent(_: *const Document, event_type: []const u8, page: *Page) !*@import("Event.zig") {
|
||||
pub fn createEvent(_: *const Document, event_type: []const u8, frame: *Frame) !*@import("Event.zig") {
|
||||
const Event = @import("Event.zig");
|
||||
if (event_type.len > 100) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
const normalized = std.ascii.lowerString(&page.buf, event_type);
|
||||
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, page);
|
||||
return Event.init("", null, frame);
|
||||
}
|
||||
|
||||
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, page)).asEvent();
|
||||
return (try CustomEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "keyboardevent")) {
|
||||
const KeyboardEvent = @import("event/KeyboardEvent.zig");
|
||||
return (try KeyboardEvent.init("", null, page)).asEvent();
|
||||
return (try KeyboardEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "inputevent")) {
|
||||
const InputEvent = @import("event/InputEvent.zig");
|
||||
return (try InputEvent.init("", null, page)).asEvent();
|
||||
return (try InputEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "mouseevent") or std.mem.eql(u8, normalized, "mouseevents")) {
|
||||
const MouseEvent = @import("event/MouseEvent.zig");
|
||||
return (try MouseEvent.init("", null, page)).asEvent();
|
||||
return (try MouseEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "messageevent")) {
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
return (try MessageEvent.init("", null, page._session)).asEvent();
|
||||
return (try MessageEvent.init("", null, frame._session)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "uievent") or std.mem.eql(u8, normalized, "uievents")) {
|
||||
const UIEvent = @import("event/UIEvent.zig");
|
||||
return (try UIEvent.init("", null, page)).asEvent();
|
||||
return (try UIEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "focusevent") or std.mem.eql(u8, normalized, "focusevents")) {
|
||||
const FocusEvent = @import("event/FocusEvent.zig");
|
||||
return (try FocusEvent.init("", null, page)).asEvent();
|
||||
return (try FocusEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, normalized, "textevent") or std.mem.eql(u8, normalized, "textevents")) {
|
||||
const TextEvent = @import("event/TextEvent.zig");
|
||||
return (try TextEvent.init("", null, page)).asEvent();
|
||||
return (try TextEvent.init("", null, frame)).asEvent();
|
||||
}
|
||||
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMTreeWalker.FilterOpts, page: *Page) !*DOMTreeWalker {
|
||||
return DOMTreeWalker.init(root, try whatToShow(what_to_show), filter, page);
|
||||
pub fn createTreeWalker(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMTreeWalker.FilterOpts, frame: *Frame) !*DOMTreeWalker {
|
||||
return DOMTreeWalker.init(root, try whatToShow(what_to_show), filter, frame);
|
||||
}
|
||||
|
||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMNodeIterator.FilterOpts, page: *Page) !*DOMNodeIterator {
|
||||
return DOMNodeIterator.init(root, try whatToShow(what_to_show), filter, page);
|
||||
pub fn createNodeIterator(_: *const Document, root: *Node, what_to_show: ?js.Value, filter: ?DOMNodeIterator.FilterOpts, frame: *Frame) !*DOMNodeIterator {
|
||||
return DOMNodeIterator.init(root, try whatToShow(what_to_show), filter, frame);
|
||||
}
|
||||
|
||||
fn whatToShow(value_: ?js.Value) !u32 {
|
||||
@@ -441,81 +441,81 @@ pub fn getActiveElement(self: *Document) ?*Element {
|
||||
return self.getDocumentElement();
|
||||
}
|
||||
|
||||
pub fn getStyleSheets(self: *Document, page: *Page) !*StyleSheetList {
|
||||
pub fn getStyleSheets(self: *Document, frame: *Frame) !*StyleSheetList {
|
||||
if (self._style_sheets) |sheets| {
|
||||
return sheets;
|
||||
}
|
||||
const sheets = try StyleSheetList.init(page);
|
||||
const sheets = try StyleSheetList.init(frame);
|
||||
self._style_sheets = sheets;
|
||||
return sheets;
|
||||
}
|
||||
|
||||
pub fn getFonts(self: *Document, page: *Page) !*FontFaceSet {
|
||||
pub fn getFonts(self: *Document, frame: *Frame) !*FontFaceSet {
|
||||
if (self._fonts) |fonts| {
|
||||
return fonts;
|
||||
}
|
||||
const fonts = try FontFaceSet.init(page);
|
||||
const fonts = try FontFaceSet.init(frame);
|
||||
fonts.acquireRef();
|
||||
self._fonts = fonts;
|
||||
return fonts;
|
||||
}
|
||||
|
||||
pub fn adoptNode(_: *const Document, node: *Node, page: *Page) !*Node {
|
||||
pub fn adoptNode(_: *const Document, node: *Node, frame: *Frame) !*Node {
|
||||
if (node._type == .document) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
if (node._parent) |parent| {
|
||||
page.removeNode(parent, node, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(parent, node, .{ .will_be_reconnected = false });
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn importNode(_: *const Document, node: *Node, deep_: ?bool, page: *Page) !*Node {
|
||||
pub fn importNode(_: *const Document, node: *Node, deep_: ?bool, frame: *Frame) !*Node {
|
||||
if (node._type == .document) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
return node.cloneNode(deep_, page);
|
||||
return node.cloneNode(deep_, frame);
|
||||
}
|
||||
|
||||
pub fn append(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn append(self: *Document, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
try validateDocumentNodes(self, nodes, false);
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
const parent = self.asNode();
|
||||
const parent_is_connected = parent.isConnected();
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(page);
|
||||
const child = try node_or_text.toNode(frame);
|
||||
|
||||
// DocumentFragments are special - append all their children
|
||||
if (child.is(Node.DocumentFragment)) |_| {
|
||||
try page.appendAllChildren(child, parent);
|
||||
try frame.appendAllChildren(child, parent);
|
||||
continue;
|
||||
}
|
||||
|
||||
var child_connected = false;
|
||||
if (child._parent) |previous_parent| {
|
||||
child_connected = child.isConnected();
|
||||
page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||
frame.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||
}
|
||||
try page.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||
try frame.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
try validateDocumentNodes(self, nodes, false);
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
const parent = self.asNode();
|
||||
const parent_is_connected = parent.isConnected();
|
||||
|
||||
var i = nodes.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
const child = try nodes[i].toNode(page);
|
||||
const child = try nodes[i].toNode(frame);
|
||||
|
||||
// DocumentFragments are special - need to insert all their children
|
||||
if (child.is(Node.DocumentFragment)) |frag| {
|
||||
@@ -523,11 +523,11 @@ pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !vo
|
||||
var frag_child = frag.asNode().lastChild();
|
||||
while (frag_child) |fc| {
|
||||
const prev = fc.previousSibling();
|
||||
page.removeNode(frag.asNode(), fc, .{ .will_be_reconnected = parent_is_connected });
|
||||
frame.removeNode(frag.asNode(), fc, .{ .will_be_reconnected = parent_is_connected });
|
||||
if (first_child) |before| {
|
||||
try page.insertNodeRelative(parent, fc, .{ .before = before }, .{});
|
||||
try frame.insertNodeRelative(parent, fc, .{ .before = before }, .{});
|
||||
} else {
|
||||
try page.appendNode(parent, fc, .{});
|
||||
try frame.appendNode(parent, fc, .{});
|
||||
}
|
||||
frag_child = prev;
|
||||
}
|
||||
@@ -537,37 +537,37 @@ pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !vo
|
||||
var child_connected = false;
|
||||
if (child._parent) |previous_parent| {
|
||||
child_connected = child.isConnected();
|
||||
page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||
frame.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||
}
|
||||
|
||||
const first_child = parent.firstChild();
|
||||
if (first_child) |before| {
|
||||
try page.insertNodeRelative(parent, child, .{ .before = before }, .{ .child_already_connected = child_connected });
|
||||
try frame.insertNodeRelative(parent, child, .{ .before = before }, .{ .child_already_connected = child_connected });
|
||||
} else {
|
||||
try page.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||
try frame.appendNode(parent, child, .{ .child_already_connected = child_connected });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replaceChildren(self: *Document, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn replaceChildren(self: *Document, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
try validateDocumentNodes(self, nodes, false);
|
||||
return self.asNode().replaceChildren(nodes, page);
|
||||
return self.asNode().replaceChildren(nodes, frame);
|
||||
}
|
||||
|
||||
pub fn elementFromPoint(self: *Document, x: f64, y: f64, page: *Page) !?*Element {
|
||||
pub fn elementFromPoint(self: *Document, x: f64, y: f64, frame: *Frame) !?*Element {
|
||||
// Traverse document in depth-first order to find the topmost (last in document order)
|
||||
// element that contains the point (x, y)
|
||||
var topmost: ?*Element = null;
|
||||
|
||||
const root = self.asNode();
|
||||
var stack: std.ArrayList(*Node) = .empty;
|
||||
try stack.append(page.call_arena, root);
|
||||
try stack.append(frame.call_arena, root);
|
||||
|
||||
while (stack.items.len > 0) {
|
||||
const node = stack.pop() orelse break;
|
||||
if (node.is(Element)) |element| {
|
||||
if (element.checkVisibilityCached(null, page)) {
|
||||
const rect = element.getBoundingClientRectForVisible(page);
|
||||
if (element.checkVisibilityCached(null, frame)) {
|
||||
const rect = element.getBoundingClientRectForVisible(frame);
|
||||
if (x >= rect.getLeft() and x <= rect.getRight() and y >= rect.getTop() and y <= rect.getBottom()) {
|
||||
topmost = element;
|
||||
}
|
||||
@@ -577,7 +577,7 @@ pub fn elementFromPoint(self: *Document, x: f64, y: f64, page: *Page) !?*Element
|
||||
// Add children to stack in reverse order so we process them in document order
|
||||
var child = node.lastChild();
|
||||
while (child) |c| {
|
||||
try stack.append(page.call_arena, c);
|
||||
try stack.append(frame.call_arena, c);
|
||||
child = c.previousSibling();
|
||||
}
|
||||
}
|
||||
@@ -585,12 +585,12 @@ pub fn elementFromPoint(self: *Document, x: f64, y: f64, page: *Page) !?*Element
|
||||
return topmost;
|
||||
}
|
||||
|
||||
pub fn elementsFromPoint(self: *Document, x: f64, y: f64, page: *Page) ![]const *Element {
|
||||
pub fn elementsFromPoint(self: *Document, x: f64, y: f64, frame: *Frame) ![]const *Element {
|
||||
// Get topmost element
|
||||
var current: ?*Element = (try self.elementFromPoint(x, y, page)) orelse return &.{};
|
||||
var current: ?*Element = (try self.elementFromPoint(x, y, frame)) orelse return &.{};
|
||||
var result: std.ArrayList(*Element) = .empty;
|
||||
while (current) |el| {
|
||||
try result.append(page.call_arena, el);
|
||||
try result.append(frame.call_arena, el);
|
||||
current = el.parentElement();
|
||||
}
|
||||
return result.items;
|
||||
@@ -621,18 +621,18 @@ fn looksLikeNewDocument(html: []const u8) bool {
|
||||
std.ascii.startsWithIgnoreCase(trimmed, "<html");
|
||||
}
|
||||
|
||||
pub fn write(self: *Document, text: []const []const u8, page: *Page) !void {
|
||||
return self.writeInternal(text, false, page);
|
||||
pub fn write(self: *Document, text: []const []const u8, frame: *Frame) !void {
|
||||
return self.writeInternal(text, false, frame);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-document-writeln
|
||||
// `writeln(...text)` runs the document write steps with `text` followed by a
|
||||
// U+000A LINE FEED character.
|
||||
pub fn writeln(self: *Document, text: []const []const u8, page: *Page) !void {
|
||||
return self.writeInternal(text, true, page);
|
||||
pub fn writeln(self: *Document, text: []const []const u8, frame: *Frame) !void {
|
||||
return self.writeInternal(text, true, frame);
|
||||
}
|
||||
|
||||
fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool, page: *Page) !void {
|
||||
fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool, frame: *Frame) !void {
|
||||
if (self._type == .xml) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -644,17 +644,17 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
|
||||
const html = blk: {
|
||||
var joined: std.ArrayList(u8) = .empty;
|
||||
for (text) |str| {
|
||||
try joined.appendSlice(page.call_arena, str);
|
||||
try joined.appendSlice(frame.call_arena, str);
|
||||
}
|
||||
if (append_newline) {
|
||||
try joined.append(page.call_arena, '\n');
|
||||
try joined.append(frame.call_arena, '\n');
|
||||
}
|
||||
break :blk joined.items;
|
||||
};
|
||||
|
||||
if (self._current_script == null or page._load_state != .parsing) {
|
||||
if (self._current_script == null or frame._load_state != .parsing) {
|
||||
if (self._script_created_parser == null or looksLikeNewDocument(html)) {
|
||||
_ = try self.open(page);
|
||||
_ = try self.open(frame);
|
||||
}
|
||||
|
||||
if (html.len > 0) {
|
||||
@@ -675,17 +675,17 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
|
||||
|
||||
// Our implementation is hacky. We'll write to a DocumentFragment, then
|
||||
// append its children.
|
||||
const fragment = try Node.DocumentFragment.init(page);
|
||||
const fragment = try Node.DocumentFragment.init(frame);
|
||||
const fragment_node = fragment.asNode();
|
||||
|
||||
const previous_parse_mode = page._parse_mode;
|
||||
page._parse_mode = .document_write;
|
||||
defer page._parse_mode = previous_parse_mode;
|
||||
const previous_parse_mode = frame._parse_mode;
|
||||
frame._parse_mode = .document_write;
|
||||
defer frame._parse_mode = previous_parse_mode;
|
||||
|
||||
const arena = try page.getArena(.medium, "Document.write");
|
||||
defer page.releaseArena(arena);
|
||||
const arena = try frame.getArena(.medium, "Document.write");
|
||||
defer frame.releaseArena(arena);
|
||||
|
||||
var parser = Parser.init(arena, fragment_node, page);
|
||||
var parser = Parser.init(arena, fragment_node, frame);
|
||||
parser.parseFragment(html);
|
||||
|
||||
// Extract children from wrapper HTML element (html5ever wraps fragments)
|
||||
@@ -720,15 +720,15 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
|
||||
for (children_to_insert.items) |child| {
|
||||
// Clear parent pointer (child is currently parented to fragment/HTML wrapper)
|
||||
child._parent = null;
|
||||
try page.insertNodeRelative(parent, child, .{ .after = insert_after.? }, .{});
|
||||
try frame.insertNodeRelative(parent, child, .{ .after = insert_after.? }, .{});
|
||||
insert_after = child;
|
||||
}
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
self._write_insertion_point = children_to_insert.getLast();
|
||||
}
|
||||
|
||||
pub fn open(self: *Document, page: *Page) !*Document {
|
||||
pub fn open(self: *Document, frame: *Frame) !*Document {
|
||||
if (self._type == .xml) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -737,7 +737,7 @@ pub fn open(self: *Document, page: *Page) !*Document {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
|
||||
if (page._load_state == .parsing) {
|
||||
if (frame._load_state == .parsing) {
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -752,25 +752,25 @@ pub fn open(self: *Document, page: *Page) !*Document {
|
||||
// Remove all children from document
|
||||
var it = doc_node.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
page.removeNode(doc_node, child, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(doc_node, child, .{ .will_be_reconnected = false });
|
||||
}
|
||||
}
|
||||
|
||||
// reset the document
|
||||
self._elements_by_id.clearAndFree(page.arena);
|
||||
self._elements_by_id.clearAndFree(frame.arena);
|
||||
self._active_element = null;
|
||||
self._style_sheets = null;
|
||||
self._implementation = null;
|
||||
self._ready_state = .loading;
|
||||
|
||||
self._script_created_parser = Parser.Streaming.init(page.arena, doc_node, page);
|
||||
self._script_created_parser = Parser.Streaming.init(frame.arena, doc_node, frame);
|
||||
try self._script_created_parser.?.start();
|
||||
page._parse_mode = .document;
|
||||
frame._parse_mode = .document;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn close(self: *Document, page: *Page) !void {
|
||||
pub fn close(self: *Document, frame: *Frame) !void {
|
||||
if (self._type == .xml) {
|
||||
return error.InvalidStateError;
|
||||
}
|
||||
@@ -790,7 +790,7 @@ pub fn close(self: *Document, page: *Page) !void {
|
||||
self._script_created_parser.?.handle = null;
|
||||
self._script_created_parser = null;
|
||||
|
||||
page.documentIsComplete();
|
||||
frame.documentIsComplete();
|
||||
}
|
||||
|
||||
pub fn getFirstElementChild(self: *Document) ?*Element {
|
||||
@@ -825,11 +825,11 @@ pub fn getChildElementCount(self: *Document) u32 {
|
||||
return i;
|
||||
}
|
||||
|
||||
pub fn getAdoptedStyleSheets(self: *Document, page: *Page) !js.Object.Global {
|
||||
pub fn getAdoptedStyleSheets(self: *Document, frame: *Frame) !js.Object.Global {
|
||||
if (self._adopted_style_sheets) |ass| {
|
||||
return ass;
|
||||
}
|
||||
const js_arr = page.js.local.?.newArray(0);
|
||||
const js_arr = frame.js.local.?.newArray(0);
|
||||
const js_obj = js_arr.toObject();
|
||||
self._adopted_style_sheets = try js_obj.persist();
|
||||
return self._adopted_style_sheets.?;
|
||||
@@ -963,10 +963,10 @@ fn validateElementName(name: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
// When a page or frame's URL is about:blank, or as soon as a frame is
|
||||
// When a frame's URL is about:blank, or as soon as a frame is
|
||||
// programmatically created, it has this default "blank" content
|
||||
pub fn injectBlank(self: *Document, page: *Page) error{InjectBlankError}!void {
|
||||
self._injectBlank(page) catch |err| {
|
||||
pub fn injectBlank(self: *Document, frame: *Frame) error{InjectBlankError}!void {
|
||||
self._injectBlank(frame) catch |err| {
|
||||
// we wrap _injectBlank like this so that injectBlank can only return an
|
||||
// InjectBlankError. injectBlank is used in when nodes are inserted
|
||||
// as since it inserts node itself, Zig can't infer the error set.
|
||||
@@ -975,18 +975,18 @@ pub fn injectBlank(self: *Document, page: *Page) error{InjectBlankError}!void {
|
||||
};
|
||||
}
|
||||
|
||||
fn _injectBlank(self: *Document, page: *Page) !void {
|
||||
fn _injectBlank(self: *Document, frame: *Frame) !void {
|
||||
if (comptime IS_DEBUG) {
|
||||
// should only be called on an empty document
|
||||
std.debug.assert(self.asNode()._children == null);
|
||||
}
|
||||
|
||||
const html = try page.createElementNS(.html, "html", null);
|
||||
const head = try page.createElementNS(.html, "head", null);
|
||||
const body = try page.createElementNS(.html, "body", null);
|
||||
try page.appendNode(html, head, .{});
|
||||
try page.appendNode(html, body, .{});
|
||||
try page.appendNode(self.asNode(), html, .{});
|
||||
const html = try frame.createElementNS(.html, "html", null);
|
||||
const head = try frame.createElementNS(.html, "head", null);
|
||||
const body = try frame.createElementNS(.html, "body", null);
|
||||
try frame.appendNode(html, head, .{});
|
||||
try frame.appendNode(html, body, .{});
|
||||
try frame.appendNode(self.asNode(), html, .{});
|
||||
}
|
||||
|
||||
const ReadyState = enum {
|
||||
@@ -1006,8 +1006,8 @@ pub const JsApi = struct {
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(_constructor, .{});
|
||||
fn _constructor(page: *Page) !*Document {
|
||||
return page._factory.node(Document{
|
||||
fn _constructor(frame: *Frame) !*Document {
|
||||
return frame._factory.node(Document{
|
||||
._proto = undefined,
|
||||
._type = .generic,
|
||||
});
|
||||
@@ -1040,15 +1040,15 @@ pub const JsApi = struct {
|
||||
pub const createTreeWalker = bridge.function(Document.createTreeWalker, .{});
|
||||
pub const createNodeIterator = bridge.function(Document.createNodeIterator, .{});
|
||||
pub const getElementById = bridge.function(_getElementById, .{});
|
||||
fn _getElementById(self: *Document, value_: ?js.Value, page: *Page) !?*Element {
|
||||
fn _getElementById(self: *Document, value_: ?js.Value, frame: *Frame) !?*Element {
|
||||
const value = value_ orelse return null;
|
||||
if (value.isNull()) {
|
||||
return self.getElementById("null", page);
|
||||
return self.getElementById("null", frame);
|
||||
}
|
||||
if (value.isUndefined()) {
|
||||
return self.getElementById("undefined", page);
|
||||
return self.getElementById("undefined", frame);
|
||||
}
|
||||
return self.getElementById(try value.toZig([]const u8), page);
|
||||
return self.getElementById(try value.toZig([]const u8), frame);
|
||||
}
|
||||
pub const querySelector = bridge.function(Document.querySelector, .{ .dom_exception = true });
|
||||
pub const querySelectorAll = bridge.function(Document.querySelectorAll, .{ .dom_exception = true });
|
||||
@@ -1076,8 +1076,8 @@ pub const JsApi = struct {
|
||||
pub const hidden = bridge.property(false, .{ .template = false, .readonly = true });
|
||||
pub const visibilityState = bridge.property("visible", .{ .template = false, .readonly = true });
|
||||
pub const defaultView = bridge.accessor(struct {
|
||||
fn defaultView(_: *const Document, page: *Page) *@import("Window.zig") {
|
||||
return page.window;
|
||||
fn defaultView(_: *const Document, frame: *Frame) *@import("Window.zig") {
|
||||
return frame.window;
|
||||
}
|
||||
}.defaultView, null, .{});
|
||||
pub const hasFocus = bridge.function(Document.hasFocus, .{});
|
||||
@@ -1089,8 +1089,8 @@ pub const JsApi = struct {
|
||||
pub const compatMode = bridge.property("CSS1Compat", .{ .template = false });
|
||||
|
||||
fn getCharacterSet(self: *const Document) []const u8 {
|
||||
const doc_page = self._page orelse return "UTF-8";
|
||||
return doc_page.charset;
|
||||
const doc_frame = self._frame orelse return "UTF-8";
|
||||
return doc_frame.charset;
|
||||
}
|
||||
pub const referrer = bridge.property("", .{ .template = false });
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Node = @import("Node.zig");
|
||||
const Element = @import("Element.zig");
|
||||
const ShadowRoot = @import("ShadowRoot.zig");
|
||||
@@ -52,8 +52,8 @@ pub fn as(self: *DocumentFragment, comptime T: type) *T {
|
||||
return self.is(T).?;
|
||||
}
|
||||
|
||||
pub fn init(page: *Page) !*DocumentFragment {
|
||||
return page._factory.node(DocumentFragment{
|
||||
pub fn init(frame: *Frame) !*DocumentFragment {
|
||||
return frame._factory.node(DocumentFragment{
|
||||
._type = .generic,
|
||||
._proto = undefined,
|
||||
});
|
||||
@@ -83,16 +83,16 @@ pub fn getElementById(self: *DocumentFragment, id: []const u8) ?*Element {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn querySelector(self: *DocumentFragment, selector: []const u8, page: *Page) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), selector, page);
|
||||
pub fn querySelector(self: *DocumentFragment, selector: []const u8, frame: *Frame) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), selector, frame);
|
||||
}
|
||||
|
||||
pub fn querySelectorAll(self: *DocumentFragment, input: []const u8, page: *Page) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input, page);
|
||||
pub fn querySelectorAll(self: *DocumentFragment, input: []const u8, frame: *Frame) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input, frame);
|
||||
}
|
||||
|
||||
pub fn getChildren(self: *DocumentFragment, page: *Page) !collections.NodeLive(.child_elements) {
|
||||
return collections.NodeLive(.child_elements).init(self.asNode(), {}, page);
|
||||
pub fn getChildren(self: *DocumentFragment, frame: *Frame) !collections.NodeLive(.child_elements) {
|
||||
return collections.NodeLive(.child_elements).init(self.asNode(), {}, frame);
|
||||
}
|
||||
|
||||
pub fn firstElementChild(self: *DocumentFragment) ?*Element {
|
||||
@@ -124,51 +124,51 @@ pub fn getChildElementCount(self: *DocumentFragment) usize {
|
||||
return count;
|
||||
}
|
||||
|
||||
pub fn append(self: *DocumentFragment, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn append(self: *DocumentFragment, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
const parent = self.asNode();
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(page);
|
||||
_ = try parent.appendChild(child, page);
|
||||
const child = try node_or_text.toNode(frame);
|
||||
_ = try parent.appendChild(child, frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepend(self: *DocumentFragment, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
pub fn prepend(self: *DocumentFragment, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
const parent = self.asNode();
|
||||
var i = nodes.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
const child = try nodes[i].toNode(page);
|
||||
_ = try parent.insertBefore(child, parent.firstChild(), page);
|
||||
const child = try nodes[i].toNode(frame);
|
||||
_ = try parent.insertBefore(child, parent.firstChild(), frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replaceChildren(self: *DocumentFragment, nodes: []const Node.NodeOrText, page: *Page) !void {
|
||||
return self.asNode().replaceChildren(nodes, page);
|
||||
pub fn replaceChildren(self: *DocumentFragment, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
return self.asNode().replaceChildren(nodes, frame);
|
||||
}
|
||||
|
||||
pub fn getInnerHTML(self: *DocumentFragment, writer: *std.Io.Writer, page: *Page) !void {
|
||||
pub fn getInnerHTML(self: *DocumentFragment, writer: *std.Io.Writer, frame: *Frame) !void {
|
||||
const dump = @import("../dump.zig");
|
||||
return dump.children(self.asNode(), .{ .shadow = .complete }, writer, page);
|
||||
return dump.children(self.asNode(), .{ .shadow = .complete }, writer, frame);
|
||||
}
|
||||
|
||||
pub fn setInnerHTML(self: *DocumentFragment, html: []const u8, page: *Page) !void {
|
||||
pub fn setInnerHTML(self: *DocumentFragment, html: []const u8, frame: *Frame) !void {
|
||||
const parent = self.asNode();
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
var it = parent.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
page.removeNode(parent, child, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(parent, child, .{ .will_be_reconnected = false });
|
||||
}
|
||||
|
||||
if (html.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try page.parseHtmlAsChildren(parent, html);
|
||||
try frame.parseHtmlAsChildren(parent, html);
|
||||
}
|
||||
|
||||
pub fn cloneFragment(self: *DocumentFragment, deep: bool, page: *Page) !*Node {
|
||||
const fragment = try DocumentFragment.init(page);
|
||||
pub fn cloneFragment(self: *DocumentFragment, deep: bool, frame: *Frame) !*Node {
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
const fragment_node = fragment.asNode();
|
||||
|
||||
if (deep) {
|
||||
@@ -177,8 +177,8 @@ pub fn cloneFragment(self: *DocumentFragment, deep: bool, page: *Page) !*Node {
|
||||
|
||||
var child_it = node.childrenIterator();
|
||||
while (child_it.next()) |child| {
|
||||
if (try child.cloneNodeForAppending(true, page)) |cloned_child| {
|
||||
try page.appendNode(fragment_node, cloned_child, .{ .child_already_connected = self_is_connected });
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned_child| {
|
||||
try frame.appendNode(fragment_node, cloned_child, .{ .child_already_connected = self_is_connected });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,9 +244,9 @@ pub const JsApi = struct {
|
||||
pub const replaceChildren = bridge.function(DocumentFragment.replaceChildren, .{ .dom_exception = true });
|
||||
pub const innerHTML = bridge.accessor(_innerHTML, DocumentFragment.setInnerHTML, .{});
|
||||
|
||||
fn _innerHTML(self: *DocumentFragment, page: *Page) ![]const u8 {
|
||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
try self.getInnerHTML(&buf.writer, page);
|
||||
fn _innerHTML(self: *DocumentFragment, frame: *Frame) ![]const u8 {
|
||||
var buf = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
try self.getInnerHTML(&buf.writer, frame);
|
||||
return buf.written();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Node = @import("Node.zig");
|
||||
|
||||
@@ -30,13 +30,13 @@ _name: []const u8,
|
||||
_public_id: []const u8,
|
||||
_system_id: []const u8,
|
||||
|
||||
pub fn init(qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, page: *Page) !*DocumentType {
|
||||
const name = try page.dupeString(qualified_name);
|
||||
pub fn init(qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, frame: *Frame) !*DocumentType {
|
||||
const name = try frame.dupeString(qualified_name);
|
||||
// Firefox converts null to the string "null", not empty string
|
||||
const pub_id = if (public_id) |p| try page.dupeString(p) else "null";
|
||||
const sys_id = if (system_id) |s| try page.dupeString(s) else "null";
|
||||
const pub_id = if (public_id) |p| try frame.dupeString(p) else "null";
|
||||
const sys_id = if (system_id) |s| try frame.dupeString(s) else "null";
|
||||
|
||||
return page._factory.node(DocumentType{
|
||||
return frame._factory.node(DocumentType{
|
||||
._proto = undefined,
|
||||
._name = name,
|
||||
._public_id = pub_id,
|
||||
@@ -70,14 +70,14 @@ pub fn isEqualNode(self: *const DocumentType, other: *const DocumentType) bool {
|
||||
std.mem.eql(u8, self._system_id, other._system_id);
|
||||
}
|
||||
|
||||
pub fn clone(self: *const DocumentType, page: *Page) !*DocumentType {
|
||||
return .init(self._name, self._public_id, self._system_id, page);
|
||||
pub fn clone(self: *const DocumentType, frame: *Frame) !*DocumentType {
|
||||
return .init(self._name, self._public_id, self._system_id, frame);
|
||||
}
|
||||
|
||||
pub fn remove(self: *DocumentType, page: *Page) !void {
|
||||
pub fn remove(self: *DocumentType, frame: *Frame) !void {
|
||||
const node = self.asNode();
|
||||
const parent = node.parentNode() orelse return;
|
||||
_ = try parent.removeChild(node, page);
|
||||
_ = try parent.removeChild(node, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ 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");
|
||||
@@ -89,16 +89,16 @@ pub const Options = struct {
|
||||
composed: bool = false,
|
||||
};
|
||||
|
||||
pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*Event {
|
||||
const arena = try page.getArena(.tiny, "Event");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(typ: []const u8, opts_: ?Options, frame: *Frame) !*Event {
|
||||
const arena = try frame.getArena(.tiny, "Event");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const str = try String.init(arena, typ, .{});
|
||||
return initWithTrusted(arena, str, opts_, false);
|
||||
}
|
||||
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, page: *Page) !*Event {
|
||||
const arena = try page.getArena(.tiny, "Event.trusted");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn initTrusted(typ: String, opts_: ?Options, frame: *Frame) !*Event {
|
||||
const arena = try frame.getArena(.tiny, "Event.trusted");
|
||||
errdefer frame.releaseArena(arena);
|
||||
return initWithTrusted(arena, typ, opts_, true);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ pub fn getIsTrusted(self: *const Event) bool {
|
||||
return self._is_trusted;
|
||||
}
|
||||
|
||||
pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget {
|
||||
pub fn composedPath(self: *Event, frame: *Frame) ![]const *EventTarget {
|
||||
// Return empty array if event is not being dispatched
|
||||
if (self._event_phase == .none) {
|
||||
return &.{};
|
||||
@@ -332,7 +332,7 @@ pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget {
|
||||
// Add window at the end (unless we stopped at shadow boundary)
|
||||
if (!stopped_at_shadow_boundary) {
|
||||
if (path_len < path_buffer.len) {
|
||||
path_buffer[path_len] = page.window.asEventTarget();
|
||||
path_buffer[path_len] = frame.window.asEventTarget();
|
||||
path_len += 1;
|
||||
}
|
||||
}
|
||||
@@ -369,7 +369,7 @@ pub fn composedPath(self: *Event, page: *Page) ![]const *EventTarget {
|
||||
const visible_path_len = if (path_len > visible_start_index) path_len - visible_start_index else 0;
|
||||
|
||||
// Allocate and return the visible path using call_arena (short-lived)
|
||||
const path = try page.call_arena.alloc(*EventTarget, visible_path_len);
|
||||
const path = try frame.call_arena.alloc(*EventTarget, visible_path_len);
|
||||
@memcpy(path, path_buffer[visible_start_index..path_len]);
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 log = lp.log;
|
||||
|
||||
@@ -100,16 +100,16 @@ pub fn getSize(_: *const EventCounts) u32 {
|
||||
return tracked_event_types.len;
|
||||
}
|
||||
|
||||
pub fn keys(self: *EventCounts, page: *Page) !*KeyIterator {
|
||||
return .init(.{ .event_counts = self }, page);
|
||||
pub fn keys(self: *EventCounts, frame: *Frame) !*KeyIterator {
|
||||
return .init(.{ .event_counts = self }, frame);
|
||||
}
|
||||
|
||||
pub fn values(self: *EventCounts, page: *Page) !*ValueIterator {
|
||||
return .init(.{ .event_counts = self }, page);
|
||||
pub fn values(self: *EventCounts, frame: *Frame) !*ValueIterator {
|
||||
return .init(.{ .event_counts = self }, frame);
|
||||
}
|
||||
|
||||
pub fn entries(self: *EventCounts, page: *Page) !*EntryIterator {
|
||||
return .init(.{ .event_counts = self }, page);
|
||||
pub fn entries(self: *EventCounts, frame: *Frame) !*EntryIterator {
|
||||
return .init(.{ .event_counts = self }, frame);
|
||||
}
|
||||
|
||||
pub fn forEach(self: *EventCounts, cb_: js.Function, js_this_: ?js.Object) !void {
|
||||
@@ -158,7 +158,7 @@ pub const Iterator = struct {
|
||||
|
||||
pub const Entry = struct { []const u8, u32 };
|
||||
|
||||
pub fn next(self: *Iterator, _: *const Page) ?Iterator.Entry {
|
||||
pub fn next(self: *Iterator, _: *const Frame) ?Iterator.Entry {
|
||||
const index = self.index;
|
||||
if (index >= tracked_event_types.len) {
|
||||
return null;
|
||||
|
||||
@@ -65,10 +65,10 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, exec: *js.Execution) !bo
|
||||
event._is_trusted = false;
|
||||
|
||||
switch (exec.context.global) {
|
||||
.page => |page| {
|
||||
.frame => |frame| {
|
||||
event.acquireRef();
|
||||
defer _ = event.releaseRef(page._session);
|
||||
try page._event_manager.dispatch(self, event);
|
||||
defer _ = event.releaseRef(frame._session);
|
||||
try frame._event_manager.dispatch(self, event);
|
||||
},
|
||||
.worker => |wgs| try wgs.dispatch(self, event, null),
|
||||
}
|
||||
@@ -101,7 +101,7 @@ pub fn addEventListener(self: *EventTarget, typ: []const u8, callback_: ?EventLi
|
||||
};
|
||||
|
||||
switch (exec.context.global) {
|
||||
.page => |page| _ = try page._event_manager.register(self, typ, em_callback, options),
|
||||
.frame => |frame| _ = try frame._event_manager.register(self, typ, em_callback, options),
|
||||
.worker => |wgs| _ = try wgs._event_manager.register(self, typ, em_callback, options),
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback_: ?Even
|
||||
};
|
||||
|
||||
switch (exec.context.global) {
|
||||
.page => |page| page._event_manager.remove(self, typ, em_callback, use_capture),
|
||||
.frame => |frame| frame._event_manager.remove(self, typ, em_callback, use_capture),
|
||||
.worker => |wgs| wgs._event_manager.remove(self, typ, em_callback, use_capture),
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ pub const JsApi = struct {
|
||||
|
||||
const testing = @import("../../testing.zig");
|
||||
test "WebApi: EventTarget" {
|
||||
// we create thousands of these per page. Nothing should bloat it.
|
||||
// we create thousands of these per frame. Nothing should bloat it.
|
||||
try testing.expectEqual(16, @sizeOf(EventTarget));
|
||||
try testing.htmlRunner("events.html", .{});
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ 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");
|
||||
@@ -34,7 +34,7 @@ const Allocator = std.mem.Allocator;
|
||||
const FileReader = @This();
|
||||
|
||||
_rc: lp.RC(u8) = .{},
|
||||
_page: *Page,
|
||||
_frame: *Frame,
|
||||
_proto: *EventTarget,
|
||||
_arena: Allocator,
|
||||
|
||||
@@ -62,12 +62,12 @@ const Result = union(enum) {
|
||||
arraybuffer: js.ArrayBuffer,
|
||||
};
|
||||
|
||||
pub fn init(page: *Page) !*FileReader {
|
||||
const arena = try page.getArena(.tiny, "FileReader");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(frame: *Frame) !*FileReader {
|
||||
const arena = try frame.getArena(.tiny, "FileReader");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
return page._factory.eventTargetWithAllocator(arena, FileReader{
|
||||
._page = page,
|
||||
return frame._factory.eventTargetWithAllocator(arena, FileReader{
|
||||
._frame = frame,
|
||||
._arena = arena,
|
||||
._proto = undefined,
|
||||
});
|
||||
@@ -191,9 +191,9 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
self._error = null;
|
||||
self._aborted = false;
|
||||
|
||||
const page = self._page;
|
||||
const frame = self._frame;
|
||||
|
||||
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, page);
|
||||
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, frame);
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -201,7 +201,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
// Perform the read (synchronous since data is in memory)
|
||||
const data = blob._slice;
|
||||
const size = data.len;
|
||||
try self.dispatch(.progress, .{ .loaded = size, .total = size }, page);
|
||||
try self.dispatch(.progress, .{ .loaded = size, .total = size }, frame);
|
||||
if (self._aborted) {
|
||||
return;
|
||||
}
|
||||
@@ -221,8 +221,8 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
|
||||
self._ready_state = .done;
|
||||
|
||||
try self.dispatch(.load, .{ .loaded = size, .total = size }, page);
|
||||
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, page);
|
||||
try self.dispatch(.load, .{ .loaded = size, .total = size }, frame);
|
||||
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, frame);
|
||||
}
|
||||
|
||||
pub fn abort(self: *FileReader) !void {
|
||||
@@ -234,14 +234,14 @@ pub fn abort(self: *FileReader) !void {
|
||||
self._ready_state = .done;
|
||||
self._result = null;
|
||||
|
||||
const page = self._page;
|
||||
const frame = self._frame;
|
||||
|
||||
try self.dispatch(.abort, null, page);
|
||||
try self.dispatch(.abort, null, frame);
|
||||
|
||||
try self.dispatch(.load_end, null, page);
|
||||
try self.dispatch(.load_end, null, frame);
|
||||
}
|
||||
|
||||
fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Progress, page: *Page) !void {
|
||||
fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Progress, frame: *Frame) !void {
|
||||
const field, const typ = comptime blk: {
|
||||
break :blk switch (event_type) {
|
||||
.abort => .{ "_on_abort", "abort" },
|
||||
@@ -257,10 +257,10 @@ fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Pr
|
||||
const event = (try ProgressEvent.initTrusted(
|
||||
comptime .wrap(typ),
|
||||
.{ .total = progress.total, .loaded = progress.loaded },
|
||||
page,
|
||||
frame,
|
||||
)).asEvent();
|
||||
|
||||
return page._event_manager.dispatchDirect(
|
||||
return frame._event_manager.dispatchDirect(
|
||||
self.asEventTarget(),
|
||||
event,
|
||||
@field(self, field),
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Node = @import("Node.zig");
|
||||
const Document = @import("Document.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -68,7 +68,7 @@ pub fn getBody(self: *HTMLDocument) ?*Element.Html.Body {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getTitle(self: *HTMLDocument, page: *Page) ![]const u8 {
|
||||
pub fn getTitle(self: *HTMLDocument, frame: *Frame) ![]const u8 {
|
||||
// Search the entire document for the first <title> element
|
||||
const root = self._proto.getDocumentElement() orelse return "";
|
||||
const title_element = blk: {
|
||||
@@ -81,7 +81,7 @@ pub fn getTitle(self: *HTMLDocument, page: *Page) ![]const u8 {
|
||||
return "";
|
||||
};
|
||||
|
||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
var buf = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
try title_element.asNode().getTextContent(&buf.writer);
|
||||
const text = buf.written();
|
||||
|
||||
@@ -92,7 +92,7 @@ pub fn getTitle(self: *HTMLDocument, page: *Page) ![]const u8 {
|
||||
var started = false;
|
||||
var in_whitespace = false;
|
||||
var result: std.ArrayList(u8) = .empty;
|
||||
try result.ensureTotalCapacity(page.call_arena, text.len);
|
||||
try result.ensureTotalCapacity(frame.call_arena, text.len);
|
||||
|
||||
for (text) |c| {
|
||||
const is_ascii_ws = c == ' ' or c == '\t' or c == '\n' or c == '\r' or c == '\x0C';
|
||||
@@ -114,7 +114,7 @@ pub fn getTitle(self: *HTMLDocument, page: *Page) ![]const u8 {
|
||||
return result.items;
|
||||
}
|
||||
|
||||
pub fn setTitle(self: *HTMLDocument, title: []const u8, page: *Page) !void {
|
||||
pub fn setTitle(self: *HTMLDocument, title: []const u8, frame: *Frame) !void {
|
||||
const head = self.getHead() orelse return;
|
||||
|
||||
// Find existing title element in head
|
||||
@@ -123,47 +123,47 @@ pub fn setTitle(self: *HTMLDocument, title: []const u8, page: *Page) !void {
|
||||
if (node.is(Element.Html.Title)) |title_element| {
|
||||
// Replace children, but don't create text node for empty string
|
||||
if (title.len == 0) {
|
||||
return title_element.asElement().replaceChildren(&.{}, page);
|
||||
return title_element.asElement().replaceChildren(&.{}, frame);
|
||||
} else {
|
||||
return title_element.asElement().replaceChildren(&.{.{ .text = title }}, page);
|
||||
return title_element.asElement().replaceChildren(&.{.{ .text = title }}, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No title element found, create one
|
||||
const title_node = try page.createElementNS(.html, "title", null);
|
||||
const title_node = try frame.createElementNS(.html, "title", null);
|
||||
const title_element = title_node.as(Element);
|
||||
|
||||
// Only add text if non-empty
|
||||
if (title.len > 0) {
|
||||
try title_element.replaceChildren(&.{.{ .text = title }}, page);
|
||||
try title_element.replaceChildren(&.{.{ .text = title }}, frame);
|
||||
}
|
||||
|
||||
_ = try head.asNode().appendChild(title_node, page);
|
||||
_ = try head.asNode().appendChild(title_node, frame);
|
||||
}
|
||||
|
||||
pub fn getImages(self: *HTMLDocument, page: *Page) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .img, page);
|
||||
pub fn getImages(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .img, frame);
|
||||
}
|
||||
|
||||
pub fn getScripts(self: *HTMLDocument, page: *Page) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .script, page);
|
||||
pub fn getScripts(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .script, frame);
|
||||
}
|
||||
|
||||
pub fn getLinks(self: *HTMLDocument, page: *Page) !collections.NodeLive(.links) {
|
||||
return collections.NodeLive(.links).init(self.asNode(), {}, page);
|
||||
pub fn getLinks(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.links) {
|
||||
return collections.NodeLive(.links).init(self.asNode(), {}, frame);
|
||||
}
|
||||
|
||||
pub fn getAnchors(self: *HTMLDocument, page: *Page) !collections.NodeLive(.anchors) {
|
||||
return collections.NodeLive(.anchors).init(self.asNode(), {}, page);
|
||||
pub fn getAnchors(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.anchors) {
|
||||
return collections.NodeLive(.anchors).init(self.asNode(), {}, frame);
|
||||
}
|
||||
|
||||
pub fn getForms(self: *HTMLDocument, page: *Page) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .form, page);
|
||||
pub fn getForms(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .form, frame);
|
||||
}
|
||||
|
||||
pub fn getEmbeds(self: *HTMLDocument, page: *Page) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .embed, page);
|
||||
pub fn getEmbeds(self: *HTMLDocument, frame: *Frame) !collections.NodeLive(.tag) {
|
||||
return collections.NodeLive(.tag).init(self.asNode(), .embed, frame);
|
||||
}
|
||||
|
||||
pub fn getApplets(_: *const HTMLDocument) collections.HTMLCollection {
|
||||
@@ -178,8 +178,8 @@ pub fn getLocation(self: *const HTMLDocument) ?*@import("Location.zig") {
|
||||
return self._proto._location;
|
||||
}
|
||||
|
||||
pub fn setLocation(self: *HTMLDocument, url: [:0]const u8, page: *Page) !void {
|
||||
return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._proto._page });
|
||||
pub fn setLocation(self: *HTMLDocument, url: [:0]const u8, frame: *Frame) !void {
|
||||
return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._proto._frame });
|
||||
}
|
||||
|
||||
pub fn getDir(self: *HTMLDocument) []const u8 {
|
||||
@@ -188,10 +188,10 @@ pub fn getDir(self: *HTMLDocument) []const u8 {
|
||||
return html.getDir();
|
||||
}
|
||||
|
||||
pub fn setDir(self: *HTMLDocument, value: []const u8, page: *Page) !void {
|
||||
pub fn setDir(self: *HTMLDocument, value: []const u8, frame: *Frame) !void {
|
||||
const el = self._proto.getDocumentElement() orelse return;
|
||||
const html = el.is(Element.Html) orelse return;
|
||||
try html.setDir(value, page);
|
||||
try html.setDir(value, frame);
|
||||
}
|
||||
|
||||
pub fn getLang(self: *HTMLDocument) []const u8 {
|
||||
@@ -200,30 +200,30 @@ pub fn getLang(self: *HTMLDocument) []const u8 {
|
||||
return html.getLang();
|
||||
}
|
||||
|
||||
pub fn setLang(self: *HTMLDocument, value: []const u8, page: *Page) !void {
|
||||
pub fn setLang(self: *HTMLDocument, value: []const u8, frame: *Frame) !void {
|
||||
const el = self._proto.getDocumentElement() orelse return;
|
||||
const html = el.is(Element.Html) orelse return;
|
||||
try html.setLang(value, page);
|
||||
try html.setLang(value, frame);
|
||||
}
|
||||
|
||||
pub fn getAll(self: *HTMLDocument, page: *Page) !*collections.HTMLAllCollection {
|
||||
return page._factory.create(collections.HTMLAllCollection.init(self.asNode(), page));
|
||||
pub fn getAll(self: *HTMLDocument, frame: *Frame) !*collections.HTMLAllCollection {
|
||||
return frame._factory.create(collections.HTMLAllCollection.init(self.asNode(), frame));
|
||||
}
|
||||
|
||||
pub fn getCookie(_: *HTMLDocument, page: *Page) ![]const u8 {
|
||||
pub fn getCookie(_: *HTMLDocument, frame: *Frame) ![]const u8 {
|
||||
var buf: std.ArrayList(u8) = .empty;
|
||||
try page._session.cookie_jar.forRequest(page.url, buf.writer(page.call_arena), .{
|
||||
try frame._session.cookie_jar.forRequest(frame.url, buf.writer(frame.call_arena), .{
|
||||
.is_http = false,
|
||||
.is_navigation = true,
|
||||
});
|
||||
return buf.items;
|
||||
}
|
||||
|
||||
pub fn setCookie(_: *HTMLDocument, cookie_str: []const u8, page: *Page) ![]const u8 {
|
||||
pub fn setCookie(_: *HTMLDocument, cookie_str: []const u8, frame: *Frame) ![]const u8 {
|
||||
// we use the cookie jar's allocator to parse the cookie because it
|
||||
// outlives the page's arena.
|
||||
// outlives the frame's arena.
|
||||
const Cookie = @import("storage/Cookie.zig");
|
||||
const c = Cookie.parse(page._session.cookie_jar.allocator, page.url, cookie_str) catch {
|
||||
const c = Cookie.parse(frame._session.cookie_jar.allocator, frame.url, cookie_str) catch {
|
||||
// Invalid cookies should be silently ignored, not throw errors
|
||||
return "";
|
||||
};
|
||||
@@ -232,11 +232,11 @@ pub fn setCookie(_: *HTMLDocument, cookie_str: []const u8, page: *Page) ![]const
|
||||
c.deinit();
|
||||
return ""; // HttpOnly cookies cannot be set from JS
|
||||
}
|
||||
try page._session.cookie_jar.add(c, std.time.timestamp());
|
||||
try frame._session.cookie_jar.add(c, std.time.timestamp());
|
||||
return cookie_str;
|
||||
}
|
||||
|
||||
pub fn getDocType(self: *HTMLDocument, page: *Page) !*DocumentType {
|
||||
pub fn getDocType(self: *HTMLDocument, frame: *Frame) !*DocumentType {
|
||||
if (self._document_type) |dt| {
|
||||
return dt;
|
||||
}
|
||||
@@ -249,7 +249,7 @@ pub fn getDocType(self: *HTMLDocument, page: *Page) !*DocumentType {
|
||||
}
|
||||
}
|
||||
|
||||
self._document_type = try page._factory.node(DocumentType{
|
||||
self._document_type = try frame._factory.node(DocumentType{
|
||||
._proto = undefined,
|
||||
._name = "html",
|
||||
._public_id = "",
|
||||
@@ -268,8 +268,8 @@ pub const JsApi = struct {
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(_constructor, .{});
|
||||
fn _constructor(page: *Page) !*HTMLDocument {
|
||||
return page._factory.document(HTMLDocument{
|
||||
fn _constructor(frame: *Frame) !*HTMLDocument {
|
||||
return frame._factory.document(HTMLDocument{
|
||||
._proto = undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Location = @import("Location.zig");
|
||||
const PopStateEvent = @import("event/PopStateEvent.zig");
|
||||
const URL = @import("URL.zig");
|
||||
@@ -30,13 +30,13 @@ const ScrollRestoration = enum { auto, manual };
|
||||
|
||||
_scroll_restoration: ScrollRestoration = .auto,
|
||||
|
||||
pub fn getLength(_: *const History, page: *Page) u32 {
|
||||
return @intCast(page._session.navigation._entries.items.len);
|
||||
pub fn getLength(_: *const History, frame: *Frame) u32 {
|
||||
return @intCast(frame._session.navigation._entries.items.len);
|
||||
}
|
||||
|
||||
pub fn getState(_: *const History, page: *Page) !?js.Value {
|
||||
if (page._session.navigation.getCurrentEntry()._state.value) |state| {
|
||||
const value = try page.js.local.?.parseJSON(state);
|
||||
pub fn getState(_: *const History, frame: *Frame) !?js.Value {
|
||||
if (frame._session.navigation.getCurrentEntry()._state.value) |state| {
|
||||
const value = try frame.js.local.?.parseJSON(state);
|
||||
return value;
|
||||
} else return null;
|
||||
}
|
||||
@@ -51,69 +51,69 @@ pub fn setScrollRestoration(self: *History, str: []const u8) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pushState(_: *History, state: js.Value, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
||||
const arena = page._session.arena;
|
||||
pub fn pushState(_: *History, state: js.Value, _: ?[]const u8, _url: ?[]const u8, frame: *Frame) !void {
|
||||
const arena = frame._session.arena;
|
||||
const url = if (_url) |u|
|
||||
try @import("../URL.zig").resolve(arena, page.url, u, .{ .always_dupe = true })
|
||||
try @import("../URL.zig").resolve(arena, frame.url, u, .{ .always_dupe = true })
|
||||
else
|
||||
try arena.dupeZ(u8, page.url);
|
||||
try arena.dupeZ(u8, frame.url);
|
||||
|
||||
const json = state.toJson(arena) catch return error.DataClone;
|
||||
_ = try page._session.navigation.pushEntry(url, .{ .source = .history, .value = json }, page, true);
|
||||
_ = try frame._session.navigation.pushEntry(url, .{ .source = .history, .value = json }, frame, true);
|
||||
|
||||
page.url = url;
|
||||
page.window._location._url = try URL.init(url, null, &page.js.execution);
|
||||
frame.url = url;
|
||||
frame.window._location._url = try URL.init(url, null, &frame.js.execution);
|
||||
}
|
||||
|
||||
pub fn replaceState(_: *History, state: js.Value, _: ?[]const u8, _url: ?[]const u8, page: *Page) !void {
|
||||
const arena = page._session.arena;
|
||||
pub fn replaceState(_: *History, state: js.Value, _: ?[]const u8, _url: ?[]const u8, frame: *Frame) !void {
|
||||
const arena = frame._session.arena;
|
||||
const url = if (_url) |u|
|
||||
try @import("../URL.zig").resolve(arena, page.url, u, .{ .always_dupe = true })
|
||||
try @import("../URL.zig").resolve(arena, frame.url, u, .{ .always_dupe = true })
|
||||
else
|
||||
try arena.dupeZ(u8, page.url);
|
||||
try arena.dupeZ(u8, frame.url);
|
||||
|
||||
const json = state.toJson(arena) catch return error.DataClone;
|
||||
_ = try page._session.navigation.replaceEntry(url, .{ .source = .history, .value = json }, page, true);
|
||||
_ = try frame._session.navigation.replaceEntry(url, .{ .source = .history, .value = json }, frame, true);
|
||||
|
||||
page.url = url;
|
||||
page.window._location = try Location.init(url, page);
|
||||
frame.url = url;
|
||||
frame.window._location = try Location.init(url, frame);
|
||||
}
|
||||
|
||||
fn goInner(delta: i32, page: *Page) !void {
|
||||
// 0 behaves the same as no argument, both reloading the page.
|
||||
fn goInner(delta: i32, frame: *Frame) !void {
|
||||
// 0 behaves the same as no argument, both reloading the frame.
|
||||
|
||||
const current = page._session.navigation._index;
|
||||
const current = frame._session.navigation._index;
|
||||
const index_s: i64 = @intCast(@as(i64, @intCast(current)) + @as(i64, @intCast(delta)));
|
||||
if (index_s < 0 or index_s > page._session.navigation._entries.items.len - 1) {
|
||||
if (index_s < 0 or index_s > frame._session.navigation._entries.items.len - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = @as(usize, @intCast(index_s));
|
||||
const entry = page._session.navigation._entries.items[index];
|
||||
const entry = frame._session.navigation._entries.items[index];
|
||||
|
||||
if (entry._url) |url| {
|
||||
if (page.isSameOrigin(url)) {
|
||||
const target = page.window.asEventTarget();
|
||||
if (page._event_manager.hasDirectListeners(target, "popstate", page.window._on_popstate)) {
|
||||
const event = (try PopStateEvent.initTrusted(comptime .wrap("popstate"), .{ .state = entry._state.value }, page)).asEvent();
|
||||
try page._event_manager.dispatchDirect(target, event, page.window._on_popstate, .{ .context = "Pop State" });
|
||||
if (frame.isSameOrigin(url)) {
|
||||
const target = frame.window.asEventTarget();
|
||||
if (frame._event_manager.hasDirectListeners(target, "popstate", frame.window._on_popstate)) {
|
||||
const event = (try PopStateEvent.initTrusted(comptime .wrap("popstate"), .{ .state = entry._state.value }, frame)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(target, event, frame.window._on_popstate, .{ .context = "Pop State" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = try page._session.navigation.navigateInner(entry._url, .{ .traverse = index }, page);
|
||||
_ = try frame._session.navigation.navigateInner(entry._url, .{ .traverse = index }, frame);
|
||||
}
|
||||
|
||||
pub fn back(_: *History, page: *Page) !void {
|
||||
try goInner(-1, page);
|
||||
pub fn back(_: *History, frame: *Frame) !void {
|
||||
try goInner(-1, frame);
|
||||
}
|
||||
|
||||
pub fn forward(_: *History, page: *Page) !void {
|
||||
try goInner(1, page);
|
||||
pub fn forward(_: *History, frame: *Frame) !void {
|
||||
try goInner(1, frame);
|
||||
}
|
||||
|
||||
pub fn go(_: *History, delta: ?i32, page: *Page) !void {
|
||||
try goInner(delta orelse 0, page);
|
||||
pub fn go(_: *History, delta: ?i32, frame: *Frame) !void {
|
||||
try goInner(delta orelse 0, frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 String = lp.String;
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn init(
|
||||
width: u32,
|
||||
height: u32,
|
||||
maybe_settings: ?ConstructorSettings,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*ImageData {
|
||||
// Though arguments are unsigned long, these are capped to max. i32 on Chrome.
|
||||
// https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/core/html/canvas/image_data.cc#L61
|
||||
@@ -77,10 +77,10 @@ pub fn init(
|
||||
size, overflown = @mulWithOverflow(size, 4);
|
||||
if (overflown == 1) return error.IndexSizeError;
|
||||
|
||||
return page._factory.create(ImageData{
|
||||
return frame._factory.create(ImageData{
|
||||
._width = width,
|
||||
._height = height,
|
||||
._data = try page.js.local.?.createTypedArray(.uint8_clamped, size).persist(),
|
||||
._data = try frame.js.local.?.createTypedArray(.uint8_clamped, size).persist(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ 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");
|
||||
@@ -69,9 +69,9 @@ pub const ObserverInit = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*IntersectionObserver {
|
||||
const arena = try page.getArena(.small, "IntersectionObserver");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(callback: js.Function.Temp, options: ?ObserverInit, frame: *Frame) !*IntersectionObserver {
|
||||
const arena = try frame.getArena(.small, "IntersectionObserver");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const opts = options orelse ObserverInit{};
|
||||
const root_margin = if (opts.rootMargin) |rm| try arena.dupe(u8, rm) else "0px";
|
||||
@@ -128,7 +128,7 @@ pub fn acquireRef(self: *IntersectionObserver) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void {
|
||||
pub fn observe(self: *IntersectionObserver, target: *Element, frame: *Frame) !void {
|
||||
// Check if already observing this target
|
||||
for (self._observing.items) |elem| {
|
||||
if (elem == target) {
|
||||
@@ -138,20 +138,20 @@ pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void
|
||||
|
||||
try self._observing.append(self._arena, target);
|
||||
if (self._observing.items.len == 1) {
|
||||
try page.registerIntersectionObserver(self);
|
||||
try frame.registerIntersectionObserver(self);
|
||||
}
|
||||
|
||||
// Don't initialize previous state yet - let checkIntersection do it
|
||||
// This ensures we get an entry on first observation
|
||||
|
||||
// Check intersection for this new target and schedule delivery
|
||||
try self.checkIntersection(target, page);
|
||||
try self.checkIntersection(target, frame);
|
||||
if (self._pending_entries.items.len > 0) {
|
||||
try page.scheduleIntersectionDelivery();
|
||||
try frame.scheduleIntersectionDelivery();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) void {
|
||||
pub fn unobserve(self: *IntersectionObserver, target: *Element, frame: *Frame) void {
|
||||
const original_length = self._observing.items.len;
|
||||
for (self._observing.items, 0..) |elem, i| {
|
||||
if (elem == target) {
|
||||
@@ -164,7 +164,7 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
|
||||
while (j < self._pending_entries.items.len) {
|
||||
if (self._pending_entries.items[j]._target == target) {
|
||||
const entry = self._pending_entries.swapRemove(j);
|
||||
entry.deinit(page._session);
|
||||
entry.deinit(frame._session);
|
||||
} else {
|
||||
j += 1;
|
||||
}
|
||||
@@ -174,25 +174,25 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
|
||||
}
|
||||
|
||||
if (original_length > 0 and self._observing.items.len == 0) {
|
||||
page.unregisterIntersectionObserver(self);
|
||||
frame.unregisterIntersectionObserver(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
|
||||
pub fn disconnect(self: *IntersectionObserver, frame: *Frame) void {
|
||||
for (self._pending_entries.items) |entry| {
|
||||
entry.deinit(page._session);
|
||||
entry.deinit(frame._session);
|
||||
}
|
||||
self._pending_entries.clearRetainingCapacity();
|
||||
self._previous_states.clearRetainingCapacity();
|
||||
|
||||
if (self._observing.items.len > 0) {
|
||||
page.unregisterIntersectionObserver(self);
|
||||
frame.unregisterIntersectionObserver(self);
|
||||
}
|
||||
self._observing.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry {
|
||||
const entries = try page.call_arena.dupe(*IntersectionObserverEntry, self._pending_entries.items);
|
||||
pub fn takeRecords(self: *IntersectionObserver, frame: *Frame) ![]*IntersectionObserverEntry {
|
||||
const entries = try frame.call_arena.dupe(*IntersectionObserverEntry, self._pending_entries.items);
|
||||
self._pending_entries.clearRetainingCapacity();
|
||||
return entries;
|
||||
}
|
||||
@@ -200,13 +200,13 @@ pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObs
|
||||
fn calculateIntersection(
|
||||
self: *IntersectionObserver,
|
||||
target: *Element,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !IntersectionData {
|
||||
const target_rect = target.getBoundingClientRect(page);
|
||||
const target_rect = target.getBoundingClientRect(frame);
|
||||
|
||||
// Use root element's rect or viewport (simplified: assume 1920x1080)
|
||||
const root_rect = if (self._root) |root|
|
||||
root.getBoundingClientRect(page)
|
||||
root.getBoundingClientRect(frame)
|
||||
else
|
||||
// Simplified viewport - assume 1920x1080 for now
|
||||
DOMRect{
|
||||
@@ -253,8 +253,8 @@ fn meetsThreshold(self: *IntersectionObserver, ratio: f64) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page) !void {
|
||||
const data = try self.calculateIntersection(target, page);
|
||||
fn checkIntersection(self: *IntersectionObserver, target: *Element, frame: *Frame) !void {
|
||||
const data = try self.calculateIntersection(target, frame);
|
||||
const was_intersecting_opt = self._previous_states.get(target);
|
||||
const is_now_intersecting = data.is_intersecting and self.meetsThreshold(data.intersection_ratio);
|
||||
|
||||
@@ -265,18 +265,18 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
|
||||
(was_intersecting_opt != null and was_intersecting_opt.? != is_now_intersecting);
|
||||
|
||||
if (should_report) {
|
||||
const arena = try page.getArena(.tiny, "IntersectionObserverEntry");
|
||||
errdefer page.releaseArena(arena);
|
||||
const arena = try frame.getArena(.tiny, "IntersectionObserverEntry");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const entry = try arena.create(IntersectionObserverEntry);
|
||||
entry.* = .{
|
||||
._arena = arena,
|
||||
._target = target,
|
||||
._time = page.window._performance.now(),
|
||||
._time = frame.window._performance.now(),
|
||||
._is_intersecting = is_now_intersecting,
|
||||
._root_bounds = try page._factory.create(data.root_bounds),
|
||||
._intersection_rect = try page._factory.create(data.intersection_rect),
|
||||
._bounding_client_rect = try page._factory.create(data.bounding_client_rect),
|
||||
._root_bounds = try frame._factory.create(data.root_bounds),
|
||||
._intersection_rect = try frame._factory.create(data.intersection_rect),
|
||||
._bounding_client_rect = try frame._factory.create(data.bounding_client_rect),
|
||||
._intersection_ratio = data.intersection_ratio,
|
||||
};
|
||||
try self._pending_entries.append(self._arena, entry);
|
||||
@@ -287,34 +287,34 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
|
||||
try self._previous_states.put(self._arena, target, is_now_intersecting);
|
||||
}
|
||||
|
||||
pub fn checkIntersections(self: *IntersectionObserver, page: *Page) !void {
|
||||
pub fn checkIntersections(self: *IntersectionObserver, frame: *Frame) !void {
|
||||
if (self._observing.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (self._observing.items) |target| {
|
||||
try self.checkIntersection(target, page);
|
||||
try self.checkIntersection(target, frame);
|
||||
}
|
||||
|
||||
if (self._pending_entries.items.len > 0) {
|
||||
try page.scheduleIntersectionDelivery();
|
||||
try frame.scheduleIntersectionDelivery();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
|
||||
pub fn deliverEntries(self: *IntersectionObserver, frame: *Frame) !void {
|
||||
if (self._pending_entries.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = try self.takeRecords(page);
|
||||
const entries = try self.takeRecords(frame);
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
ls.toLocal(self._callback).tryCall(void, .{ entries, self }, &caught) catch |err| {
|
||||
log.err(.page, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
|
||||
log.err(.frame, "IntsctObserver.deliverEntries", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 h5e = @import("../parser/html5ever.zig");
|
||||
|
||||
const String = lp.String;
|
||||
|
||||
@@ -20,15 +20,15 @@ const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const URL = @import("URL.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const Location = @This();
|
||||
|
||||
_url: *URL,
|
||||
|
||||
pub fn init(raw_url: [:0]const u8, page: *Page) !*Location {
|
||||
const url = try URL.init(raw_url, null, &page.js.execution);
|
||||
return page._factory.create(Location{
|
||||
pub fn init(raw_url: [:0]const u8, frame: *Frame) !*Location {
|
||||
const url = try URL.init(raw_url, null, &frame.js.execution);
|
||||
return frame._factory.create(Location{
|
||||
._url = url,
|
||||
});
|
||||
}
|
||||
@@ -65,10 +65,10 @@ pub fn getHash(self: *const Location) []const u8 {
|
||||
return self._url.getHash();
|
||||
}
|
||||
|
||||
pub fn setHash(_: *const Location, hash: []const u8, page: *Page) !void {
|
||||
pub fn setHash(_: *const Location, hash: []const u8, frame: *Frame) !void {
|
||||
const normalized_hash = blk: {
|
||||
if (hash.len == 0) {
|
||||
const old_url = page.url;
|
||||
const old_url = frame.url;
|
||||
|
||||
break :blk if (std.mem.indexOfScalar(u8, old_url, '#')) |index|
|
||||
old_url[0..index]
|
||||
@@ -77,25 +77,25 @@ pub fn setHash(_: *const Location, hash: []const u8, page: *Page) !void {
|
||||
} else if (hash[0] == '#')
|
||||
break :blk hash
|
||||
else
|
||||
break :blk try std.fmt.allocPrint(page.call_arena, "#{s}", .{hash});
|
||||
break :blk try std.fmt.allocPrint(frame.call_arena, "#{s}", .{hash});
|
||||
};
|
||||
|
||||
return page.scheduleNavigation(normalized_hash, .{
|
||||
return frame.scheduleNavigation(normalized_hash, .{
|
||||
.reason = .script,
|
||||
.kind = .{ .replace = null },
|
||||
}, .{ .script = page });
|
||||
}, .{ .script = frame });
|
||||
}
|
||||
|
||||
pub fn assign(_: *const Location, url: [:0]const u8, page: *Page) !void {
|
||||
return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = page });
|
||||
pub fn assign(_: *const Location, url: [:0]const u8, frame: *Frame) !void {
|
||||
return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = frame });
|
||||
}
|
||||
|
||||
pub fn replace(_: *const Location, url: [:0]const u8, page: *Page) !void {
|
||||
return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .replace = null } }, .{ .script = page });
|
||||
pub fn replace(_: *const Location, url: [:0]const u8, frame: *Frame) !void {
|
||||
return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .replace = null } }, .{ .script = frame });
|
||||
}
|
||||
|
||||
pub fn reload(_: *const Location, page: *Page) !void {
|
||||
return page.scheduleNavigation(page.url, .{ .reason = .script, .kind = .reload }, .{ .script = page });
|
||||
pub fn reload(_: *const Location, frame: *Frame) !void {
|
||||
return frame.scheduleNavigation(frame.url, .{ .reason = .script, .kind = .reload }, .{ .script = frame });
|
||||
}
|
||||
|
||||
pub fn toString(self: *const Location, exec: *const js.Execution) ![:0]const u8 {
|
||||
@@ -113,8 +113,8 @@ pub const JsApi = struct {
|
||||
|
||||
pub const toString = bridge.function(Location.toString, .{});
|
||||
pub const href = bridge.accessor(Location.toString, setHref, .{});
|
||||
fn setHref(self: *const Location, url: [:0]const u8, page: *Page) !void {
|
||||
return self.assign(url, page);
|
||||
fn setHref(self: *const Location, url: [:0]const u8, frame: *Frame) !void {
|
||||
return self.assign(url, frame);
|
||||
}
|
||||
|
||||
pub const search = bridge.accessor(Location.getSearch, null, .{});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const MessagePort = @import("MessagePort.zig");
|
||||
|
||||
const MessageChannel = @This();
|
||||
@@ -25,13 +25,13 @@ const MessageChannel = @This();
|
||||
_port1: *MessagePort,
|
||||
_port2: *MessagePort,
|
||||
|
||||
pub fn init(page: *Page) !*MessageChannel {
|
||||
const port1 = try MessagePort.init(page);
|
||||
const port2 = try MessagePort.init(page);
|
||||
pub fn init(frame: *Frame) !*MessageChannel {
|
||||
const port1 = try MessagePort.init(frame);
|
||||
const port2 = try MessagePort.init(frame);
|
||||
|
||||
MessagePort.entangle(port1, port2);
|
||||
|
||||
return page._factory.create(MessageChannel{
|
||||
return frame._factory.create(MessageChannel{
|
||||
._port1 = port1,
|
||||
._port2 = port2,
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 EventTarget = @import("EventTarget.zig");
|
||||
const MessageEvent = @import("event/MessageEvent.zig");
|
||||
@@ -36,8 +36,8 @@ _on_message: ?js.Function.Global = null,
|
||||
_on_message_error: ?js.Function.Global = null,
|
||||
_entangled_port: ?*MessagePort = null,
|
||||
|
||||
pub fn init(page: *Page) !*MessagePort {
|
||||
return page._factory.eventTarget(MessagePort{
|
||||
pub fn init(frame: *Frame) !*MessagePort {
|
||||
return frame._factory.eventTarget(MessagePort{
|
||||
._proto = undefined,
|
||||
});
|
||||
}
|
||||
@@ -51,7 +51,7 @@ pub fn entangle(port1: *MessagePort, port2: *MessagePort) void {
|
||||
port2._entangled_port = port1;
|
||||
}
|
||||
|
||||
pub fn postMessage(self: *MessagePort, message: js.Value.Temp, page: *Page) !void {
|
||||
pub fn postMessage(self: *MessagePort, message: js.Value.Temp, frame: *Frame) !void {
|
||||
if (self._closed) {
|
||||
return;
|
||||
}
|
||||
@@ -62,13 +62,13 @@ pub fn postMessage(self: *MessagePort, message: js.Value.Temp, page: *Page) !voi
|
||||
}
|
||||
|
||||
// Create callback to deliver message
|
||||
const callback = try page._factory.create(PostMessageCallback{
|
||||
.page = page,
|
||||
const callback = try frame._factory.create(PostMessageCallback{
|
||||
.frame = frame,
|
||||
.port = other,
|
||||
.message = message,
|
||||
});
|
||||
|
||||
try page.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||
try frame.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||
.name = "MessagePort.postMessage",
|
||||
.low_priority = false,
|
||||
});
|
||||
@@ -110,33 +110,33 @@ pub fn setOnMessageError(self: *MessagePort, cb: ?js.Function.Global) !void {
|
||||
const PostMessageCallback = struct {
|
||||
port: *MessagePort,
|
||||
message: js.Value.Temp,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
|
||||
fn deinit(self: *PostMessageCallback) void {
|
||||
self.page._factory.destroy(self);
|
||||
self.frame._factory.destroy(self);
|
||||
}
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||
defer self.deinit();
|
||||
const page = self.page;
|
||||
const frame = self.frame;
|
||||
|
||||
if (self.port._closed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const target = self.port.asEventTarget();
|
||||
if (page._event_manager.hasDirectListeners(target, "message", self.port._on_message)) {
|
||||
if (frame._event_manager.hasDirectListeners(target, "message", self.port._on_message)) {
|
||||
const event = (MessageEvent.initTrusted(comptime .wrap("message"), .{
|
||||
.data = .{ .value = self.message },
|
||||
.origin = "",
|
||||
.source = null,
|
||||
}, page._session) catch |err| {
|
||||
}, frame._session) catch |err| {
|
||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||
return null;
|
||||
}).asEvent();
|
||||
|
||||
page._event_manager.dispatchDirect(target, event, self.port._on_message, .{ .context = "MessagePort message" }) catch |err| {
|
||||
frame._event_manager.dispatchDirect(target, event, self.port._on_message, .{ .context = "MessagePort message" }) catch |err| {
|
||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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");
|
||||
@@ -46,7 +46,7 @@ _callback: js.Function.Temp,
|
||||
_observing: std.ArrayList(Observing) = .{},
|
||||
_pending_records: std.ArrayList(*MutationRecord) = .{},
|
||||
|
||||
/// Intrusively linked to next element (see Page.zig).
|
||||
/// Intrusively linked to next element (see Frame.zig).
|
||||
node: std.DoublyLinkedList.Node = .{},
|
||||
|
||||
const Observing = struct {
|
||||
@@ -75,9 +75,9 @@ pub const ObserveOptions = struct {
|
||||
attributeFilter: ?[]const []const u8 = null,
|
||||
};
|
||||
|
||||
pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
|
||||
const arena = try page.getArena(.small, "MutationObserver");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(callback: js.Function.Temp, frame: *Frame) !*MutationObserver {
|
||||
const arena = try frame.getArena(.small, "MutationObserver");
|
||||
errdefer frame.releaseArena(arena);
|
||||
const self = try arena.create(MutationObserver);
|
||||
self.* = .{
|
||||
._arena = arena,
|
||||
@@ -104,7 +104,7 @@ pub fn acquireRef(self: *MutationObserver) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
|
||||
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, frame: *Frame) !void {
|
||||
const arena = self._arena;
|
||||
|
||||
// Per spec: if attributeOldValue/attributeFilter present and attributes
|
||||
@@ -172,24 +172,24 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
|
||||
});
|
||||
|
||||
if (self._observing.items.len == 1) {
|
||||
try page.registerMutationObserver(self);
|
||||
try frame.registerMutationObserver(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(self: *MutationObserver, page: *Page) void {
|
||||
pub fn disconnect(self: *MutationObserver, frame: *Frame) void {
|
||||
for (self._pending_records.items) |record| {
|
||||
record.deinit(page._session);
|
||||
record.deinit(frame._session);
|
||||
}
|
||||
self._pending_records.clearRetainingCapacity();
|
||||
|
||||
if (self._observing.items.len > 0) {
|
||||
page.unregisterMutationObserver(self);
|
||||
frame.unregisterMutationObserver(self);
|
||||
}
|
||||
self._observing.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord {
|
||||
const records = try page.call_arena.dupe(*MutationRecord, self._pending_records.items);
|
||||
pub fn takeRecords(self: *MutationObserver, frame: *Frame) ![]*MutationRecord {
|
||||
const records = try frame.call_arena.dupe(*MutationRecord, self._pending_records.items);
|
||||
self._pending_records.clearRetainingCapacity();
|
||||
return records;
|
||||
}
|
||||
@@ -200,7 +200,7 @@ pub fn notifyAttributeChange(
|
||||
target: *Element,
|
||||
attribute_name: String,
|
||||
old_value: ?String,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
const target_node = target.asNode();
|
||||
|
||||
@@ -226,7 +226,7 @@ pub fn notifyAttributeChange(
|
||||
}
|
||||
}
|
||||
|
||||
const arena = try page.getArena(.tiny, "MutationRecord");
|
||||
const arena = try frame.getArena(.tiny, "MutationRecord");
|
||||
const record = try arena.create(MutationRecord);
|
||||
record.* = .{
|
||||
._arena = arena,
|
||||
@@ -245,7 +245,7 @@ pub fn notifyAttributeChange(
|
||||
|
||||
try self._pending_records.append(self._arena, record);
|
||||
|
||||
try page.scheduleMutationDelivery();
|
||||
try frame.scheduleMutationDelivery();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,7 @@ pub fn notifyCharacterDataChange(
|
||||
self: *MutationObserver,
|
||||
target: *Node,
|
||||
old_value: ?String,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
for (self._observing.items) |obs| {
|
||||
if (obs.target != target) {
|
||||
@@ -270,7 +270,7 @@ pub fn notifyCharacterDataChange(
|
||||
continue;
|
||||
}
|
||||
|
||||
const arena = try page.getArena(.tiny, "MutationRecord");
|
||||
const arena = try frame.getArena(.tiny, "MutationRecord");
|
||||
const record = try arena.create(MutationRecord);
|
||||
record.* = .{
|
||||
._arena = arena,
|
||||
@@ -289,7 +289,7 @@ pub fn notifyCharacterDataChange(
|
||||
|
||||
try self._pending_records.append(self._arena, record);
|
||||
|
||||
try page.scheduleMutationDelivery();
|
||||
try frame.scheduleMutationDelivery();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -302,7 +302,7 @@ pub fn notifyChildListChange(
|
||||
removed_nodes: []const *Node,
|
||||
previous_sibling: ?*Node,
|
||||
next_sibling: ?*Node,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
for (self._observing.items) |obs| {
|
||||
if (obs.target != target) {
|
||||
@@ -317,7 +317,7 @@ pub fn notifyChildListChange(
|
||||
continue;
|
||||
}
|
||||
|
||||
const arena = try page.getArena(.tiny, "MutationRecord");
|
||||
const arena = try frame.getArena(.tiny, "MutationRecord");
|
||||
const record = try arena.create(MutationRecord);
|
||||
record.* = .{
|
||||
._arena = arena,
|
||||
@@ -333,26 +333,26 @@ pub fn notifyChildListChange(
|
||||
|
||||
try self._pending_records.append(self._arena, record);
|
||||
|
||||
try page.scheduleMutationDelivery();
|
||||
try frame.scheduleMutationDelivery();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
|
||||
pub fn deliverRecords(self: *MutationObserver, frame: *Frame) !void {
|
||||
if (self._pending_records.items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Take a copy of the records and clear the list before calling callback
|
||||
// This ensures mutations triggered during the callback go into a fresh list
|
||||
const records = try self.takeRecords(page);
|
||||
const records = try self.takeRecords(frame);
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
ls.toLocal(self._callback).tryCall(void, .{ records, self }, &caught) catch |err| {
|
||||
log.err(.page, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
|
||||
log.err(.frame, "MutObserver.deliverRecords", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
const PluginArray = @import("PluginArray.zig");
|
||||
const Permissions = @import("Permissions.zig");
|
||||
@@ -37,8 +37,8 @@ _storage: StorageManager = .{},
|
||||
|
||||
pub const init: Navigator = .{};
|
||||
|
||||
pub fn getUserAgent(_: *const Navigator, page: *Page) []const u8 {
|
||||
return page._session.browser.http_client.getUserAgent();
|
||||
pub fn getUserAgent(_: *const Navigator, frame: *Frame) []const u8 {
|
||||
return frame._session.browser.http_client.getUserAgent();
|
||||
}
|
||||
|
||||
pub fn getLanguages(_: *const Navigator) [2][]const u8 {
|
||||
@@ -72,18 +72,18 @@ pub fn getStorage(self: *Navigator) *StorageManager {
|
||||
return &self._storage;
|
||||
}
|
||||
|
||||
pub fn getBattery(_: *const Navigator, page: *Page) !js.Promise {
|
||||
pub fn getBattery(_: *const Navigator, frame: *Frame) !js.Promise {
|
||||
log.info(.not_implemented, "navigator.getBattery", .{});
|
||||
return page.js.local.?.rejectErrorPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return frame.js.local.?.rejectErrorPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
}
|
||||
|
||||
pub fn registerProtocolHandler(_: *const Navigator, scheme: []const u8, url: [:0]const u8, page: *const Page) !void {
|
||||
pub fn registerProtocolHandler(_: *const Navigator, scheme: []const u8, url: [:0]const u8, frame: *const Frame) !void {
|
||||
try validateProtocolHandlerScheme(scheme);
|
||||
try validateProtocolHandlerURL(url, page);
|
||||
try validateProtocolHandlerURL(url, frame);
|
||||
}
|
||||
pub fn unregisterProtocolHandler(_: *const Navigator, scheme: []const u8, url: [:0]const u8, page: *const Page) !void {
|
||||
pub fn unregisterProtocolHandler(_: *const Navigator, scheme: []const u8, url: [:0]const u8, frame: *const Frame) !void {
|
||||
try validateProtocolHandlerScheme(scheme);
|
||||
try validateProtocolHandlerURL(url, page);
|
||||
try validateProtocolHandlerURL(url, frame);
|
||||
}
|
||||
|
||||
fn validateProtocolHandlerScheme(scheme: []const u8) !void {
|
||||
@@ -136,11 +136,11 @@ fn validateProtocolHandlerScheme(scheme: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn validateProtocolHandlerURL(url: [:0]const u8, page: *const Page) !void {
|
||||
fn validateProtocolHandlerURL(url: [:0]const u8, frame: *const Frame) !void {
|
||||
if (std.mem.indexOf(u8, url, "%s") == null) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
if (page.isSameOrigin(url) == false) {
|
||||
if (frame.isSameOrigin(url) == false) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 URL = @import("../URL.zig");
|
||||
const reflect = @import("../reflect.zig");
|
||||
|
||||
@@ -49,7 +49,7 @@ _parent: ?*Node = null,
|
||||
_children: ?*Children = null,
|
||||
_child_link: LinkedList.Node = .{},
|
||||
|
||||
// Lookup for nodes that have a different owner document than page.document
|
||||
// Lookup for nodes that have a different owner document than frame.document
|
||||
pub const OwnerDocumentLookup = std.AutoHashMapUnmanaged(*Node, *Document);
|
||||
|
||||
pub const Type = union(enum) {
|
||||
@@ -218,15 +218,15 @@ fn validateNodeInsertion(parent: *Node, node: *Node) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
||||
pub fn appendChild(self: *Node, child: *Node, frame: *Frame) !*Node {
|
||||
if (child.is(DocumentFragment)) |_| {
|
||||
try page.appendAllChildren(child, self);
|
||||
try frame.appendAllChildren(child, self);
|
||||
return child;
|
||||
}
|
||||
|
||||
try validateNodeInsertion(self, child);
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
|
||||
// If the child is currently connected, and if its new parent is connected,
|
||||
// then we can remove + add a bit more efficiently (we don't have to fully
|
||||
@@ -234,30 +234,30 @@ pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
||||
const child_connected = child.isConnected();
|
||||
|
||||
// Check if we're adopting the node to a different document
|
||||
const child_owner = child.ownerDocument(page);
|
||||
const parent_owner = self.ownerDocument(page) orelse self.as(Document);
|
||||
const child_owner = child.ownerDocument(frame);
|
||||
const parent_owner = self.ownerDocument(frame) orelse self.as(Document);
|
||||
const adopting_to_new_document = child_owner != null and child_owner.? != parent_owner;
|
||||
|
||||
if (child._parent) |parent| {
|
||||
// we can signal removeNode that the child will remain connected
|
||||
// (when it's appended to self) so that it can be a bit more efficient.
|
||||
page.removeNode(parent, child, .{ .will_be_reconnected = self.isConnected() });
|
||||
frame.removeNode(parent, child, .{ .will_be_reconnected = self.isConnected() });
|
||||
}
|
||||
|
||||
// Adopt the node tree if moving between documents
|
||||
if (adopting_to_new_document) {
|
||||
try page.adoptNodeTree(child, parent_owner);
|
||||
try frame.adoptNodeTree(child, parent_owner);
|
||||
}
|
||||
|
||||
try page.appendNode(self, child, .{
|
||||
try frame.appendNode(self, child, .{
|
||||
.child_already_connected = child_connected,
|
||||
.adopting_to_new_document = adopting_to_new_document,
|
||||
});
|
||||
return child;
|
||||
}
|
||||
|
||||
pub fn childNodes(self: *Node, page: *Page) !*collections.ChildNodes {
|
||||
return collections.ChildNodes.init(self, page);
|
||||
pub fn childNodes(self: *Node, frame: *Frame) !*collections.ChildNodes {
|
||||
return collections.ChildNodes.init(self, frame);
|
||||
}
|
||||
|
||||
pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!void {
|
||||
@@ -300,25 +300,25 @@ pub fn getChildTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFaile
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setTextContent(self: *Node, data: []const u8, page: *Page) !void {
|
||||
pub fn setTextContent(self: *Node, data: []const u8, frame: *Frame) !void {
|
||||
switch (self._type) {
|
||||
.element => |el| {
|
||||
if (data.len == 0) {
|
||||
return el.replaceChildren(&.{}, page);
|
||||
return el.replaceChildren(&.{}, frame);
|
||||
}
|
||||
return el.replaceChildren(&.{.{ .text = data }}, page);
|
||||
return el.replaceChildren(&.{.{ .text = data }}, frame);
|
||||
},
|
||||
// Per spec, setting textContent on CharacterData runs replaceData(0, length, value)
|
||||
.cdata => |c| try c.replaceData(0, c.getLength(), data, page),
|
||||
.cdata => |c| try c.replaceData(0, c.getLength(), data, frame),
|
||||
.document => {},
|
||||
.document_type => {},
|
||||
.document_fragment => |frag| {
|
||||
if (data.len == 0) {
|
||||
return frag.replaceChildren(&.{}, page);
|
||||
return frag.replaceChildren(&.{}, frame);
|
||||
}
|
||||
return frag.replaceChildren(&.{.{ .text = data }}, page);
|
||||
return frag.replaceChildren(&.{.{ .text = data }}, frame);
|
||||
},
|
||||
.attribute => |attr| return attr.setValue(.wrap(data), page),
|
||||
.attribute => |attr| return attr.setValue(.wrap(data), frame),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,30 +354,30 @@ pub fn getNodeType(self: *const Node) u8 {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn lookupNamespaceURI(self: *Node, prefix_arg: ?[]const u8, page: *Page) ?[]const u8 {
|
||||
pub fn lookupNamespaceURI(self: *Node, prefix_arg: ?[]const u8, frame: *Frame) ?[]const u8 {
|
||||
const prefix: ?[]const u8 = if (prefix_arg) |p| (if (p.len == 0) null else p) else null;
|
||||
|
||||
switch (self._type) {
|
||||
.element => |el| return el.lookupNamespaceURIForElement(prefix, page),
|
||||
.element => |el| return el.lookupNamespaceURIForElement(prefix, frame),
|
||||
.document => |doc| {
|
||||
const de = doc.getDocumentElement() orelse return null;
|
||||
return de.lookupNamespaceURIForElement(prefix, page);
|
||||
return de.lookupNamespaceURIForElement(prefix, frame);
|
||||
},
|
||||
.document_type, .document_fragment => return null,
|
||||
.attribute => |attr| {
|
||||
const owner = attr.getOwnerElement() orelse return null;
|
||||
return owner.lookupNamespaceURIForElement(prefix, page);
|
||||
return owner.lookupNamespaceURIForElement(prefix, frame);
|
||||
},
|
||||
.cdata => {
|
||||
const parent = self.parentElement() orelse return null;
|
||||
return parent.lookupNamespaceURIForElement(prefix, page);
|
||||
return parent.lookupNamespaceURIForElement(prefix, frame);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isDefaultNamespace(self: *Node, namespace_arg: ?[]const u8, page: *Page) bool {
|
||||
pub fn isDefaultNamespace(self: *Node, namespace_arg: ?[]const u8, frame: *Frame) bool {
|
||||
const namespace: ?[]const u8 = if (namespace_arg) |ns| (if (ns.len == 0) null else ns) else null;
|
||||
const default_ns = self.lookupNamespaceURI(null, page);
|
||||
const default_ns = self.lookupNamespaceURI(null, frame);
|
||||
if (default_ns == null and namespace == null) return true;
|
||||
if (default_ns != null and namespace != null) return std.mem.eql(u8, default_ns.?, namespace.?);
|
||||
return false;
|
||||
@@ -481,7 +481,7 @@ pub fn contains(self: *const Node, child_: ?*const Node) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn ownerDocument(self: *const Node, page: *const Page) ?*Document {
|
||||
pub fn ownerDocument(self: *const Node, frame: *const Frame) ?*Document {
|
||||
// A document node does not have an owner.
|
||||
if (self._type == .document) {
|
||||
return null;
|
||||
@@ -500,17 +500,17 @@ pub fn ownerDocument(self: *const Node, page: *const Page) ?*Document {
|
||||
|
||||
// Otherwise, this is a detached node. Check if it has a specific owner
|
||||
// document registered (for nodes created via non-main documents).
|
||||
if (page._node_owner_documents.get(@constCast(self))) |owner| {
|
||||
if (frame._node_owner_documents.get(@constCast(self))) |owner| {
|
||||
return owner;
|
||||
}
|
||||
|
||||
// Default to the main document for detached nodes without a specific owner.
|
||||
return page.document;
|
||||
return frame.document;
|
||||
}
|
||||
|
||||
pub fn ownerPage(self: *const Node, default: *Page) *Page {
|
||||
pub fn ownerFrame(self: *const Node, default: *Frame) *Frame {
|
||||
const doc = self.ownerDocument(default) orelse return default;
|
||||
return doc._page orelse default;
|
||||
return doc._frame orelse default;
|
||||
}
|
||||
|
||||
pub const ResolveURLOpts = struct {
|
||||
@@ -519,16 +519,16 @@ pub const ResolveURLOpts = struct {
|
||||
|
||||
// Resolve a URL relative to this node's owning document.
|
||||
// Uses the document's charset for query string encoding (with NCR fallback for unmappable chars).
|
||||
pub fn resolveURL(self: *const Node, url: anytype, page: *Page, opts: ResolveURLOpts) ![:0]const u8 {
|
||||
const owner_page = self.ownerPage(page);
|
||||
const allocator = opts.allocator orelse page.call_arena;
|
||||
return URL.resolve(allocator, owner_page.base(), url, .{ .encoding = owner_page.charset });
|
||||
pub fn resolveURL(self: *const Node, url: anytype, frame: *Frame, opts: ResolveURLOpts) ![:0]const u8 {
|
||||
const owner_frame = self.ownerFrame(frame);
|
||||
const allocator = opts.allocator orelse frame.call_arena;
|
||||
return URL.resolve(allocator, owner_frame.base(), url, .{ .encoding = owner_frame.charset });
|
||||
}
|
||||
|
||||
pub fn isSameDocumentAs(self: *const Node, other: *const Node, page: *const Page) bool {
|
||||
pub fn isSameDocumentAs(self: *const Node, other: *const Node, frame: *const Frame) bool {
|
||||
// Get the root document for each node
|
||||
const self_doc = if (self._type == .document) self._type.document else self.ownerDocument(page);
|
||||
const other_doc = if (other._type == .document) other._type.document else other.ownerDocument(page);
|
||||
const self_doc = if (self._type == .document) self._type.document else self.ownerDocument(frame);
|
||||
const other_doc = if (other._type == .document) other._type.document else other.ownerDocument(frame);
|
||||
return self_doc == other_doc;
|
||||
}
|
||||
|
||||
@@ -540,33 +540,33 @@ pub fn isSameNode(self: *const Node, other: ?*Node) bool {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
pub fn removeChild(self: *Node, child: *Node, page: *Page) !*Node {
|
||||
pub fn removeChild(self: *Node, child: *Node, frame: *Frame) !*Node {
|
||||
var it = self.childrenIterator();
|
||||
while (it.next()) |n| {
|
||||
if (n == child) {
|
||||
page.domChanged();
|
||||
page.removeNode(self, child, .{ .will_be_reconnected = false });
|
||||
frame.domChanged();
|
||||
frame.removeNode(self, child, .{ .will_be_reconnected = false });
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return error.NotFound;
|
||||
}
|
||||
|
||||
pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page) !*Node {
|
||||
pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, frame: *Frame) !*Node {
|
||||
const ref_node = ref_node_ orelse {
|
||||
return self.appendChild(new_node, page);
|
||||
return self.appendChild(new_node, frame);
|
||||
};
|
||||
|
||||
// special case: if nodes are the same, ignore the change.
|
||||
if (new_node == ref_node_) {
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
|
||||
if (page.hasMutationObservers()) {
|
||||
if (frame.hasMutationObservers()) {
|
||||
const parent = new_node._parent.?;
|
||||
const previous_sibling = new_node.previousSibling();
|
||||
const next_sibling = new_node.nextSibling();
|
||||
const replaced = [_]*Node{new_node};
|
||||
page.childListChange(parent, &replaced, &replaced, previous_sibling, next_sibling);
|
||||
frame.childListChange(parent, &replaced, &replaced, previous_sibling, next_sibling);
|
||||
}
|
||||
|
||||
return new_node;
|
||||
@@ -577,7 +577,7 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
||||
}
|
||||
|
||||
if (new_node.is(DocumentFragment)) |_| {
|
||||
try page.insertAllChildrenBefore(new_node, self, ref_node);
|
||||
try frame.insertAllChildrenBefore(new_node, self, ref_node);
|
||||
return new_node;
|
||||
}
|
||||
|
||||
@@ -586,22 +586,22 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
||||
const child_already_connected = new_node.isConnected();
|
||||
|
||||
// Check if we're adopting the node to a different document
|
||||
const child_owner = new_node.ownerDocument(page);
|
||||
const parent_owner = self.ownerDocument(page) orelse self.as(Document);
|
||||
const child_owner = new_node.ownerDocument(frame);
|
||||
const parent_owner = self.ownerDocument(frame) orelse self.as(Document);
|
||||
const adopting_to_new_document = child_owner != null and child_owner.? != parent_owner;
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
const will_be_reconnected = self.isConnected();
|
||||
if (new_node._parent) |parent| {
|
||||
page.removeNode(parent, new_node, .{ .will_be_reconnected = will_be_reconnected });
|
||||
frame.removeNode(parent, new_node, .{ .will_be_reconnected = will_be_reconnected });
|
||||
}
|
||||
|
||||
// Adopt the node tree if moving between documents
|
||||
if (adopting_to_new_document) {
|
||||
try page.adoptNodeTree(new_node, parent_owner);
|
||||
try frame.adoptNodeTree(new_node, parent_owner);
|
||||
}
|
||||
|
||||
try page.insertNodeRelative(
|
||||
try frame.insertNodeRelative(
|
||||
self,
|
||||
new_node,
|
||||
.{ .before = ref_node },
|
||||
@@ -614,21 +614,21 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
||||
return new_node;
|
||||
}
|
||||
|
||||
pub fn replaceChild(self: *Node, new_child: *Node, old_child: *Node, page: *Page) !*Node {
|
||||
pub fn replaceChild(self: *Node, new_child: *Node, old_child: *Node, frame: *Frame) !*Node {
|
||||
if (old_child._parent == null or old_child._parent.? != self) {
|
||||
return error.HierarchyError;
|
||||
}
|
||||
|
||||
try validateNodeInsertion(self, new_child);
|
||||
|
||||
_ = try self.insertBefore(new_child, old_child, page);
|
||||
_ = try self.insertBefore(new_child, old_child, frame);
|
||||
|
||||
// Special case: if we replace a node by itself, we don't remove it.
|
||||
// insertBefore is an noop in this case.
|
||||
// Re-check parent after insertBefore since callbacks (e.g. connectedCallback)
|
||||
// could have already removed old_child from self.
|
||||
if (new_child != old_child and old_child._parent == self) {
|
||||
page.removeNode(self, old_child, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(self, old_child, .{ .will_be_reconnected = false });
|
||||
}
|
||||
|
||||
return old_child;
|
||||
@@ -645,14 +645,14 @@ pub fn getNodeValue(self: *const Node) ?String {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setNodeValue(self: *const Node, value: ?String, page: *Page) !void {
|
||||
pub fn setNodeValue(self: *const Node, value: ?String, frame: *Frame) !void {
|
||||
switch (self._type) {
|
||||
// Per spec, setting nodeValue on CharacterData runs replaceData(0, length, value)
|
||||
.cdata => |c| {
|
||||
const new_value: []const u8 = if (value) |v| v.str() else "";
|
||||
try c.replaceData(0, c.getLength(), new_value, page);
|
||||
try c.replaceData(0, c.getLength(), new_value, frame);
|
||||
},
|
||||
.attribute => |attr| try attr.setValue(value, page),
|
||||
.attribute => |attr| try attr.setValue(value, frame),
|
||||
.element => {},
|
||||
.document => {},
|
||||
.document_type => {},
|
||||
@@ -740,16 +740,16 @@ pub fn getData(self: *const Node) String {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setData(self: *Node, data: []const u8, page: *Page) !void {
|
||||
pub fn setData(self: *Node, data: []const u8, frame: *Frame) !void {
|
||||
switch (self._type) {
|
||||
.cdata => |c| try c.setData(data, page),
|
||||
.cdata => |c| try c.setData(data, frame),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize(self: *Node, page: *Page) !void {
|
||||
pub fn normalize(self: *Node, frame: *Frame) !void {
|
||||
var buffer: std.ArrayList(u8) = .empty;
|
||||
return self._normalize(page.call_arena, &buffer, page);
|
||||
return self._normalize(frame.call_arena, &buffer, frame);
|
||||
}
|
||||
|
||||
const CloneError = error{
|
||||
@@ -767,27 +767,27 @@ const CloneError = error{
|
||||
CompilationError,
|
||||
JsException,
|
||||
};
|
||||
pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) CloneError!*Node {
|
||||
pub fn cloneNode(self: *Node, deep_: ?bool, frame: *Frame) CloneError!*Node {
|
||||
const deep = deep_ orelse false;
|
||||
switch (self._type) {
|
||||
.cdata => |cd| {
|
||||
const data = cd.getData().str();
|
||||
return switch (cd._type) {
|
||||
.text => page.createTextNode(data),
|
||||
.cdata_section => page.createCDATASection(data),
|
||||
.comment => page.createComment(data),
|
||||
.processing_instruction => |pi| page.createProcessingInstruction(pi._target, data),
|
||||
.text => frame.createTextNode(data),
|
||||
.cdata_section => frame.createCDATASection(data),
|
||||
.comment => frame.createComment(data),
|
||||
.processing_instruction => |pi| frame.createProcessingInstruction(pi._target, data),
|
||||
};
|
||||
},
|
||||
.element => |el| return el.clone(deep, page),
|
||||
.element => |el| return el.clone(deep, frame),
|
||||
.document => return error.NotSupported,
|
||||
.document_type => |dt| {
|
||||
const cloned = dt.clone(page) catch return error.CloneError;
|
||||
const cloned = dt.clone(frame) catch return error.CloneError;
|
||||
return cloned.asNode();
|
||||
},
|
||||
.document_fragment => |frag| return frag.cloneFragment(deep, page),
|
||||
.document_fragment => |frag| return frag.cloneFragment(deep, frame),
|
||||
.attribute => |attr| {
|
||||
const cloned = attr.clone(page) catch return error.CloneError;
|
||||
const cloned = attr.clone(frame) catch return error.CloneError;
|
||||
return cloned._proto;
|
||||
},
|
||||
}
|
||||
@@ -799,8 +799,8 @@ pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) CloneError!*Node {
|
||||
///
|
||||
/// This helper is used when iterating over children to clone them. The typical pattern is:
|
||||
/// while (child_it.next()) |child| {
|
||||
/// if (try child.cloneNodeForAppending(true, page)) |cloned| {
|
||||
/// try page.appendNode(parent, cloned, opts);
|
||||
/// if (try child.cloneNodeForAppending(true, frame)) |cloned| {
|
||||
/// try frame.appendNode(parent, cloned, opts);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@@ -808,8 +808,8 @@ pub fn cloneNode(self: *Node, deep_: ?bool, page: *Page) CloneError!*Node {
|
||||
/// constructor (which runs during cloning per the HTML spec) explicitly attaches the element
|
||||
/// somewhere. In that case, we respect the constructor's decision and return null to signal
|
||||
/// that the cloned node should not be appended to our intended parent.
|
||||
pub fn cloneNodeForAppending(self: *Node, deep: bool, page: *Page) CloneError!?*Node {
|
||||
const cloned = try self.cloneNode(deep, page);
|
||||
pub fn cloneNodeForAppending(self: *Node, deep: bool, frame: *Frame) CloneError!?*Node {
|
||||
const cloned = try self.cloneNode(deep, frame);
|
||||
if (cloned._parent != null) {
|
||||
return null;
|
||||
}
|
||||
@@ -916,10 +916,10 @@ fn isNodeBefore(node1: *const Node, node2: *const Node) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn _normalize(self: *Node, allocator: Allocator, buffer: *std.ArrayList(u8), page: *Page) !void {
|
||||
fn _normalize(self: *Node, allocator: Allocator, buffer: *std.ArrayList(u8), frame: *Frame) !void {
|
||||
var it = self.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
try child._normalize(allocator, buffer, page);
|
||||
try child._normalize(allocator, buffer, frame);
|
||||
}
|
||||
|
||||
var child = self.firstChild();
|
||||
@@ -932,7 +932,7 @@ fn _normalize(self: *Node, allocator: Allocator, buffer: *std.ArrayList(u8), pag
|
||||
};
|
||||
|
||||
if (text_node._proto.getData().len == 0) {
|
||||
page.removeNode(self, current_node, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(self, current_node, .{ .will_be_reconnected = false });
|
||||
child = next_node;
|
||||
continue;
|
||||
}
|
||||
@@ -947,9 +947,9 @@ fn _normalize(self: *Node, allocator: Allocator, buffer: *std.ArrayList(u8), pag
|
||||
|
||||
const to_remove = node_to_merge;
|
||||
next_node = node_to_merge.nextSibling();
|
||||
page.removeNode(self, to_remove, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(self, to_remove, .{ .will_be_reconnected = false });
|
||||
}
|
||||
text_node._proto._data = try page.dupeSSO(buffer.items);
|
||||
text_node._proto._data = try frame.dupeSSO(buffer.items);
|
||||
buffer.clearRetainingCapacity();
|
||||
}
|
||||
}
|
||||
@@ -964,7 +964,7 @@ pub const GetElementsByTagNameResult = union(enum) {
|
||||
all_elements: collections.NodeLive(.all_elements),
|
||||
};
|
||||
// Not exposed in the WebAPI, but used by both Element and Document
|
||||
pub fn getElementsByTagName(self: *Node, tag_name: []const u8, page: *Page) !GetElementsByTagNameResult {
|
||||
pub fn getElementsByTagName(self: *Node, tag_name: []const u8, frame: *Frame) !GetElementsByTagNameResult {
|
||||
if (tag_name.len > 256) {
|
||||
// 256 seems generous.
|
||||
return error.InvalidTagName;
|
||||
@@ -972,25 +972,25 @@ pub fn getElementsByTagName(self: *Node, tag_name: []const u8, page: *Page) !Get
|
||||
|
||||
if (std.mem.eql(u8, tag_name, "*")) {
|
||||
return .{
|
||||
.all_elements = collections.NodeLive(.all_elements).init(self, {}, page),
|
||||
.all_elements = collections.NodeLive(.all_elements).init(self, {}, frame),
|
||||
};
|
||||
}
|
||||
|
||||
const lower = std.ascii.lowerString(&page.buf, tag_name);
|
||||
const lower = std.ascii.lowerString(&frame.buf, tag_name);
|
||||
if (Node.Element.Tag.parseForMatch(lower)) |known| {
|
||||
// optimized for known tag names, comparis
|
||||
return .{
|
||||
.tag = collections.NodeLive(.tag).init(self, known, page),
|
||||
.tag = collections.NodeLive(.tag).init(self, known, frame),
|
||||
};
|
||||
}
|
||||
|
||||
const arena = page.arena;
|
||||
const arena = frame.arena;
|
||||
const filter = try String.init(arena, lower, .{});
|
||||
return .{ .tag_name = collections.NodeLive(.tag_name).init(self, filter, page) };
|
||||
return .{ .tag_name = collections.NodeLive(.tag_name).init(self, filter, frame) };
|
||||
}
|
||||
|
||||
// Not exposed in the WebAPI, but used by both Element and Document
|
||||
pub fn getElementsByTagNameNS(self: *Node, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
|
||||
pub fn getElementsByTagNameNS(self: *Node, namespace: ?[]const u8, local_name: []const u8, frame: *Frame) !collections.NodeLive(.tag_name_ns) {
|
||||
if (local_name.len > 256) {
|
||||
return error.InvalidTagName;
|
||||
}
|
||||
@@ -1003,53 +1003,53 @@ pub fn getElementsByTagNameNS(self: *Node, namespace: ?[]const u8, local_name: [
|
||||
|
||||
return collections.NodeLive(.tag_name_ns).init(self, .{
|
||||
.namespace = ns,
|
||||
.local_name = try String.init(page.arena, local_name, .{}),
|
||||
}, page);
|
||||
.local_name = try String.init(frame.arena, local_name, .{}),
|
||||
}, frame);
|
||||
}
|
||||
|
||||
// Not exposed in the WebAPI, but used by both Element and Document
|
||||
pub fn getElementsByClassName(self: *Node, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
|
||||
const arena = page.arena;
|
||||
pub fn getElementsByClassName(self: *Node, class_name: []const u8, frame: *Frame) !collections.NodeLive(.class_name) {
|
||||
const arena = frame.arena;
|
||||
|
||||
// Parse space-separated class names
|
||||
var class_names: std.ArrayList([]const u8) = .empty;
|
||||
var it = std.mem.tokenizeAny(u8, class_name, "\t\n\x0C\r ");
|
||||
while (it.next()) |name| {
|
||||
try class_names.append(arena, try page.dupeString(name));
|
||||
try class_names.append(arena, try frame.dupeString(name));
|
||||
}
|
||||
|
||||
return collections.NodeLive(.class_name).init(self, class_names.items, page);
|
||||
return collections.NodeLive(.class_name).init(self, class_names.items, frame);
|
||||
}
|
||||
|
||||
/// Shared implementation of replaceChildren for Element, Document, and DocumentFragment.
|
||||
/// Validates all nodes, removes existing children, then appends new children.
|
||||
pub fn replaceChildren(self: *Node, nodes: []const NodeOrText, page: *Page) !void {
|
||||
pub fn replaceChildren(self: *Node, nodes: []const NodeOrText, frame: *Frame) !void {
|
||||
// First pass: validate all nodes and collect them
|
||||
// We need to collect because DocumentFragments contribute their children, not themselves
|
||||
var children_to_add: std.ArrayList(*Node) = .empty;
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(page);
|
||||
const child = try node_or_text.toNode(frame);
|
||||
|
||||
// DocumentFragments contribute their children, not themselves
|
||||
if (child.is(DocumentFragment)) |frag| {
|
||||
var frag_it = frag.asNode().childrenIterator();
|
||||
while (frag_it.next()) |frag_child| {
|
||||
try validateNodeInsertion(self, frag_child);
|
||||
try children_to_add.append(page.call_arena, frag_child);
|
||||
try children_to_add.append(frame.call_arena, frag_child);
|
||||
}
|
||||
} else {
|
||||
try validateNodeInsertion(self, child);
|
||||
try children_to_add.append(page.call_arena, child);
|
||||
try children_to_add.append(frame.call_arena, child);
|
||||
}
|
||||
}
|
||||
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
|
||||
// Remove all existing children
|
||||
var it = self.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
page.removeNode(self, child, .{ .will_be_reconnected = false });
|
||||
frame.removeNode(self, child, .{ .will_be_reconnected = false });
|
||||
}
|
||||
|
||||
// Append new children
|
||||
@@ -1058,9 +1058,9 @@ pub fn replaceChildren(self: *Node, nodes: []const NodeOrText, page: *Page) !voi
|
||||
var child_connected = false;
|
||||
if (child._parent) |previous_parent| {
|
||||
child_connected = child.isConnected();
|
||||
page.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||
frame.removeNode(previous_parent, child, .{ .will_be_reconnected = parent_is_connected });
|
||||
}
|
||||
try page.appendNode(self, child, .{ .child_already_connected = child_connected });
|
||||
try frame.appendNode(self, child, .{ .child_already_connected = child_connected });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1121,18 +1121,18 @@ pub const JsApi = struct {
|
||||
pub const DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = bridge.property(0x20, .{ .template = true });
|
||||
|
||||
pub const nodeName = bridge.accessor(struct {
|
||||
fn wrap(self: *const Node, page: *Page) []const u8 {
|
||||
return self.getNodeName(&page.buf);
|
||||
fn wrap(self: *const Node, frame: *Frame) []const u8 {
|
||||
return self.getNodeName(&frame.buf);
|
||||
}
|
||||
}.wrap, null, .{});
|
||||
pub const nodeType = bridge.accessor(Node.getNodeType, null, .{});
|
||||
|
||||
pub const textContent = bridge.accessor(_textContext, Node.setTextContent, .{});
|
||||
fn _textContext(self: *Node, page: *const Page) !?[]const u8 {
|
||||
fn _textContext(self: *Node, frame: *const Frame) !?[]const u8 {
|
||||
// cdata and attributes can return value directly, avoiding the copy
|
||||
switch (self._type) {
|
||||
.element, .document_fragment => {
|
||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
var buf = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
try self.getTextContent(&buf.writer);
|
||||
return buf.written();
|
||||
},
|
||||
@@ -1168,8 +1168,8 @@ pub const JsApi = struct {
|
||||
pub const lookupNamespaceURI = bridge.function(Node.lookupNamespaceURI, .{});
|
||||
pub const isDefaultNamespace = bridge.function(Node.isDefaultNamespace, .{});
|
||||
|
||||
fn _baseURI(_: *Node, page: *const Page) []const u8 {
|
||||
return page.base();
|
||||
fn _baseURI(_: *Node, frame: *const Frame) []const u8 {
|
||||
return frame.base();
|
||||
}
|
||||
pub const baseURI = bridge.accessor(_baseURI, null, .{});
|
||||
};
|
||||
@@ -1219,10 +1219,10 @@ pub const NodeOrText = union(enum) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toNode(self: *const NodeOrText, page: *Page) !*Node {
|
||||
pub fn toNode(self: *const NodeOrText, frame: *Frame) !*Node {
|
||||
return switch (self.*) {
|
||||
.node => |n| n,
|
||||
.text => |txt| page.createTextNode(txt),
|
||||
.text => |txt| frame.createTextNode(txt),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const datetime = @import("../../datetime.zig");
|
||||
|
||||
const EventCounts = @import("EventCounts.zig");
|
||||
@@ -65,12 +65,12 @@ pub fn mark(
|
||||
self: *Performance,
|
||||
name: []const u8,
|
||||
_options: ?Mark.Options,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*Mark {
|
||||
const m = try Mark.init(name, _options, page);
|
||||
try self._entries.append(page.arena, m._proto);
|
||||
const m = try Mark.init(name, _options, frame);
|
||||
try self._entries.append(frame.arena, m._proto);
|
||||
// Notify about the change.
|
||||
try page.notifyPerformanceObservers(m._proto);
|
||||
try frame.notifyPerformanceObservers(m._proto);
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ pub fn measure(
|
||||
name: []const u8,
|
||||
maybe_options_or_start: ?MeasureOptionsOrStartMark,
|
||||
maybe_end_mark: ?[]const u8,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*Measure {
|
||||
if (maybe_options_or_start) |options_or_start| switch (options_or_start) {
|
||||
.measure_options => |options| {
|
||||
@@ -118,11 +118,11 @@ pub fn measure(
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
options.duration,
|
||||
page,
|
||||
frame,
|
||||
);
|
||||
try self._entries.append(page.arena, m._proto);
|
||||
try self._entries.append(frame.arena, m._proto);
|
||||
// Notify about the change.
|
||||
try page.notifyPerformanceObservers(m._proto);
|
||||
try frame.notifyPerformanceObservers(m._proto);
|
||||
return m;
|
||||
},
|
||||
.start_mark => |start_mark| {
|
||||
@@ -143,19 +143,19 @@ pub fn measure(
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
null,
|
||||
page,
|
||||
frame,
|
||||
);
|
||||
try self._entries.append(page.arena, m._proto);
|
||||
try self._entries.append(frame.arena, m._proto);
|
||||
// Notify about the change.
|
||||
try page.notifyPerformanceObservers(m._proto);
|
||||
try frame.notifyPerformanceObservers(m._proto);
|
||||
return m;
|
||||
},
|
||||
};
|
||||
|
||||
const m = try Measure.init(name, null, 0.0, self.now(), null, page);
|
||||
try self._entries.append(page.arena, m._proto);
|
||||
const m = try Measure.init(name, null, 0.0, self.now(), null, frame);
|
||||
try self._entries.append(frame.arena, m._proto);
|
||||
// Notify about the change.
|
||||
try page.notifyPerformanceObservers(m._proto);
|
||||
try frame.notifyPerformanceObservers(m._proto);
|
||||
return m;
|
||||
}
|
||||
|
||||
@@ -187,19 +187,19 @@ pub fn getEntries(self: *const Performance) []*Entry {
|
||||
return self._entries.items;
|
||||
}
|
||||
|
||||
pub fn getEntriesByType(self: *const Performance, entry_type: []const u8, page: *Page) ![]const *Entry {
|
||||
pub fn getEntriesByType(self: *const Performance, entry_type: []const u8, frame: *Frame) ![]const *Entry {
|
||||
var result: std.ArrayList(*Entry) = .empty;
|
||||
|
||||
for (self._entries.items) |entry| {
|
||||
if (std.mem.eql(u8, entry.getEntryType(), entry_type)) {
|
||||
try result.append(page.call_arena, entry);
|
||||
try result.append(frame.call_arena, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result.items;
|
||||
}
|
||||
|
||||
pub fn getEntriesByName(self: *const Performance, name: []const u8, entry_type: ?[]const u8, page: *Page) ![]const *Entry {
|
||||
pub fn getEntriesByName(self: *const Performance, name: []const u8, entry_type: ?[]const u8, frame: *Frame) ![]const *Entry {
|
||||
var result: std.ArrayList(*Entry) = .empty;
|
||||
|
||||
for (self._entries.items) |entry| {
|
||||
@@ -208,12 +208,12 @@ pub fn getEntriesByName(self: *const Performance, name: []const u8, entry_type:
|
||||
}
|
||||
|
||||
const et = entry_type orelse {
|
||||
try result.append(page.call_arena, entry);
|
||||
try result.append(frame.call_arena, entry);
|
||||
continue;
|
||||
};
|
||||
|
||||
if (std.mem.eql(u8, entry.getEntryType(), et)) {
|
||||
try result.append(page.call_arena, entry);
|
||||
try result.append(frame.call_arena, entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,23 +371,23 @@ pub const Mark = struct {
|
||||
startTime: ?f64 = null,
|
||||
};
|
||||
|
||||
pub fn init(name: []const u8, _opts: ?Options, page: *Page) !*Mark {
|
||||
pub fn init(name: []const u8, _opts: ?Options, frame: *Frame) !*Mark {
|
||||
const opts = _opts orelse Options{};
|
||||
const start_time = opts.startTime orelse page.window._performance.now();
|
||||
const start_time = opts.startTime orelse frame.window._performance.now();
|
||||
|
||||
if (start_time < 0.0) {
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
const detail = if (opts.detail) |d| try d.persist() else null;
|
||||
const m = try page._factory.create(Mark{
|
||||
const m = try frame._factory.create(Mark{
|
||||
._proto = undefined,
|
||||
._detail = detail,
|
||||
});
|
||||
|
||||
const entry = try page._factory.create(Entry{
|
||||
const entry = try frame._factory.create(Entry{
|
||||
._start_time = start_time,
|
||||
._name = try page.dupeString(name),
|
||||
._name = try frame.dupeString(name),
|
||||
._type = .{ .mark = m },
|
||||
});
|
||||
m._proto = entry;
|
||||
@@ -432,7 +432,7 @@ pub const Measure = struct {
|
||||
start_timestamp: f64,
|
||||
end_timestamp: f64,
|
||||
maybe_duration: ?f64,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*Measure {
|
||||
const duration = maybe_duration orelse (end_timestamp - start_timestamp);
|
||||
if (duration < 0.0) {
|
||||
@@ -440,15 +440,15 @@ pub const Measure = struct {
|
||||
}
|
||||
|
||||
const detail = if (maybe_detail) |d| try d.persist() else null;
|
||||
const m = try page._factory.create(Measure{
|
||||
const m = try frame._factory.create(Measure{
|
||||
._proto = undefined,
|
||||
._detail = detail,
|
||||
});
|
||||
|
||||
const entry = try page._factory.create(Entry{
|
||||
const entry = try frame._factory.create(Entry{
|
||||
._start_time = start_timestamp,
|
||||
._duration = duration,
|
||||
._name = try page.dupeString(name),
|
||||
._name = try frame.dupeString(name),
|
||||
._type = .{ .measure = m },
|
||||
});
|
||||
m._proto = entry;
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 Performance = @import("Performance.zig");
|
||||
|
||||
const log = lp.log;
|
||||
@@ -45,8 +45,8 @@ _entries: std.ArrayList(*Performance.Entry),
|
||||
const DefaultDurationThreshold: f64 = 104;
|
||||
|
||||
/// Creates a new PerformanceObserver object with the given observer callback.
|
||||
pub fn init(callback: js.Function.Global, page: *Page) !*PerformanceObserver {
|
||||
return page._factory.create(PerformanceObserver{
|
||||
pub fn init(callback: js.Function.Global, frame: *Frame) !*PerformanceObserver {
|
||||
return frame._factory.create(PerformanceObserver{
|
||||
._callback = callback,
|
||||
._duration_threshold = DefaultDurationThreshold,
|
||||
._interests = 0,
|
||||
@@ -65,7 +65,7 @@ const ObserveOptions = struct {
|
||||
pub fn observe(
|
||||
self: *PerformanceObserver,
|
||||
maybe_options: ?ObserveOptions,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
const options: ObserveOptions = maybe_options orelse .{};
|
||||
// Update threshold.
|
||||
@@ -109,7 +109,7 @@ pub fn observe(
|
||||
// If we had no interests before, it means Page is not aware of
|
||||
// this observer.
|
||||
if (self._interests == 0) {
|
||||
try page.registerPerformanceObserver(self);
|
||||
try frame.registerPerformanceObserver(self);
|
||||
}
|
||||
|
||||
// Update interests.
|
||||
@@ -119,19 +119,19 @@ pub fn observe(
|
||||
// Per spec, buffered is only valid with the type option, not entryTypes.
|
||||
// Delivery is async via a queued task, not synchronous.
|
||||
if (options.buffered and options.type != null and !self.hasRecords()) {
|
||||
for (page.window._performance._entries.items) |entry| {
|
||||
for (frame.window._performance._entries.items) |entry| {
|
||||
if (self.interested(entry)) {
|
||||
try self._entries.append(page.arena, entry);
|
||||
try self._entries.append(frame.arena, entry);
|
||||
}
|
||||
}
|
||||
if (self.hasRecords()) {
|
||||
try page.schedulePerformanceObserverDelivery();
|
||||
try frame.schedulePerformanceObserverDelivery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect(self: *PerformanceObserver, page: *Page) void {
|
||||
page.unregisterPerformanceObserver(self);
|
||||
pub fn disconnect(self: *PerformanceObserver, frame: *Frame) void {
|
||||
frame.unregisterPerformanceObserver(self);
|
||||
// Reset observer.
|
||||
self._duration_threshold = DefaultDurationThreshold;
|
||||
self._interests = 0;
|
||||
@@ -140,10 +140,10 @@ pub fn disconnect(self: *PerformanceObserver, page: *Page) void {
|
||||
|
||||
/// Returns the current list of PerformanceEntry objects
|
||||
/// stored in the performance observer, emptying it out.
|
||||
pub fn takeRecords(self: *PerformanceObserver, page: *Page) ![]*Performance.Entry {
|
||||
// Use page.arena instead of call_arena because this slice is wrapped in EntryList
|
||||
pub fn takeRecords(self: *PerformanceObserver, frame: *Frame) ![]*Performance.Entry {
|
||||
// Use frame.arena instead of call_arena because this slice is wrapped in EntryList
|
||||
// and may be accessed later.
|
||||
const records = try page.arena.dupe(*Performance.Entry, self._entries.items);
|
||||
const records = try frame.arena.dupe(*Performance.Entry, self._entries.items);
|
||||
self._entries.clearRetainingCapacity();
|
||||
return records;
|
||||
}
|
||||
@@ -166,16 +166,16 @@ pub inline fn hasRecords(self: *const PerformanceObserver) bool {
|
||||
}
|
||||
|
||||
/// Runs the PerformanceObserver's callback with records; emptying it out.
|
||||
pub fn dispatch(self: *PerformanceObserver, page: *Page) !void {
|
||||
const records = try self.takeRecords(page);
|
||||
pub fn dispatch(self: *PerformanceObserver, frame: *Frame) !void {
|
||||
const records = try self.takeRecords(frame);
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
ls.toLocal(self._callback).tryCall(void, .{ EntryList{ ._entries = records }, self }, &caught) catch |err| {
|
||||
log.err(.page, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
|
||||
log.err(.frame, "PerfObserver.dispatch", .{ .err = err, .caught = caught });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
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;
|
||||
@@ -37,9 +37,9 @@ const QueryDescriptor = struct {
|
||||
name: []const u8,
|
||||
};
|
||||
// We always report 'prompt' (the default safe value — neither granted nor denied).
|
||||
pub fn query(_: *const Permissions, qd: QueryDescriptor, page: *Page) !js.Promise {
|
||||
const arena = try page.getArena(.tiny, "PermissionStatus");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn query(_: *const Permissions, qd: QueryDescriptor, frame: *Frame) !js.Promise {
|
||||
const arena = try frame.getArena(.tiny, "PermissionStatus");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const status = try arena.create(PermissionStatus);
|
||||
status.* = .{
|
||||
@@ -47,7 +47,7 @@ pub fn query(_: *const Permissions, qd: QueryDescriptor, page: *Page) !js.Promis
|
||||
._state = "prompt",
|
||||
._name = try arena.dupe(u8, qd.name),
|
||||
};
|
||||
return page.js.local.?.resolvePromise(status);
|
||||
return frame.js.local.?.resolvePromise(status);
|
||||
}
|
||||
|
||||
const PermissionStatus = struct {
|
||||
|
||||
@@ -20,7 +20,7 @@ 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");
|
||||
@@ -34,10 +34,10 @@ const Range = @This();
|
||||
|
||||
_proto: *AbstractRange,
|
||||
|
||||
pub fn init(page: *Page) !*Range {
|
||||
const arena = try page.getArena(.medium, "Range");
|
||||
errdefer page.releaseArena(arena);
|
||||
return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
|
||||
pub fn init(frame: *Frame) !*Range {
|
||||
const arena = try frame.getArena(.medium, "Range");
|
||||
errdefer frame.releaseArena(arena);
|
||||
return frame._factory.abstractRange(arena, Range{ ._proto = undefined }, frame);
|
||||
}
|
||||
|
||||
pub fn asAbstractRange(self: *Range) *AbstractRange {
|
||||
@@ -313,11 +313,11 @@ pub fn intersectsNode(self: *const Range, node: *Node) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
||||
const arena = try page.getArena(.medium, "Range.clone");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn cloneRange(self: *const Range, frame: *Frame) !*Range {
|
||||
const arena = try frame.getArena(.medium, "Range.clone");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const clone = try page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
|
||||
const clone = try frame._factory.abstractRange(arena, Range{ ._proto = undefined }, frame);
|
||||
clone._proto._end_offset = self._proto._end_offset;
|
||||
clone._proto._start_offset = self._proto._start_offset;
|
||||
clone._proto._end_container = self._proto._end_container;
|
||||
@@ -325,7 +325,7 @@ pub fn cloneRange(self: *const Range, page: *Page) !*Range {
|
||||
return clone;
|
||||
}
|
||||
|
||||
pub fn insertNode(self: *Range, node: *Node, page: *Page) !void {
|
||||
pub fn insertNode(self: *Range, node: *Node, frame: *Frame) !void {
|
||||
// Insert node at the start of the range
|
||||
const container = self._proto._start_container;
|
||||
const offset = self._proto._start_offset;
|
||||
@@ -340,28 +340,28 @@ pub fn insertNode(self: *Range, node: *Node, page: *Page) !void {
|
||||
const parent = container.parentNode() orelse return error.InvalidNodeType;
|
||||
|
||||
if (offset == 0) {
|
||||
_ = try parent.insertBefore(node, container, page);
|
||||
_ = try parent.insertBefore(node, container, frame);
|
||||
} else {
|
||||
const text_data = container.getData().str();
|
||||
if (offset >= text_data.len) {
|
||||
_ = try parent.insertBefore(node, container.nextSibling(), page);
|
||||
_ = try parent.insertBefore(node, container.nextSibling(), frame);
|
||||
} else {
|
||||
// Split the text node into before and after parts
|
||||
const before_text = text_data[0..offset];
|
||||
const after_text = text_data[offset..];
|
||||
|
||||
const before = try page.createTextNode(before_text);
|
||||
const after = try page.createTextNode(after_text);
|
||||
const before = try frame.createTextNode(before_text);
|
||||
const after = try frame.createTextNode(after_text);
|
||||
|
||||
_ = try parent.replaceChild(before, container, page);
|
||||
_ = try parent.insertBefore(node, before.nextSibling(), page);
|
||||
_ = try parent.insertBefore(after, node.nextSibling(), page);
|
||||
_ = try parent.replaceChild(before, container, frame);
|
||||
_ = try parent.insertBefore(node, before.nextSibling(), frame);
|
||||
_ = try parent.insertBefore(after, node.nextSibling(), frame);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Container is an element, insert at offset
|
||||
const ref_child = container.getChildAt(offset);
|
||||
_ = try container.insertBefore(node, ref_child, page);
|
||||
_ = try container.insertBefore(node, ref_child, frame);
|
||||
}
|
||||
|
||||
// Per spec step 11: if range was collapsed, extend end to include inserted node.
|
||||
@@ -371,11 +371,11 @@ pub fn insertNode(self: *Range, node: *Node, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
pub fn deleteContents(self: *Range, frame: *Frame) !void {
|
||||
if (self._proto.getCollapsed()) {
|
||||
return;
|
||||
}
|
||||
page.domChanged();
|
||||
frame.domChanged();
|
||||
|
||||
// Simple case: same container
|
||||
if (self._proto._start_container == self._proto._end_container) {
|
||||
@@ -384,10 +384,10 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
const old_value = cdata.getData();
|
||||
const text_data = old_value.str();
|
||||
cdata._data = try String.concat(
|
||||
page.arena,
|
||||
frame.arena,
|
||||
&.{ text_data[0..self._proto._start_offset], text_data[self._proto._end_offset..] },
|
||||
);
|
||||
page.characterDataChange(self._proto._start_container, old_value);
|
||||
frame.characterDataChange(self._proto._start_container, old_value);
|
||||
} else {
|
||||
// Delete child nodes in range.
|
||||
// Capture count before the loop: removeChild triggers live range
|
||||
@@ -396,7 +396,7 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
var i: u32 = 0;
|
||||
while (i < count) : (i += 1) {
|
||||
if (self._proto._start_container.getChildAt(self._proto._start_offset)) |child| {
|
||||
_ = try self._proto._start_container.removeChild(child, page);
|
||||
_ = try self._proto._start_container.removeChild(child, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,7 +411,7 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
if (self._proto._start_offset < text_data.len) {
|
||||
// Keep only the part before start_offset
|
||||
const new_text = text_data[0..self._proto._start_offset];
|
||||
try self._proto._start_container.setData(new_text, page);
|
||||
try self._proto._start_container.setData(new_text, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,10 +421,10 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
if (self._proto._end_offset < text_data.len) {
|
||||
// Keep only the part from end_offset onwards
|
||||
const new_text = text_data[self._proto._end_offset..];
|
||||
try self._proto._end_container.setData(new_text, page);
|
||||
try self._proto._end_container.setData(new_text, frame);
|
||||
} else if (self._proto._end_offset == text_data.len) {
|
||||
// If we're at the end, set to empty (will be removed if needed)
|
||||
try self._proto._end_container.setData("", page);
|
||||
try self._proto._end_container.setData("", frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,7 +435,7 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
while (current != null and current != self._proto._end_container) {
|
||||
const next = current.?.nextSibling();
|
||||
if (current.?.parentNode()) |parent| {
|
||||
_ = try parent.removeChild(current.?, page);
|
||||
_ = try parent.removeChild(current.?, frame);
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
@@ -444,8 +444,8 @@ pub fn deleteContents(self: *Range, page: *Page) !void {
|
||||
self.collapse(true);
|
||||
}
|
||||
|
||||
pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||
const fragment = try DocumentFragment.init(page);
|
||||
pub fn cloneContents(self: *const Range, frame: *Frame) !*DocumentFragment {
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
|
||||
if (self._proto.getCollapsed()) return fragment;
|
||||
|
||||
@@ -456,16 +456,16 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||
const text_data = self._proto._start_container.getData().str();
|
||||
if (self._proto._start_offset < text_data.len and self._proto._end_offset <= text_data.len) {
|
||||
const cloned_text = text_data[self._proto._start_offset..self._proto._end_offset];
|
||||
const text_node = try page.createTextNode(cloned_text);
|
||||
_ = try fragment.asNode().appendChild(text_node, page);
|
||||
const text_node = try frame.createTextNode(cloned_text);
|
||||
_ = try fragment.asNode().appendChild(text_node, frame);
|
||||
}
|
||||
} else {
|
||||
// Clone child nodes in range
|
||||
var offset = self._proto._start_offset;
|
||||
while (offset < self._proto._end_offset) : (offset += 1) {
|
||||
if (self._proto._start_container.getChildAt(offset)) |child| {
|
||||
if (try child.cloneNodeForAppending(true, page)) |cloned| {
|
||||
_ = try fragment.asNode().appendChild(cloned, page);
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned| {
|
||||
_ = try fragment.asNode().appendChild(cloned, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,8 +478,8 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||
if (self._proto._start_offset < text_data.len) {
|
||||
// Clone from start_offset to end of text
|
||||
const cloned_text = text_data[self._proto._start_offset..];
|
||||
const text_node = try page.createTextNode(cloned_text);
|
||||
_ = try fragment.asNode().appendChild(text_node, page);
|
||||
const text_node = try frame.createTextNode(cloned_text);
|
||||
_ = try fragment.asNode().appendChild(text_node, frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,8 +488,8 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||
var current = self._proto._start_container.nextSibling();
|
||||
while (current != null and current != self._proto._end_container) {
|
||||
const next = current.?.nextSibling();
|
||||
if (try current.?.cloneNodeForAppending(true, page)) |cloned| {
|
||||
_ = try fragment.asNode().appendChild(cloned, page);
|
||||
if (try current.?.cloneNodeForAppending(true, frame)) |cloned| {
|
||||
_ = try fragment.asNode().appendChild(cloned, frame);
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
@@ -501,8 +501,8 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||
if (self._proto._end_offset > 0 and self._proto._end_offset <= text_data.len) {
|
||||
// Clone from start to end_offset
|
||||
const cloned_text = text_data[0..self._proto._end_offset];
|
||||
const text_node = try page.createTextNode(cloned_text);
|
||||
_ = try fragment.asNode().appendChild(text_node, page);
|
||||
const text_node = try frame.createTextNode(cloned_text);
|
||||
_ = try fragment.asNode().appendChild(text_node, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,27 +510,27 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
pub fn extractContents(self: *Range, page: *Page) !*DocumentFragment {
|
||||
const fragment = try self.cloneContents(page);
|
||||
try self.deleteContents(page);
|
||||
pub fn extractContents(self: *Range, frame: *Frame) !*DocumentFragment {
|
||||
const fragment = try self.cloneContents(frame);
|
||||
try self.deleteContents(frame);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
pub fn surroundContents(self: *Range, new_parent: *Node, page: *Page) !void {
|
||||
pub fn surroundContents(self: *Range, new_parent: *Node, frame: *Frame) !void {
|
||||
// Extract contents
|
||||
const contents = try self.extractContents(page);
|
||||
const contents = try self.extractContents(frame);
|
||||
|
||||
// Insert the new parent
|
||||
try self.insertNode(new_parent, page);
|
||||
try self.insertNode(new_parent, frame);
|
||||
|
||||
// Move contents into new parent
|
||||
_ = try new_parent.appendChild(contents.asNode(), page);
|
||||
_ = try new_parent.appendChild(contents.asNode(), frame);
|
||||
|
||||
// Select the new parent's contents
|
||||
try self.selectNodeContents(new_parent);
|
||||
}
|
||||
|
||||
pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Page) !*DocumentFragment {
|
||||
pub fn createContextualFragment(self: *const Range, html: []const u8, frame: *Frame) !*DocumentFragment {
|
||||
var context_node = self._proto._start_container;
|
||||
|
||||
// If start container is a text node, use its parent as context
|
||||
@@ -538,7 +538,7 @@ pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Pag
|
||||
context_node = context_node.parentNode() orelse context_node;
|
||||
}
|
||||
|
||||
const fragment = try DocumentFragment.init(page);
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
|
||||
if (html.len == 0) {
|
||||
return fragment;
|
||||
@@ -547,26 +547,26 @@ pub fn createContextualFragment(self: *const Range, html: []const u8, page: *Pag
|
||||
// Create a temporary element of the same type as the context for parsing
|
||||
// This preserves the parsing context without modifying the original node
|
||||
const temp_node = if (context_node.is(Node.Element)) |el|
|
||||
try page.createElementNS(el._namespace, el.getTagNameLower(), null)
|
||||
try frame.createElementNS(el._namespace, el.getTagNameLower(), null)
|
||||
else
|
||||
try page.createElementNS(.html, "div", null);
|
||||
try frame.createElementNS(.html, "div", null);
|
||||
|
||||
try page.parseHtmlAsChildren(temp_node, html);
|
||||
try frame.parseHtmlAsChildren(temp_node, html);
|
||||
|
||||
// Move all parsed children to the fragment
|
||||
// Keep removing first child until temp element is empty
|
||||
const fragment_node = fragment.asNode();
|
||||
while (temp_node.firstChild()) |child| {
|
||||
page.removeNode(temp_node, child, .{ .will_be_reconnected = true });
|
||||
try page.appendNode(fragment_node, child, .{ .child_already_connected = false });
|
||||
frame.removeNode(temp_node, child, .{ .will_be_reconnected = true });
|
||||
try frame.appendNode(fragment_node, child, .{ .child_already_connected = false });
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
pub fn toString(self: *const Range, page: *Page) ![]const u8 {
|
||||
pub fn toString(self: *const Range, frame: *Frame) ![]const u8 {
|
||||
// Simplified implementation: just extract text content
|
||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
var buf = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
try self.writeTextContent(&buf.writer);
|
||||
return buf.written();
|
||||
}
|
||||
@@ -661,24 +661,24 @@ fn nextAfterSubtree(node: *Node, root: *Node) ?*Node {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getBoundingClientRect(self: *const Range, page: *Page) DOMRect {
|
||||
pub fn getBoundingClientRect(self: *const Range, frame: *Frame) DOMRect {
|
||||
if (self._proto.getCollapsed()) {
|
||||
return .{ ._x = 0, ._y = 0, ._width = 0, ._height = 0 };
|
||||
}
|
||||
const element = self.getContainerElement() orelse {
|
||||
return .{ ._x = 0, ._y = 0, ._width = 0, ._height = 0 };
|
||||
};
|
||||
return element.getBoundingClientRect(page);
|
||||
return element.getBoundingClientRect(frame);
|
||||
}
|
||||
|
||||
pub fn getClientRects(self: *const Range, page: *Page) ![]DOMRect {
|
||||
pub fn getClientRects(self: *const Range, frame: *Frame) ![]DOMRect {
|
||||
if (self._proto.getCollapsed()) {
|
||||
return &.{};
|
||||
}
|
||||
const element = self.getContainerElement() orelse {
|
||||
return &.{};
|
||||
};
|
||||
return element.getClientRects(page);
|
||||
return element.getClientRects(frame);
|
||||
}
|
||||
|
||||
fn getContainerElement(self: *const Range) ?*Node.Element {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
@@ -36,11 +36,11 @@ pub fn asEventTarget(self: *Screen) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getOrientation(self: *Screen, page: *Page) !*Orientation {
|
||||
pub fn getOrientation(self: *Screen, frame: *Frame) !*Orientation {
|
||||
if (self._orientation) |orientation| {
|
||||
return orientation;
|
||||
}
|
||||
const orientation = try Orientation.init(page);
|
||||
const orientation = try Orientation.init(frame);
|
||||
self._orientation = orientation;
|
||||
return orientation;
|
||||
}
|
||||
@@ -66,8 +66,8 @@ pub const JsApi = struct {
|
||||
pub const Orientation = struct {
|
||||
_proto: *EventTarget,
|
||||
|
||||
pub fn init(page: *Page) !*Orientation {
|
||||
return page._factory.eventTarget(Orientation{
|
||||
pub fn init(frame: *Frame) !*Orientation {
|
||||
return frame._factory.eventTarget(Orientation{
|
||||
._proto = undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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");
|
||||
@@ -54,9 +54,9 @@ pub fn acquireRef(self: *Selection) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
fn dispatchSelectionChangeEvent(page: *Page) !void {
|
||||
const event = try Event.init("selectionchange", .{}, page);
|
||||
try page._event_manager.dispatch(page.document.asEventTarget(), event);
|
||||
fn dispatchSelectionChangeEvent(frame: *Frame) !void {
|
||||
const event = try Event.init("selectionchange", .{}, frame);
|
||||
try frame._event_manager.dispatch(frame.document.asEventTarget(), event);
|
||||
}
|
||||
|
||||
fn isInTree(self: *const Selection) bool {
|
||||
@@ -146,70 +146,70 @@ pub fn getType(self: *const Selection) []const u8 {
|
||||
return "Range";
|
||||
}
|
||||
|
||||
pub fn addRange(self: *Selection, range: *Range, page: *Page) !void {
|
||||
pub fn addRange(self: *Selection, range: *Range, frame: *Frame) !void {
|
||||
if (self._range != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add the range if its root node is in the document associated with this selection
|
||||
const start_node = range.asAbstractRange().getStartContainer();
|
||||
if (!page.document.asNode().contains(start_node)) {
|
||||
if (!frame.document.asNode().contains(start_node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.setRange(range, page);
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
self.setRange(range, frame);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn removeRange(self: *Selection, range: *Range, page: *Page) !void {
|
||||
pub fn removeRange(self: *Selection, range: *Range, frame: *Frame) !void {
|
||||
const existing_range = self._range orelse return error.NotFound;
|
||||
if (existing_range != range) {
|
||||
return error.NotFound;
|
||||
}
|
||||
self.setRange(null, page);
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
self.setRange(null, frame);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn removeAllRanges(self: *Selection, page: *Page) !void {
|
||||
pub fn removeAllRanges(self: *Selection, frame: *Frame) !void {
|
||||
if (self._range == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.setRange(null, page);
|
||||
self.setRange(null, frame);
|
||||
self._direction = .none;
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
||||
pub fn collapseToEnd(self: *Selection, frame: *Frame) !void {
|
||||
const range = self._range orelse return;
|
||||
|
||||
const abstract = range.asAbstractRange();
|
||||
const last_node = abstract.getEndContainer();
|
||||
const last_offset = abstract.getEndOffset();
|
||||
|
||||
const new_range = try Range.init(page);
|
||||
const new_range = try Range.init(frame);
|
||||
try new_range.setStart(last_node, last_offset);
|
||||
try new_range.setEnd(last_node, last_offset);
|
||||
|
||||
self.setRange(new_range, page);
|
||||
self.setRange(new_range, frame);
|
||||
self._direction = .none;
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
||||
pub fn collapseToStart(self: *Selection, frame: *Frame) !void {
|
||||
const range = self._range orelse return error.InvalidStateError;
|
||||
|
||||
const abstract = range.asAbstractRange();
|
||||
const first_node = abstract.getStartContainer();
|
||||
const first_offset = abstract.getStartOffset();
|
||||
|
||||
const new_range = try Range.init(page);
|
||||
const new_range = try Range.init(frame);
|
||||
try new_range.setStart(first_node, first_offset);
|
||||
try new_range.setEnd(first_node, first_offset);
|
||||
|
||||
self.setRange(new_range, page);
|
||||
self.setRange(new_range, frame);
|
||||
self._direction = .none;
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn containsNode(self: *const Selection, node: *Node, partial: bool) !bool {
|
||||
@@ -238,18 +238,18 @@ pub fn containsNode(self: *const Selection, node: *Node, partial: bool) !bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn deleteFromDocument(self: *Selection, page: *Page) !void {
|
||||
pub fn deleteFromDocument(self: *Selection, frame: *Frame) !void {
|
||||
const range = self._range orelse return;
|
||||
try range.deleteContents(page);
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try range.deleteContents(frame);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
||||
pub fn extend(self: *Selection, node: *Node, _offset: ?u32, frame: *Frame) !void {
|
||||
const range = self._range orelse return error.InvalidState;
|
||||
const offset = _offset orelse 0;
|
||||
|
||||
// If the node is not contained in the document, do not change the selection
|
||||
if (!page.document.asNode().contains(node)) {
|
||||
if (!frame.document.asNode().contains(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
||||
.forward, .none => range.asAbstractRange().getStartOffset(),
|
||||
};
|
||||
|
||||
const new_range = try Range.init(page);
|
||||
const new_range = try Range.init(frame);
|
||||
|
||||
const cmp = AbstractRange.compareBoundaryPoints(node, offset, old_anchor, old_anchor_offset);
|
||||
switch (cmp) {
|
||||
@@ -289,8 +289,8 @@ pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
||||
},
|
||||
}
|
||||
|
||||
self.setRange(new_range, page);
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
self.setRange(new_range, frame);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn getRangeAt(self: *Selection, index: u32) !*Range {
|
||||
@@ -338,7 +338,7 @@ pub fn modify(
|
||||
alter_str: []const u8,
|
||||
direction_str: []const u8,
|
||||
granularity_str: []const u8,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
const alter = ModifyAlter.fromString(alter_str) orelse return;
|
||||
const direction = ModifyDirection.fromString(direction_str) orelse return;
|
||||
@@ -352,8 +352,8 @@ pub fn modify(
|
||||
};
|
||||
|
||||
switch (granularity) {
|
||||
.character => try self.modifyByCharacter(alter, is_forward, range, page),
|
||||
.word => try self.modifyByWord(alter, is_forward, range, page),
|
||||
.character => try self.modifyByCharacter(alter, is_forward, range, frame),
|
||||
.word => try self.modifyByWord(alter, is_forward, range, frame),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ fn prevTextNode(node: *Node) ?*Node {
|
||||
}
|
||||
}
|
||||
|
||||
fn modifyByCharacter(self: *Selection, alter: ModifyAlter, forward: bool, range: *Range, page: *Page) !void {
|
||||
fn modifyByCharacter(self: *Selection, alter: ModifyAlter, forward: bool, range: *Range, frame: *Frame) !void {
|
||||
const abstract = range.asAbstractRange();
|
||||
|
||||
const focus_node = switch (self._direction) {
|
||||
@@ -491,7 +491,7 @@ fn modifyByCharacter(self: *Selection, alter: ModifyAlter, forward: bool, range:
|
||||
}
|
||||
}
|
||||
|
||||
try self.applyModify(alter, new_node, new_offset, page);
|
||||
try self.applyModify(alter, new_node, new_offset, frame);
|
||||
}
|
||||
|
||||
fn isWordChar(c: u8) bool {
|
||||
@@ -517,7 +517,7 @@ fn prevWordStart(text: []const u8, offset: u32) u32 {
|
||||
return i;
|
||||
}
|
||||
|
||||
fn modifyByWord(self: *Selection, alter: ModifyAlter, forward: bool, range: *Range, page: *Page) !void {
|
||||
fn modifyByWord(self: *Selection, alter: ModifyAlter, forward: bool, range: *Range, frame: *Frame) !void {
|
||||
const abstract = range.asAbstractRange();
|
||||
|
||||
const focus_node = switch (self._direction) {
|
||||
@@ -560,11 +560,11 @@ fn modifyByWord(self: *Selection, alter: ModifyAlter, forward: bool, range: *Ran
|
||||
new_node = next;
|
||||
new_offset = nextWordEnd(next.getData().str(), 0);
|
||||
}
|
||||
return self.applyModify(alter, new_node, new_offset, page);
|
||||
return self.applyModify(alter, new_node, new_offset, frame);
|
||||
};
|
||||
|
||||
const t = if (isTextNode(child)) child else nextTextNode(child) orelse {
|
||||
return self.applyModify(alter, new_node, new_offset, page);
|
||||
return self.applyModify(alter, new_node, new_offset, frame);
|
||||
};
|
||||
|
||||
new_node = t;
|
||||
@@ -585,41 +585,41 @@ fn modifyByWord(self: *Selection, alter: ModifyAlter, forward: bool, range: *Ran
|
||||
}
|
||||
}
|
||||
|
||||
try self.applyModify(alter, new_node, new_offset, page);
|
||||
try self.applyModify(alter, new_node, new_offset, frame);
|
||||
}
|
||||
|
||||
fn applyModify(self: *Selection, alter: ModifyAlter, new_node: *Node, new_offset: u32, page: *Page) !void {
|
||||
fn applyModify(self: *Selection, alter: ModifyAlter, new_node: *Node, new_offset: u32, frame: *Frame) !void {
|
||||
switch (alter) {
|
||||
.move => {
|
||||
const new_range = try Range.init(page);
|
||||
const new_range = try Range.init(frame);
|
||||
try new_range.setStart(new_node, new_offset);
|
||||
try new_range.setEnd(new_node, new_offset);
|
||||
|
||||
self.setRange(new_range, page);
|
||||
self.setRange(new_range, frame);
|
||||
self._direction = .none;
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
},
|
||||
.extend => try self.extend(new_node, new_offset, page),
|
||||
.extend => try self.extend(new_node, new_offset, frame),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selectAllChildren(self: *Selection, parent: *Node, page: *Page) !void {
|
||||
pub fn selectAllChildren(self: *Selection, parent: *Node, frame: *Frame) !void {
|
||||
if (parent._type == .document_type) return error.InvalidNodeType;
|
||||
|
||||
// If the node is not contained in the document, do not change the selection
|
||||
if (!page.document.asNode().contains(parent)) {
|
||||
if (!frame.document.asNode().contains(parent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = try Range.init(page);
|
||||
const range = try Range.init(frame);
|
||||
try range.setStart(parent, 0);
|
||||
|
||||
const child_count = parent.getChildrenCount();
|
||||
try range.setEnd(parent, @intCast(child_count));
|
||||
|
||||
self.setRange(range, page);
|
||||
self.setRange(range, frame);
|
||||
self._direction = .forward;
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn setBaseAndExtent(
|
||||
@@ -628,7 +628,7 @@ pub fn setBaseAndExtent(
|
||||
anchor_offset: u32,
|
||||
focus_node: *Node,
|
||||
focus_offset: u32,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !void {
|
||||
if (anchor_offset > anchor_node.getLength()) {
|
||||
return error.IndexSizeError;
|
||||
@@ -645,7 +645,7 @@ pub fn setBaseAndExtent(
|
||||
focus_offset,
|
||||
);
|
||||
|
||||
const range = try Range.init(page);
|
||||
const range = try Range.init(frame);
|
||||
|
||||
switch (cmp) {
|
||||
.before => {
|
||||
@@ -665,13 +665,13 @@ pub fn setBaseAndExtent(
|
||||
},
|
||||
}
|
||||
|
||||
self.setRange(range, page);
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
self.setRange(range, frame);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !void {
|
||||
pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, frame: *Frame) !void {
|
||||
const node = _node orelse {
|
||||
try self.removeAllRanges(page);
|
||||
try self.removeAllRanges(frame);
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -683,27 +683,27 @@ pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !vo
|
||||
}
|
||||
|
||||
// If the node is not contained in the document, do not change the selection
|
||||
if (!page.document.asNode().contains(node)) {
|
||||
if (!frame.document.asNode().contains(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = try Range.init(page);
|
||||
const range = try Range.init(frame);
|
||||
try range.setStart(node, offset);
|
||||
try range.setEnd(node, offset);
|
||||
|
||||
self.setRange(range, page);
|
||||
self.setRange(range, frame);
|
||||
self._direction = .none;
|
||||
try dispatchSelectionChangeEvent(page);
|
||||
try dispatchSelectionChangeEvent(frame);
|
||||
}
|
||||
|
||||
pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
|
||||
pub fn toString(self: *const Selection, frame: *Frame) ![]const u8 {
|
||||
const range = self._range orelse return "";
|
||||
return try range.toString(page);
|
||||
return try range.toString(frame);
|
||||
}
|
||||
|
||||
fn setRange(self: *Selection, new_range: ?*Range, page: *Page) void {
|
||||
fn setRange(self: *Selection, new_range: ?*Range, frame: *Frame) void {
|
||||
if (self._range) |existing| {
|
||||
_ = existing.asAbstractRange().releaseRef(page._session);
|
||||
_ = existing.asAbstractRange().releaseRef(frame._session);
|
||||
}
|
||||
if (new_range) |nr| {
|
||||
nr.asAbstractRange().acquireRef();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Node = @import("Node.zig");
|
||||
const DocumentFragment = @import("DocumentFragment.zig");
|
||||
const Element = @import("Element.zig");
|
||||
@@ -42,8 +42,8 @@ _elements_by_id: std.StringHashMapUnmanaged(*Element) = .{},
|
||||
_removed_ids: std.StringHashMapUnmanaged(void) = .{},
|
||||
_adopted_style_sheets: ?js.Object.Global = null,
|
||||
|
||||
pub fn init(host: *Element, mode: Mode, page: *Page) !*ShadowRoot {
|
||||
return page._factory.documentFragment(ShadowRoot{
|
||||
pub fn init(host: *Element, mode: Mode, frame: *Frame) !*ShadowRoot {
|
||||
return frame._factory.documentFragment(ShadowRoot{
|
||||
._proto = undefined,
|
||||
._mode = mode,
|
||||
._host = host,
|
||||
@@ -70,7 +70,7 @@ pub fn getHost(self: *const ShadowRoot) *Element {
|
||||
return self._host;
|
||||
}
|
||||
|
||||
pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element {
|
||||
pub fn getElementById(self: *ShadowRoot, id: []const u8, frame: *Frame) ?*Element {
|
||||
if (id.len == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -90,8 +90,8 @@ pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element
|
||||
// we ignore this error to keep getElementById easy to call
|
||||
// if it really failed, then we're out of memory and nothing's
|
||||
// going to work like it should anyways.
|
||||
const owned_id = page.dupeString(id) catch return null;
|
||||
self._elements_by_id.put(page.arena, owned_id, el) catch return null;
|
||||
const owned_id = frame.dupeString(id) catch return null;
|
||||
self._elements_by_id.put(frame.arena, owned_id, el) catch return null;
|
||||
return el;
|
||||
}
|
||||
}
|
||||
@@ -100,11 +100,11 @@ pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getAdoptedStyleSheets(self: *ShadowRoot, page: *Page) !js.Object.Global {
|
||||
pub fn getAdoptedStyleSheets(self: *ShadowRoot, frame: *Frame) !js.Object.Global {
|
||||
if (self._adopted_style_sheets) |ass| {
|
||||
return ass;
|
||||
}
|
||||
const js_arr = page.js.local.?.newArray(0);
|
||||
const js_arr = frame.js.local.?.newArray(0);
|
||||
const js_obj = js_arr.toObject();
|
||||
self._adopted_style_sheets = try js_obj.persist();
|
||||
return self._adopted_style_sheets.?;
|
||||
@@ -126,15 +126,15 @@ pub const JsApi = struct {
|
||||
pub const mode = bridge.accessor(ShadowRoot.getMode, null, .{});
|
||||
pub const host = bridge.accessor(ShadowRoot.getHost, null, .{});
|
||||
pub const getElementById = bridge.function(_getElementById, .{});
|
||||
fn _getElementById(self: *ShadowRoot, value_: ?js.Value, page: *Page) !?*Element {
|
||||
fn _getElementById(self: *ShadowRoot, value_: ?js.Value, frame: *Frame) !?*Element {
|
||||
const value = value_ orelse return null;
|
||||
if (value.isNull()) {
|
||||
return self.getElementById("null", page);
|
||||
return self.getElementById("null", frame);
|
||||
}
|
||||
if (value.isUndefined()) {
|
||||
return self.getElementById("undefined", page);
|
||||
return self.getElementById("undefined", frame);
|
||||
}
|
||||
return self.getElementById(try value.toZig([]const u8), page);
|
||||
return self.getElementById(try value.toZig([]const u8), frame);
|
||||
}
|
||||
pub const adoptedStyleSheets = bridge.accessor(ShadowRoot.getAdoptedStyleSheets, ShadowRoot.setAdoptedStyleSheets, .{});
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{ StorageManager, StorageEstimate };
|
||||
@@ -27,12 +27,12 @@ const StorageManager = @This();
|
||||
|
||||
_pad: bool = false,
|
||||
|
||||
pub fn estimate(_: *const StorageManager, page: *Page) !js.Promise {
|
||||
const est = try page._factory.create(StorageEstimate{
|
||||
pub fn estimate(_: *const StorageManager, frame: *Frame) !js.Promise {
|
||||
const est = try frame._factory.create(StorageEstimate{
|
||||
._usage = 0,
|
||||
._quota = 1024 * 1024 * 1024, // 1 GiB
|
||||
});
|
||||
return page.js.local.?.resolvePromise(est);
|
||||
return frame.js.local.?.resolvePromise(est);
|
||||
}
|
||||
|
||||
const StorageEstimate = struct {
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const crypto = @import("../../sys/libcrypto.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const CryptoKey = @import("CryptoKey.zig");
|
||||
@@ -45,13 +45,13 @@ pub fn generateKey(
|
||||
algo: algorithm.Init,
|
||||
extractable: bool,
|
||||
key_usages: []const []const u8,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !js.Promise {
|
||||
switch (algo) {
|
||||
.hmac_key_gen => |params| return HMAC.init(params, extractable, key_usages, page),
|
||||
.hmac_key_gen => |params| return HMAC.init(params, extractable, key_usages, frame),
|
||||
.name => |name| {
|
||||
if (std.mem.eql(u8, "X25519", name)) {
|
||||
return X25519.init(extractable, key_usages, page);
|
||||
return X25519.init(extractable, key_usages, frame);
|
||||
}
|
||||
|
||||
log.warn(.not_implemented, "generateKey", .{ .name = name });
|
||||
@@ -60,7 +60,7 @@ pub fn generateKey(
|
||||
// Ditto.
|
||||
const name = object.name;
|
||||
if (std.mem.eql(u8, "X25519", name)) {
|
||||
return X25519.init(extractable, key_usages, page);
|
||||
return X25519.init(extractable, key_usages, frame);
|
||||
}
|
||||
|
||||
log.warn(.not_implemented, "generateKey", .{ .name = name });
|
||||
@@ -68,7 +68,7 @@ pub fn generateKey(
|
||||
else => log.warn(.not_implemented, "generateKey", .{}),
|
||||
}
|
||||
|
||||
return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } });
|
||||
return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } });
|
||||
}
|
||||
|
||||
/// Exports a key: that is, it takes as input a CryptoKey object and gives you
|
||||
@@ -77,14 +77,14 @@ pub fn exportKey(
|
||||
_: *const SubtleCrypto,
|
||||
format: []const u8,
|
||||
key: *CryptoKey,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !js.Promise {
|
||||
if (!key.canExportKey()) {
|
||||
return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, format, "raw")) {
|
||||
return page.js.local.?.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
||||
return frame.js.local.?.resolvePromise(js.ArrayBuffer{ .values = key._key });
|
||||
}
|
||||
|
||||
const is_unsupported = std.mem.eql(u8, format, "pkcs8") or
|
||||
@@ -92,10 +92,10 @@ pub fn exportKey(
|
||||
|
||||
if (is_unsupported) {
|
||||
log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format });
|
||||
return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
}
|
||||
|
||||
return page.js.local.?.rejectPromise(.{ .type_error = "invalid format" });
|
||||
return frame.js.local.?.rejectPromise(.{ .type_error = "invalid format" });
|
||||
}
|
||||
|
||||
/// Derive a secret key from a master key.
|
||||
@@ -104,27 +104,27 @@ pub fn deriveBits(
|
||||
algo: algorithm.Derive,
|
||||
base_key: *const CryptoKey, // Private key.
|
||||
length: usize,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !js.Promise {
|
||||
return switch (algo) {
|
||||
.ecdh_or_x25519 => |params| {
|
||||
const name = params.name;
|
||||
if (std.mem.eql(u8, name, "X25519")) {
|
||||
const result = X25519.deriveBits(base_key, params.public, length, page) catch |err| switch (err) {
|
||||
error.InvalidAccessError => return page.js.local.?.rejectPromise(.{
|
||||
const result = X25519.deriveBits(base_key, params.public, length, frame) catch |err| switch (err) {
|
||||
error.InvalidAccessError => return frame.js.local.?.rejectPromise(.{
|
||||
.dom_exception = .{ .err = error.InvalidAccessError },
|
||||
}),
|
||||
else => return err,
|
||||
};
|
||||
|
||||
return page.js.local.?.resolvePromise(result);
|
||||
return frame.js.local.?.resolvePromise(result);
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, name, "ECDH")) {
|
||||
log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name });
|
||||
}
|
||||
|
||||
return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -136,14 +136,14 @@ pub fn sign(
|
||||
algo: algorithm.Sign,
|
||||
key: *CryptoKey,
|
||||
data: []const u8, // ArrayBuffer.
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !js.Promise {
|
||||
return switch (key._type) {
|
||||
// Call sign for HMAC.
|
||||
.hmac => return HMAC.sign(algo, key, data, page),
|
||||
.hmac => return HMAC.sign(algo, key, data, frame),
|
||||
else => {
|
||||
log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type });
|
||||
return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -155,33 +155,33 @@ pub fn verify(
|
||||
key: *const CryptoKey,
|
||||
signature: []const u8, // ArrayBuffer.
|
||||
data: []const u8, // ArrayBuffer.
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !js.Promise {
|
||||
if (!algo.isHMAC()) {
|
||||
return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
return frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } });
|
||||
}
|
||||
|
||||
return switch (key._type) {
|
||||
.hmac => HMAC.verify(key, signature, data, page),
|
||||
else => page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
|
||||
.hmac => HMAC.verify(key, signature, data, frame),
|
||||
else => frame.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }),
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a digest of the given data, using the specified hash function.
|
||||
pub fn digest(_: *const SubtleCrypto, algo: []const u8, data: js.TypedArray(u8), page: *Page) !js.Promise {
|
||||
const local = page.js.local.?;
|
||||
pub fn digest(_: *const SubtleCrypto, algo: []const u8, data: js.TypedArray(u8), frame: *Frame) !js.Promise {
|
||||
const local = frame.js.local.?;
|
||||
|
||||
if (algo.len > 10) {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
}
|
||||
|
||||
const normalized = std.ascii.upperString(&page.buf, algo);
|
||||
const normalized = std.ascii.upperString(&frame.buf, algo);
|
||||
const digest_type = crypto.findDigest(normalized) catch {
|
||||
return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } });
|
||||
};
|
||||
|
||||
const bytes = data.values;
|
||||
const out = page.buf[0..crypto.EVP_MAX_MD_SIZE];
|
||||
const out = frame.buf[0..crypto.EVP_MAX_MD_SIZE];
|
||||
var out_size: c_uint = 0;
|
||||
const result = crypto.EVP_Digest(bytes.ptr, bytes.len, out, &out_size, digest_type, null);
|
||||
lp.assert(result == 1, "SubtleCrypto.digest", .{ .algo = algo });
|
||||
|
||||
@@ -160,9 +160,9 @@ pub fn TreeWalker(comptime mode: Mode) type {
|
||||
|
||||
test "TreeWalker: skipChildren" {
|
||||
const testing = @import("../../testing.zig");
|
||||
const page = try testing.test_session.createPage();
|
||||
defer testing.test_session.removePage();
|
||||
const doc = page.window._document;
|
||||
const frame = try testing.test_session.createFrame();
|
||||
defer testing.test_session.removeFrame();
|
||||
const doc = frame.window._document;
|
||||
|
||||
// <div>
|
||||
// <span>
|
||||
@@ -170,13 +170,13 @@ test "TreeWalker: skipChildren" {
|
||||
// </span>
|
||||
// <p>B</p>
|
||||
// </div>
|
||||
const div = try doc.createElement("div", null, page);
|
||||
const span = try doc.createElement("span", null, page);
|
||||
const b = try doc.createElement("b", null, page);
|
||||
const p = try doc.createElement("p", null, page);
|
||||
_ = try span.asNode().appendChild(b.asNode(), page);
|
||||
_ = try div.asNode().appendChild(span.asNode(), page);
|
||||
_ = try div.asNode().appendChild(p.asNode(), page);
|
||||
const div = try doc.createElement("div", null, frame);
|
||||
const span = try doc.createElement("span", null, frame);
|
||||
const b = try doc.createElement("b", null, frame);
|
||||
const p = try doc.createElement("p", null, frame);
|
||||
_ = try span.asNode().appendChild(b.asNode(), frame);
|
||||
_ = try div.asNode().appendChild(span.asNode(), frame);
|
||||
_ = try div.asNode().appendChild(p.asNode(), frame);
|
||||
|
||||
var tw = Full.init(div.asNode(), .{});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const U = @import("../URL.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const URLSearchParams = @import("net/URLSearchParams.zig");
|
||||
const Blob = @import("Blob.zig");
|
||||
const Execution = js.Execution;
|
||||
@@ -248,28 +248,28 @@ pub fn canParse(url: []const u8, base_: ?[]const u8) bool {
|
||||
return U.isCompleteHTTPUrl(url);
|
||||
}
|
||||
|
||||
pub fn createObjectURL(blob: *Blob, page: *Page) ![]const u8 {
|
||||
pub fn createObjectURL(blob: *Blob, frame: *Frame) ![]const u8 {
|
||||
var uuid_buf: [36]u8 = undefined;
|
||||
@import("../../id.zig").uuidv4(&uuid_buf);
|
||||
|
||||
const blob_url = try std.fmt.allocPrint(
|
||||
page.arena,
|
||||
frame.arena,
|
||||
"blob:{s}/{s}",
|
||||
.{ page.origin orelse "null", uuid_buf },
|
||||
.{ frame.origin orelse "null", uuid_buf },
|
||||
);
|
||||
try page._blob_urls.put(page.arena, blob_url, blob);
|
||||
try frame._blob_urls.put(frame.arena, blob_url, blob);
|
||||
blob.acquireRef();
|
||||
return blob_url;
|
||||
}
|
||||
|
||||
pub fn revokeObjectURL(url: []const u8, page: *Page) void {
|
||||
pub fn revokeObjectURL(url: []const u8, frame: *Frame) void {
|
||||
// Per spec: silently ignore non-blob URLs
|
||||
if (!std.mem.startsWith(u8, url, "blob:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (page._blob_urls.fetchRemove(url)) |entry| {
|
||||
entry.value.releaseRef(page._session);
|
||||
if (frame._blob_urls.fetchRemove(url)) |entry| {
|
||||
entry.value.releaseRef(frame._session);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const EventTarget = @import("EventTarget.zig");
|
||||
|
||||
const VisualViewport = @This();
|
||||
@@ -28,12 +28,12 @@ pub fn asEventTarget(self: *VisualViewport) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getPageLeft(_: *const VisualViewport, page: *Page) u32 {
|
||||
return page.window.getScrollX();
|
||||
pub fn getPageLeft(_: *const VisualViewport, frame: *Frame) u32 {
|
||||
return frame.window.getScrollX();
|
||||
}
|
||||
|
||||
pub fn getPageTop(_: *const VisualViewport, page: *Page) u32 {
|
||||
return page.window.getScrollY();
|
||||
pub fn getPageTop(_: *const VisualViewport, frame: *Frame) u32 {
|
||||
return frame.window.getScrollY();
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
@@ -21,7 +21,7 @@ const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Console = @import("Console.zig");
|
||||
const History = @import("History.zig");
|
||||
const Navigation = @import("navigation/Navigation.zig");
|
||||
@@ -57,7 +57,7 @@ pub fn registerTypes() []const type {
|
||||
const Window = @This();
|
||||
|
||||
_proto: *EventTarget,
|
||||
_page: *Page,
|
||||
_frame: *Frame,
|
||||
_document: *Document,
|
||||
_css: CSS = .init,
|
||||
_crypto: Crypto = .init,
|
||||
@@ -111,17 +111,17 @@ pub fn getWindow(self: *Window) *Window {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn getTop(self: *Window, page: *Page) Access {
|
||||
var p = self._page;
|
||||
pub fn getTop(self: *Window, frame: *Frame) Access {
|
||||
var p = self._frame;
|
||||
while (p.parent) |parent| {
|
||||
p = parent;
|
||||
}
|
||||
return Access.init(page.window, p.window);
|
||||
return Access.init(frame.window, p.window);
|
||||
}
|
||||
|
||||
pub fn getParent(self: *Window, page: *Page) Access {
|
||||
if (self._page.parent) |p| {
|
||||
return Access.init(page.window, p.window);
|
||||
pub fn getParent(self: *Window, frame: *Frame) Access {
|
||||
if (self._frame.parent) |p| {
|
||||
return Access.init(frame.window, p.window);
|
||||
}
|
||||
return .{ .window = self };
|
||||
}
|
||||
@@ -167,7 +167,7 @@ pub fn getSessionStorage(self: *Window) *storage.Lookup {
|
||||
}
|
||||
|
||||
pub fn getOrigin(self: *const Window) []const u8 {
|
||||
return self._page.origin orelse "null";
|
||||
return self._frame.origin orelse "null";
|
||||
}
|
||||
|
||||
pub fn getLocation(self: *const Window) *Location {
|
||||
@@ -178,16 +178,16 @@ pub fn getSelection(self: *const Window) *Selection {
|
||||
return &self._document._selection;
|
||||
}
|
||||
|
||||
pub fn setLocation(self: *Window, url: [:0]const u8, page: *Page) !void {
|
||||
return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._page });
|
||||
pub fn setLocation(self: *Window, url: [:0]const u8, frame: *Frame) !void {
|
||||
return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._frame });
|
||||
}
|
||||
|
||||
pub fn getHistory(_: *Window, page: *Page) *History {
|
||||
return &page._session.history;
|
||||
pub fn getHistory(_: *Window, frame: *Frame) *History {
|
||||
return &frame._session.history;
|
||||
}
|
||||
|
||||
pub fn getNavigation(_: *Window, page: *Page) *Navigation {
|
||||
return &page._session.navigation;
|
||||
pub fn getNavigation(_: *Window, frame: *Frame) *Navigation {
|
||||
return &frame._session.navigation;
|
||||
}
|
||||
|
||||
pub fn getCustomElements(self: *Window) *CustomElementRegistry {
|
||||
@@ -250,8 +250,8 @@ pub fn setOnUnhandledRejection(self: *Window, setter: ?FunctionSetter) void {
|
||||
self._on_unhandled_rejection = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, page: *Page) !js.Promise {
|
||||
return Fetch.init(input, options, page);
|
||||
pub fn fetch(_: *const Window, input: Fetch.Input, options: ?Fetch.InitOpts, frame: *Frame) !js.Promise {
|
||||
return Fetch.init(input, options, frame);
|
||||
}
|
||||
|
||||
const LegacyHandler = union(enum) {
|
||||
@@ -259,24 +259,24 @@ const LegacyHandler = union(enum) {
|
||||
string: js.String,
|
||||
};
|
||||
|
||||
pub fn setTimeout(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
|
||||
const cb = try resolveTimerHandler(handler, page);
|
||||
pub fn setTimeout(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, frame: *Frame) !u32 {
|
||||
const cb = try resolveTimerHandler(handler, frame);
|
||||
return self.scheduleCallback(cb, delay_ms orelse 0, .{
|
||||
.repeat = false,
|
||||
.params = params,
|
||||
.low_priority = false,
|
||||
.name = "window.setTimeout",
|
||||
}, page);
|
||||
}, frame);
|
||||
}
|
||||
|
||||
pub fn setInterval(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, page: *Page) !u32 {
|
||||
const cb = try resolveTimerHandler(handler, page);
|
||||
pub fn setInterval(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params: []js.Value.Temp, frame: *Frame) !u32 {
|
||||
const cb = try resolveTimerHandler(handler, frame);
|
||||
return self.scheduleCallback(cb, delay_ms orelse 0, .{
|
||||
.repeat = true,
|
||||
.params = params,
|
||||
.low_priority = false,
|
||||
.name = "window.setInterval",
|
||||
}, page);
|
||||
}, frame);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout
|
||||
@@ -284,37 +284,37 @@ pub fn setInterval(self: *Window, handler: LegacyHandler, delay_ms: ?u32, params
|
||||
// TimerHandler = Function or DOMString. When a string is passed, it is
|
||||
// compiled into an anonymous function body, matching how legacy browsers
|
||||
// (and all current UAs) interpret `setTimeout("foo()", 100)`.
|
||||
fn resolveTimerHandler(handler: LegacyHandler, page: *Page) !js.Function.Temp {
|
||||
fn resolveTimerHandler(handler: LegacyHandler, frame: *Frame) !js.Function.Temp {
|
||||
switch (handler) {
|
||||
.function => |fun| return fun,
|
||||
.string => |str| {
|
||||
const fun = try page.js.local.?.compileFunction(str, &.{}, &.{});
|
||||
const fun = try frame.js.local.?.compileFunction(str, &.{}, &.{});
|
||||
return fun.temp();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setImmediate(self: *Window, cb: js.Function.Temp, params: []js.Value.Temp, page: *Page) !u32 {
|
||||
pub fn setImmediate(self: *Window, cb: js.Function.Temp, params: []js.Value.Temp, frame: *Frame) !u32 {
|
||||
return self.scheduleCallback(cb, 0, .{
|
||||
.repeat = false,
|
||||
.params = params,
|
||||
.low_priority = false,
|
||||
.name = "window.setImmediate",
|
||||
}, page);
|
||||
}, frame);
|
||||
}
|
||||
|
||||
pub fn requestAnimationFrame(self: *Window, cb: js.Function.Temp, page: *Page) !u32 {
|
||||
pub fn requestAnimationFrame(self: *Window, cb: js.Function.Temp, frame: *Frame) !u32 {
|
||||
return self.scheduleCallback(cb, 5, .{
|
||||
.repeat = false,
|
||||
.params = &.{},
|
||||
.low_priority = false,
|
||||
.mode = .animation_frame,
|
||||
.name = "window.requestAnimationFrame",
|
||||
}, page);
|
||||
}, frame);
|
||||
}
|
||||
|
||||
pub fn queueMicrotask(_: *Window, cb: js.Function, page: *Page) void {
|
||||
page.js.queueMicrotaskFunc(cb);
|
||||
pub fn queueMicrotask(_: *Window, cb: js.Function, frame: *Frame) void {
|
||||
frame.js.queueMicrotaskFunc(cb);
|
||||
}
|
||||
|
||||
pub fn clearTimeout(self: *Window, id: u32) void {
|
||||
@@ -340,7 +340,7 @@ pub fn cancelAnimationFrame(self: *Window, id: u32) void {
|
||||
const RequestIdleCallbackOpts = struct {
|
||||
timeout: ?u32 = null,
|
||||
};
|
||||
pub fn requestIdleCallback(self: *Window, cb: js.Function.Temp, opts_: ?RequestIdleCallbackOpts, page: *Page) !u32 {
|
||||
pub fn requestIdleCallback(self: *Window, cb: js.Function.Temp, opts_: ?RequestIdleCallbackOpts, frame: *Frame) !u32 {
|
||||
const opts = opts_ orelse RequestIdleCallbackOpts{};
|
||||
return self.scheduleCallback(cb, opts.timeout orelse 50, .{
|
||||
.mode = .idle,
|
||||
@@ -348,7 +348,7 @@ pub fn requestIdleCallback(self: *Window, cb: js.Function.Temp, opts_: ?RequestI
|
||||
.params = &.{},
|
||||
.low_priority = true,
|
||||
.name = "window.requestIdleCallback",
|
||||
}, page);
|
||||
}, frame);
|
||||
}
|
||||
|
||||
pub fn cancelIdleCallback(self: *Window, id: u32) void {
|
||||
@@ -356,13 +356,13 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void {
|
||||
sc.value.removed = true;
|
||||
}
|
||||
|
||||
pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
|
||||
pub fn reportError(self: *Window, err: js.Value, frame: *Frame) !void {
|
||||
const error_event = try ErrorEvent.initTrusted(comptime .wrap("error"), .{
|
||||
.@"error" = try err.temp(),
|
||||
.message = err.toStringSlice() catch "Unknown error",
|
||||
.bubbles = false,
|
||||
.cancelable = true,
|
||||
}, page._session);
|
||||
}, frame._session);
|
||||
|
||||
// Invoke window.onerror callback if set (per WHATWG spec, this is called
|
||||
// with 5 arguments: message, source, lineno, colno, error)
|
||||
@@ -370,7 +370,7 @@ pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
|
||||
var prevent_default = false;
|
||||
if (self._on_error) |on_error| {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
const local_func = ls.toLocal(on_error);
|
||||
@@ -392,7 +392,7 @@ pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
|
||||
event._prevent_default = prevent_default;
|
||||
// Pass null as handler: onerror was already called above with 5 args.
|
||||
// We still dispatch so that addEventListener('error', ...) listeners fire.
|
||||
try page._event_manager.dispatchDirect(self.asEventTarget(), event, null, .{
|
||||
try frame._event_manager.dispatchDirect(self.asEventTarget(), event, null, .{
|
||||
.context = "window.reportError",
|
||||
});
|
||||
|
||||
@@ -408,59 +408,57 @@ pub fn reportError(self: *Window, err: js.Value, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matchMedia(_: *const Window, query: []const u8, page: *Page) !*MediaQueryList {
|
||||
return page._factory.eventTarget(MediaQueryList{
|
||||
pub fn matchMedia(_: *const Window, query: []const u8, frame: *Frame) !*MediaQueryList {
|
||||
return frame._factory.eventTarget(MediaQueryList{
|
||||
._proto = undefined,
|
||||
._media = try page.dupeString(query),
|
||||
._media = try frame.dupeString(query),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getComputedStyle(_: *const Window, element: *Element, pseudo_element: ?[]const u8, page: *Page) !*CSSStyleProperties {
|
||||
pub fn getComputedStyle(_: *const Window, element: *Element, pseudo_element: ?[]const u8, frame: *Frame) !*CSSStyleProperties {
|
||||
if (pseudo_element) |pe| {
|
||||
if (pe.len != 0) {
|
||||
log.warn(.not_implemented, "window.GetComputedStyle", .{ .pseudo_element = pe });
|
||||
}
|
||||
}
|
||||
return CSSStyleProperties.init(element, true, page);
|
||||
return CSSStyleProperties.init(element, true, frame);
|
||||
}
|
||||
|
||||
pub fn postMessage(self: *Window, message: js.Value.Temp, target_origin: ?[]const u8, page: *Page) !void {
|
||||
pub fn postMessage(self: *Window, message: js.Value.Temp, target_origin: ?[]const u8, frame: *Frame) !void {
|
||||
// For now, we ignore targetOrigin checking and just dispatch the message
|
||||
// In a full implementation, we would validate the origin
|
||||
_ = target_origin;
|
||||
|
||||
// self = the window that will get the message
|
||||
// page = the context calling postMessage
|
||||
const target_page = self._page;
|
||||
const source_window = target_page.js.getIncumbent().window;
|
||||
const target_frame = self._frame;
|
||||
const source_window = target_frame.js.getIncumbent().window;
|
||||
|
||||
const arena = try target_page.getArena(.medium, "Window.postMessage");
|
||||
errdefer target_page.releaseArena(arena);
|
||||
const arena = try target_frame.getArena(.medium, "Window.postMessage");
|
||||
errdefer target_frame.releaseArena(arena);
|
||||
|
||||
// Origin should be the source window's origin (where the message came from)
|
||||
const origin = try source_window._location.getOrigin(&page.js.execution);
|
||||
const origin = try source_window._location.getOrigin(&frame.js.execution);
|
||||
const callback = try arena.create(PostMessageCallback);
|
||||
callback.* = .{
|
||||
.arena = arena,
|
||||
.message = message,
|
||||
.page = target_page,
|
||||
.frame = target_frame,
|
||||
.source = source_window,
|
||||
.origin = try arena.dupe(u8, origin),
|
||||
};
|
||||
|
||||
try target_page.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||
try target_frame.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
|
||||
.name = "postMessage",
|
||||
.low_priority = false,
|
||||
.finalizer = PostMessageCallback.cancelled,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn btoa(_: *const Window, input: []const u8, page: *Page) ![]const u8 {
|
||||
return @import("encoding/base64.zig").encode(page.call_arena, input);
|
||||
pub fn btoa(_: *const Window, input: []const u8, frame: *Frame) ![]const u8 {
|
||||
return @import("encoding/base64.zig").encode(frame.call_arena, input);
|
||||
}
|
||||
|
||||
pub fn atob(_: *const Window, input: []const u8, page: *Page) ![]const u8 {
|
||||
return @import("encoding/base64.zig").decode(page.call_arena, input);
|
||||
pub fn atob(_: *const Window, input: []const u8, frame: *Frame) ![]const u8 {
|
||||
return @import("encoding/base64.zig").decode(frame.call_arena, input);
|
||||
}
|
||||
|
||||
pub fn structuredClone(_: *const Window, value: js.Value) !js.Value {
|
||||
@@ -468,15 +466,15 @@ pub fn structuredClone(_: *const Window, value: js.Value) !js.Value {
|
||||
}
|
||||
|
||||
pub fn getFrame(self: *Window, idx: usize) !?*Window {
|
||||
const page = self._page;
|
||||
const frames = page.frames.items;
|
||||
const frame = self._frame;
|
||||
const frames = frame.child_frames.items;
|
||||
if (idx >= frames.len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (page.frames_sorted == false) {
|
||||
std.mem.sort(*Page, frames, {}, struct {
|
||||
fn lessThan(_: void, a: *Page, b: *Page) bool {
|
||||
if (frame.child_frames_sorted == false) {
|
||||
std.mem.sort(*Frame, frames, {}, struct {
|
||||
fn lessThan(_: void, a: *Frame, b: *Frame) bool {
|
||||
const iframe_a = a.iframe orelse return false;
|
||||
const iframe_b = b.iframe orelse return true;
|
||||
|
||||
@@ -485,13 +483,13 @@ pub fn getFrame(self: *Window, idx: usize) !?*Window {
|
||||
return (pos & 0x04) != 0; // FOLLOWING bit: b follows a
|
||||
}
|
||||
}.lessThan);
|
||||
page.frames_sorted = true;
|
||||
frame.child_frames_sorted = true;
|
||||
}
|
||||
return frames[idx].window;
|
||||
}
|
||||
|
||||
pub fn getFramesLength(self: *const Window) u32 {
|
||||
return @intCast(self._page.frames.items.len);
|
||||
return @intCast(self._frame.child_frames.items.len);
|
||||
}
|
||||
|
||||
pub fn getScrollX(self: *const Window) u32 {
|
||||
@@ -512,7 +510,7 @@ const ScrollToOpts = union(enum) {
|
||||
behavior: []const u8 = "",
|
||||
};
|
||||
};
|
||||
pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
||||
pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void {
|
||||
switch (opts) {
|
||||
.x => |x| {
|
||||
self._scroll_pos.x = @intCast(@max(x, 0));
|
||||
@@ -528,11 +526,11 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
||||
|
||||
// We dispatch scroll event asynchronously after 10ms. So we can throttle
|
||||
// them.
|
||||
try page.js.scheduler.add(
|
||||
page,
|
||||
try frame.js.scheduler.add(
|
||||
frame,
|
||||
struct {
|
||||
fn dispatch(_page: *anyopaque) anyerror!?u32 {
|
||||
const p: *Page = @ptrCast(@alignCast(_page));
|
||||
fn dispatch(_frame: *anyopaque) anyerror!?u32 {
|
||||
const p: *Frame = @ptrCast(@alignCast(_frame));
|
||||
const pos = &p.window._scroll_pos;
|
||||
// If the state isn't scroll, we can ignore safely to throttle
|
||||
// the events.
|
||||
@@ -552,11 +550,11 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
||||
.{ .low_priority = true },
|
||||
);
|
||||
// We dispatch scrollend event asynchronously after 20ms.
|
||||
try page.js.scheduler.add(
|
||||
page,
|
||||
try frame.js.scheduler.add(
|
||||
frame,
|
||||
struct {
|
||||
fn dispatch(_page: *anyopaque) anyerror!?u32 {
|
||||
const p: *Page = @ptrCast(@alignCast(_page));
|
||||
fn dispatch(_frame: *anyopaque) anyerror!?u32 {
|
||||
const p: *Frame = @ptrCast(@alignCast(_frame));
|
||||
const pos = &p.window._scroll_pos;
|
||||
// Dispatch only if the state is .end.
|
||||
// If a scroll is pending, retry in 10ms.
|
||||
@@ -580,7 +578,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn scrollBy(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
||||
pub fn scrollBy(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void {
|
||||
// The scroll is relative to the current position. So compute to new
|
||||
// absolute position.
|
||||
var absx: i32 = undefined;
|
||||
@@ -595,7 +593,7 @@ pub fn scrollBy(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
|
||||
absy = @as(i32, @intCast(self._scroll_pos.y)) + o.top;
|
||||
},
|
||||
}
|
||||
return self.scrollTo(.{ .x = absx }, absy, page);
|
||||
return self.scrollTo(.{ .x = absx }, absy, frame);
|
||||
}
|
||||
|
||||
// only exposed when the binary is built with the -Dwpt_extensions flag
|
||||
@@ -603,7 +601,7 @@ pub fn getWebDriver(_: *const Window) @import("WebDriver.zig") {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js.PromiseRejection, page: *Page) !void {
|
||||
pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js.PromiseRejection, frame: *Frame) !void {
|
||||
if (comptime IS_DEBUG) {
|
||||
log.debug(.js, "unhandled rejection", .{
|
||||
.target = "window",
|
||||
@@ -620,12 +618,12 @@ pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js.
|
||||
};
|
||||
|
||||
const target = self.asEventTarget();
|
||||
if (page._event_manager.hasDirectListeners(target, event_name, attribute_callback)) {
|
||||
if (frame._event_manager.hasDirectListeners(target, event_name, attribute_callback)) {
|
||||
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(),
|
||||
}, page._session)).asEvent();
|
||||
try page._event_manager.dispatchDirect(target, event, attribute_callback, .{ .context = "window.unhandledrejection" });
|
||||
}, frame._session)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(target, event, attribute_callback, .{ .context = "window.unhandledrejection" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,7 +637,7 @@ pub const Access = union(enum) {
|
||||
return .{ .window = accessing };
|
||||
}
|
||||
|
||||
if (callee._page.js.origin == accessing._page.js.origin) {
|
||||
if (callee._frame.js.origin == accessing._frame.js.origin) {
|
||||
// two different windows, but same origin, return the full window
|
||||
return .{ .window = accessing };
|
||||
}
|
||||
@@ -656,14 +654,14 @@ const ScheduleOpts = struct {
|
||||
animation_frame: bool = false,
|
||||
mode: ScheduleCallback.Mode = .normal,
|
||||
};
|
||||
fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: ScheduleOpts, page: *Page) !u32 {
|
||||
fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: ScheduleOpts, frame: *Frame) !u32 {
|
||||
if (self._timers.count() > 512) {
|
||||
// these are active
|
||||
return error.TooManyTimeout;
|
||||
}
|
||||
|
||||
const arena = try page.getArena(.tiny, "Window.schedule");
|
||||
errdefer page.releaseArena(arena);
|
||||
const arena = try frame.getArena(.tiny, "Window.schedule");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const timer_id = self._timer_id +% 1;
|
||||
self._timer_id = timer_id;
|
||||
@@ -674,7 +672,7 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
|
||||
persisted_params = try arena.dupe(js.Value.Temp, params);
|
||||
}
|
||||
|
||||
const gop = try self._timers.getOrPut(page.arena, timer_id);
|
||||
const gop = try self._timers.getOrPut(frame.arena, timer_id);
|
||||
if (gop.found_existing) {
|
||||
// 2^31 would have to wrap for this to happen.
|
||||
return error.TooManyTimeout;
|
||||
@@ -684,7 +682,7 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
|
||||
const callback = try arena.create(ScheduleCallback);
|
||||
callback.* = .{
|
||||
.cb = cb,
|
||||
.page = page,
|
||||
.frame = frame,
|
||||
.arena = arena,
|
||||
.mode = opts.mode,
|
||||
.name = opts.name,
|
||||
@@ -694,7 +692,7 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
|
||||
};
|
||||
gop.value_ptr.* = callback;
|
||||
|
||||
try page.js.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{
|
||||
try frame.js.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{
|
||||
.name = opts.name,
|
||||
.low_priority = opts.low_priority,
|
||||
.finalizer = ScheduleCallback.cancelled,
|
||||
@@ -716,7 +714,7 @@ const ScheduleCallback = struct {
|
||||
cb: js.Function.Temp,
|
||||
|
||||
mode: Mode,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
arena: Allocator,
|
||||
removed: bool = false,
|
||||
params: []const js.Value.Temp,
|
||||
@@ -737,13 +735,13 @@ const ScheduleCallback = struct {
|
||||
for (self.params) |param| {
|
||||
param.release();
|
||||
}
|
||||
self.page.releaseArena(self.arena);
|
||||
self.frame.releaseArena(self.arena);
|
||||
}
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
const self: *ScheduleCallback = @ptrCast(@alignCast(ctx));
|
||||
const page = self.page;
|
||||
const window = page.window;
|
||||
const frame = self.frame;
|
||||
const window = frame.window;
|
||||
|
||||
if (self.removed) {
|
||||
self.deinit();
|
||||
@@ -751,7 +749,7 @@ const ScheduleCallback = struct {
|
||||
}
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
switch (self.mode) {
|
||||
@@ -783,14 +781,14 @@ const ScheduleCallback = struct {
|
||||
};
|
||||
|
||||
const PostMessageCallback = struct {
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
source: *Window,
|
||||
arena: Allocator,
|
||||
origin: []const u8,
|
||||
message: js.Value.Temp,
|
||||
|
||||
fn deinit(self: *PostMessageCallback) void {
|
||||
self.page.releaseArena(self.arena);
|
||||
self.frame.releaseArena(self.arena);
|
||||
}
|
||||
|
||||
fn cancelled(ctx: *anyopaque) void {
|
||||
@@ -802,19 +800,19 @@ const PostMessageCallback = struct {
|
||||
const self: *PostMessageCallback = @ptrCast(@alignCast(ctx));
|
||||
defer self.deinit();
|
||||
|
||||
const page = self.page;
|
||||
const window = page.window;
|
||||
const frame = self.frame;
|
||||
const window = frame.window;
|
||||
|
||||
const event_target = window.asEventTarget();
|
||||
if (page._event_manager.hasDirectListeners(event_target, "message", window._on_message)) {
|
||||
if (frame._event_manager.hasDirectListeners(event_target, "message", window._on_message)) {
|
||||
const event = (try MessageEvent.initTrusted(comptime .wrap("message"), .{
|
||||
.data = .{ .value = self.message },
|
||||
.origin = self.origin,
|
||||
.source = self.source,
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, page._session)).asEvent();
|
||||
try page._event_manager.dispatchDirect(event_target, event, window._on_message, .{ .context = "window.postMessage" });
|
||||
}, frame._session)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(event_target, event, window._on_message, .{ .context = "window.postMessage" });
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -921,18 +919,18 @@ pub const JsApi = struct {
|
||||
pub const opener = bridge.property(null, .{ .template = false });
|
||||
|
||||
pub const alert = bridge.function(struct {
|
||||
fn alert(_: *const Window, message: ?[]const u8, page: *Page) void {
|
||||
page._session.notification.dispatch(.javascript_dialog_opening, &.{
|
||||
.url = page.url,
|
||||
fn alert(_: *const Window, message: ?[]const u8, frame: *Frame) void {
|
||||
frame._session.notification.dispatch(.javascript_dialog_opening, &.{
|
||||
.url = frame.url,
|
||||
.message = message orelse "",
|
||||
.dialog_type = "alert",
|
||||
});
|
||||
}
|
||||
}.alert, .{});
|
||||
pub const confirm = bridge.function(struct {
|
||||
fn confirm(_: *const Window, message: ?[]const u8, page: *Page) bool {
|
||||
page._session.notification.dispatch(.javascript_dialog_opening, &.{
|
||||
.url = page.url,
|
||||
fn confirm(_: *const Window, message: ?[]const u8, frame: *Frame) bool {
|
||||
frame._session.notification.dispatch(.javascript_dialog_opening, &.{
|
||||
.url = frame.url,
|
||||
.message = message orelse "",
|
||||
.dialog_type = "confirm",
|
||||
});
|
||||
@@ -940,9 +938,9 @@ pub const JsApi = struct {
|
||||
}
|
||||
}.confirm, .{});
|
||||
pub const prompt = bridge.function(struct {
|
||||
fn prompt(_: *const Window, message: ?[]const u8, _: ?[]const u8, page: *Page) ?[]const u8 {
|
||||
page._session.notification.dispatch(.javascript_dialog_opening, &.{
|
||||
.url = page.url,
|
||||
fn prompt(_: *const Window, message: ?[]const u8, _: ?[]const u8, frame: *Frame) ?[]const u8 {
|
||||
frame._session.notification.dispatch(.javascript_dialog_opening, &.{
|
||||
.url = frame.url,
|
||||
.message = message orelse "",
|
||||
.dialog_type = "prompt",
|
||||
});
|
||||
@@ -956,16 +954,16 @@ pub const JsApi = struct {
|
||||
const CrossOriginWindow = struct {
|
||||
window: *Window,
|
||||
|
||||
pub fn postMessage(self: *CrossOriginWindow, message: js.Value.Temp, target_origin: ?[]const u8, page: *Page) !void {
|
||||
return self.window.postMessage(message, target_origin, page);
|
||||
pub fn postMessage(self: *CrossOriginWindow, message: js.Value.Temp, target_origin: ?[]const u8, frame: *Frame) !void {
|
||||
return self.window.postMessage(message, target_origin, frame);
|
||||
}
|
||||
|
||||
pub fn getTop(self: *CrossOriginWindow, page: *Page) Access {
|
||||
return self.window.getParent(page);
|
||||
pub fn getTop(self: *CrossOriginWindow, frame: *Frame) Access {
|
||||
return self.window.getParent(frame);
|
||||
}
|
||||
|
||||
pub fn getParent(self: *CrossOriginWindow, page: *Page) Access {
|
||||
return self.window.getParent(page);
|
||||
pub fn getParent(self: *CrossOriginWindow, frame: *Frame) Access {
|
||||
return self.window.getParent(frame);
|
||||
}
|
||||
|
||||
pub fn getFramesLength(self: *const CrossOriginWindow) u32 {
|
||||
|
||||
@@ -23,7 +23,7 @@ const js = @import("../js/js.zig");
|
||||
const http = @import("../../network/http.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");
|
||||
|
||||
@@ -42,11 +42,11 @@ const Worker = @This();
|
||||
|
||||
// used by HttpClient when generating notification
|
||||
// Ultimately used by CDP to generate request/loader ids.
|
||||
id: u32,
|
||||
_pseudo_frame_id: u32,
|
||||
_frame_id: u32,
|
||||
_loader_id: u32,
|
||||
|
||||
_proto: *EventTarget,
|
||||
_page: *Page,
|
||||
_frame: *Frame,
|
||||
_arena: Allocator,
|
||||
_worker_scope: *WorkerGlobalScope,
|
||||
|
||||
@@ -61,32 +61,32 @@ _on_message: ?js.Function.Global = null,
|
||||
_on_messageerror: ?js.Function.Global = null,
|
||||
|
||||
pub fn init(url: []const u8, exec: *Execution) !*Worker {
|
||||
const page = switch (exec.context.global) {
|
||||
.page => |p| p,
|
||||
const frame = switch (exec.context.global) {
|
||||
.frame => |f| f,
|
||||
.worker => return error.WorkerCannotCreateWorker,
|
||||
};
|
||||
const session = page._session;
|
||||
const session = frame._session;
|
||||
|
||||
const arena = try session.getArena(.large, "Worker");
|
||||
errdefer session.releaseArena(arena);
|
||||
|
||||
const resolved_url = try URL.resolve(arena, exec.url.*, url, .{});
|
||||
const self = try session.factory.eventTargetWithAllocator(arena, Worker{
|
||||
.id = session.nextPageId(),
|
||||
._pseudo_frame_id = session.nextFrameId(),
|
||||
._arena = arena,
|
||||
._proto = undefined,
|
||||
._page = page,
|
||||
._frame = frame,
|
||||
._url = resolved_url,
|
||||
._worker_scope = undefined,
|
||||
._frame_id = session.nextFrameId(),
|
||||
._loader_id = session.nextLoaderId(),
|
||||
});
|
||||
self._worker_scope = try WorkerGlobalScope.init(self, resolved_url);
|
||||
errdefer self._worker_scope.deinit();
|
||||
try page.trackWorker(self);
|
||||
try frame.trackWorker(self);
|
||||
|
||||
if (std.mem.startsWith(u8, url, "blob:")) {
|
||||
errdefer page.removeWorker(self);
|
||||
const blob: *Blob = page.lookupBlobUrl(url) orelse {
|
||||
errdefer frame.removeWorker(self);
|
||||
const blob: *Blob = frame.lookupBlobUrl(url) orelse {
|
||||
log.warn(.js, "invalid blob", .{ .target = "worker" });
|
||||
return error.BlobNotFound;
|
||||
};
|
||||
@@ -100,8 +100,8 @@ pub fn init(url: []const u8, exec: *Execution) !*Worker {
|
||||
.url = resolved_url,
|
||||
.method = .GET,
|
||||
.headers = try http_client.newHeaders(),
|
||||
.page_id = self.id,
|
||||
.frame_id = self._pseudo_frame_id,
|
||||
.frame_id = self._frame_id,
|
||||
.loader_id = self._loader_id,
|
||||
.resource_type = .script,
|
||||
.cookie_jar = &session.cookie_jar,
|
||||
.cookie_origin = resolved_url,
|
||||
@@ -112,21 +112,21 @@ pub fn init(url: []const u8, exec: *Execution) !*Worker {
|
||||
.error_callback = httpErrorCallback,
|
||||
}) catch |err| {
|
||||
log.err(.browser, "Worker request", .{ .url = resolved_url, .err = err });
|
||||
page.removeWorker(self);
|
||||
frame.removeWorker(self);
|
||||
return err;
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
// Called from Page.deinit when the page is destroyed, so we don't need to
|
||||
// remove from the page's worker list.
|
||||
// Called from Frame.deinit when the frame is destroyed, so we don't need to
|
||||
// remove from the frame's worker list.
|
||||
pub fn deinit(self: *Worker) void {
|
||||
if (self._http_response) |res| {
|
||||
res.abort(error.Abort);
|
||||
self._http_response = null;
|
||||
}
|
||||
self._worker_scope.deinit();
|
||||
self._page._session.releaseArena(self._arena);
|
||||
self._frame._session.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn asEventTarget(self: *Worker) *EventTarget {
|
||||
@@ -215,13 +215,13 @@ 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 page = self._page;
|
||||
const session = page._session;
|
||||
const frame = self._frame;
|
||||
const session = frame._session;
|
||||
const target = self.asEventTarget();
|
||||
const on_error = self._on_error;
|
||||
|
||||
// Check if there are any listeners
|
||||
if (!page._event_manager.hasDirectListeners(target, "error", on_error)) {
|
||||
if (!frame._event_manager.hasDirectListeners(target, "error", on_error)) {
|
||||
if (error_value) |ev| ev.release();
|
||||
return;
|
||||
}
|
||||
@@ -234,7 +234,7 @@ fn _fireErrorEvent(self: *Worker, message: []const u8, error_value: ?js.Value.Te
|
||||
.cancelable = true,
|
||||
}, session);
|
||||
|
||||
try page._event_manager.dispatchDirect(target, error_event.asEvent(), on_error, .{
|
||||
try frame._event_manager.dispatchDirect(target, error_event.asEvent(), on_error, .{
|
||||
.context = "Worker.onerror",
|
||||
});
|
||||
}
|
||||
@@ -247,17 +247,17 @@ pub fn terminate(self: *Worker) void {
|
||||
}
|
||||
}
|
||||
|
||||
// Posts a message from the page to the worker.
|
||||
// Posts a message from the frame to the worker.
|
||||
pub fn postMessage(self: *Worker, data: js.Value) !void {
|
||||
try self._worker_scope.receiveMessage(data);
|
||||
}
|
||||
|
||||
// Called internally by WorkerGlobalScope when it wants to post a message to us
|
||||
pub fn receiveMessage(self: *Worker, data: js.Value) !void {
|
||||
const page = self._page;
|
||||
const frame = self._frame;
|
||||
const cloned_data = blk: {
|
||||
var ls: js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
// clones from where it currently is (the Worker context) to our Page's context
|
||||
@@ -265,8 +265,8 @@ pub fn receiveMessage(self: *Worker, data: js.Value) !void {
|
||||
break :blk cloned.temp();
|
||||
};
|
||||
|
||||
const message_arena = try page.getArena(.tiny, "Worker.receiveMessage");
|
||||
errdefer page.releaseArena(message_arena);
|
||||
const message_arena = try frame.getArena(.tiny, "Worker.receiveMessage");
|
||||
errdefer frame.releaseArena(message_arena);
|
||||
|
||||
const callback = try message_arena.create(ReceiveMessageCallback);
|
||||
callback.* = .{
|
||||
@@ -275,7 +275,7 @@ pub fn receiveMessage(self: *Worker, data: js.Value) !void {
|
||||
.arena = message_arena,
|
||||
};
|
||||
|
||||
try page.js.scheduler.add(callback, ReceiveMessageCallback.run, 0, .{
|
||||
try frame.js.scheduler.add(callback, ReceiveMessageCallback.run, 0, .{
|
||||
.name = "Worker.receiveMessage",
|
||||
.low_priority = false,
|
||||
.finalizer = ReceiveMessageCallback.cancelled,
|
||||
@@ -333,7 +333,7 @@ const ReceiveMessageCallback = struct {
|
||||
}
|
||||
|
||||
fn deinit(self: *ReceiveMessageCallback) void {
|
||||
self.worker._page._session.releaseArena(self.arena);
|
||||
self.worker._frame._session.releaseArena(self.arena);
|
||||
}
|
||||
|
||||
fn run(ctx: *anyopaque) !?u32 {
|
||||
@@ -341,28 +341,28 @@ const ReceiveMessageCallback = struct {
|
||||
defer self.deinit();
|
||||
|
||||
const worker = self.worker;
|
||||
const page = worker._page;
|
||||
const frame = worker._frame;
|
||||
const target = worker.asEventTarget();
|
||||
|
||||
// If data is null, structured clone failed - fire messageerror
|
||||
const data = self.data catch |err| {
|
||||
const on_messageerror = worker._on_messageerror;
|
||||
if (!page._event_manager.hasDirectListeners(target, "messageerror", on_messageerror)) {
|
||||
if (!frame._event_manager.hasDirectListeners(target, "messageerror", on_messageerror)) {
|
||||
return null;
|
||||
}
|
||||
const event = (try MessageEvent.initTrusted(comptime .wrap("messageerror"), .{
|
||||
.data = .{ .string = @errorName(err) },
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, page._session)).asEvent();
|
||||
try page._event_manager.dispatchDirect(target, event, on_messageerror, .{ .context = "Worker.messageerror" });
|
||||
}, frame._session)).asEvent();
|
||||
try frame._event_manager.dispatchDirect(target, event, on_messageerror, .{ .context = "Worker.messageerror" });
|
||||
return null;
|
||||
};
|
||||
|
||||
const on_message = worker._on_message;
|
||||
|
||||
// Check if there are any listeners before creating the event
|
||||
if (!page._event_manager.hasDirectListeners(target, "message", on_message)) {
|
||||
if (!frame._event_manager.hasDirectListeners(target, "message", on_message)) {
|
||||
data.release();
|
||||
return null;
|
||||
}
|
||||
@@ -371,9 +371,9 @@ const ReceiveMessageCallback = struct {
|
||||
.data = .{ .value = data },
|
||||
.bubbles = false,
|
||||
.cancelable = false,
|
||||
}, page._session)).asEvent();
|
||||
}, frame._session)).asEvent();
|
||||
|
||||
try page._event_manager.dispatchDirect(target, event, on_message, .{ .context = "Worker.receiveMessage" });
|
||||
try frame._event_manager.dispatchDirect(target, event, on_message, .{ .context = "Worker.receiveMessage" });
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -52,12 +52,12 @@ _identity: JS.Identity = .{},
|
||||
arena: Allocator,
|
||||
call_arena: Allocator,
|
||||
url: [:0]const u8,
|
||||
buf: [1024]u8 = undefined, // same size as page.buf
|
||||
buf: [1024]u8 = undefined, // same size as frame.buf
|
||||
// Document charset (matches Page.charset). Workers default to UTF-8.
|
||||
charset: []const u8 = "UTF-8",
|
||||
js: *JS.Context,
|
||||
|
||||
// Reference back to the Worker object (for postMessage to page)
|
||||
// Reference back to the Worker object (for postMessage to frame)
|
||||
_worker: *Worker,
|
||||
|
||||
// Event management for non-DOM targets in worker context
|
||||
@@ -76,7 +76,7 @@ _on_messageerror: ?JS.Function.Global = null,
|
||||
|
||||
pub fn init(worker: *Worker, url: [:0]const u8) !*WorkerGlobalScope {
|
||||
const arena = worker._arena;
|
||||
const session = worker._page._session;
|
||||
const session = worker._frame._session;
|
||||
const factory = &session.factory;
|
||||
|
||||
const call_arena = try session.getArena(.small, "WorkerGlobalScope.call_arena");
|
||||
@@ -187,7 +187,7 @@ pub fn setOnMessageError(self: *WorkerGlobalScope, setter: ?FunctionSetter) void
|
||||
self._on_messageerror = getFunctionFromSetter(setter);
|
||||
}
|
||||
|
||||
// Posts a message from the worker back to the page.
|
||||
// Posts a message from the worker back to the frame.
|
||||
// The message is cloned via structured clone and dispatched on the Worker object.
|
||||
pub fn postMessage(self: *WorkerGlobalScope, data: JS.Value) !void {
|
||||
try self._worker.receiveMessage(data);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
const js = @import("../js/js.zig");
|
||||
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Node = @import("Node.zig");
|
||||
const dump = @import("../dump.zig");
|
||||
|
||||
@@ -32,13 +32,13 @@ pub fn init() XMLSerializer {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn serializeToString(self: *const XMLSerializer, node: *Node, page: *Page) ![]const u8 {
|
||||
pub fn serializeToString(self: *const XMLSerializer, node: *Node, frame: *Frame) ![]const u8 {
|
||||
_ = self;
|
||||
var buf = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
var buf = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
if (node.is(Node.Document)) |doc| {
|
||||
try dump.root(doc, .{ .shadow = .skip }, &buf.writer, page);
|
||||
try dump.root(doc, .{ .shadow = .skip }, &buf.writer, frame);
|
||||
} else {
|
||||
try dump.deep(node, .{ .shadow = .skip }, &buf.writer, page);
|
||||
try dump.deep(node, .{ .shadow = .skip }, &buf.writer, frame);
|
||||
}
|
||||
// Not sure about this trim. But `dump` is meant to display relatively
|
||||
// pretty HTML, so it does include newlines, which can result in a trailing
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
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;
|
||||
@@ -35,7 +35,7 @@ const PlayState = enum {
|
||||
};
|
||||
|
||||
_rc: lp.RC(u32) = .{},
|
||||
_page: *Page,
|
||||
_frame: *Frame,
|
||||
_arena: Allocator,
|
||||
|
||||
_effect: ?js.Object.Global = null,
|
||||
@@ -51,13 +51,13 @@ _playState: PlayState = .idle,
|
||||
// .running => .finished after 10ms when update() is callback.
|
||||
//
|
||||
// TODO add support for effect and timeline
|
||||
pub fn init(page: *Page) !*Animation {
|
||||
const arena = try page.getArena(.tiny, "Animation");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(frame: *Frame) !*Animation {
|
||||
const arena = try frame.getArena(.tiny, "Animation");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const self = try arena.create(Animation);
|
||||
self.* = .{
|
||||
._page = page,
|
||||
._frame = frame,
|
||||
._arena = arena,
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ pub fn acquireRef(self: *Animation) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn play(self: *Animation, page: *Page) !void {
|
||||
pub fn play(self: *Animation, frame: *Frame) !void {
|
||||
if (self._playState == .running) {
|
||||
return;
|
||||
}
|
||||
@@ -86,7 +86,7 @@ pub fn play(self: *Animation, page: *Page) !void {
|
||||
|
||||
// Schedule the transition from .running => .finished in 10ms.
|
||||
self.acquireRef();
|
||||
try page.js.scheduler.add(
|
||||
try frame.js.scheduler.add(
|
||||
self,
|
||||
Animation.update,
|
||||
10,
|
||||
@@ -105,7 +105,7 @@ pub fn cancel(self: *Animation) void {
|
||||
self._playState = .idle;
|
||||
}
|
||||
|
||||
pub fn finish(self: *Animation, page: *Page) void {
|
||||
pub fn finish(self: *Animation, frame: *Frame) void {
|
||||
if (self._playState == .finished) {
|
||||
return;
|
||||
}
|
||||
@@ -114,11 +114,11 @@ pub fn finish(self: *Animation, page: *Page) void {
|
||||
|
||||
// resolve finished
|
||||
if (self._finished_resolver) |resolver| {
|
||||
page.js.local.?.toLocal(resolver).resolve("Animation.getFinished", self);
|
||||
frame.js.local.?.toLocal(resolver).resolve("Animation.getFinished", self);
|
||||
}
|
||||
// call onfinish
|
||||
if (self._onFinish) |func| {
|
||||
page.js.local.?.toLocal(func).call(void, .{}) catch |err| {
|
||||
frame.js.local.?.toLocal(func).call(void, .{}) catch |err| {
|
||||
log.warn(.js, "Animation._onFinish", .{ .err = err });
|
||||
};
|
||||
}
|
||||
@@ -128,24 +128,24 @@ pub fn reverse(_: *Animation) void {
|
||||
log.warn(.not_implemented, "Animation.reverse", .{});
|
||||
}
|
||||
|
||||
pub fn getFinished(self: *Animation, page: *Page) !js.Promise {
|
||||
pub fn getFinished(self: *Animation, frame: *Frame) !js.Promise {
|
||||
if (self._finished_resolver == null) {
|
||||
const resolver = page.js.local.?.createPromiseResolver();
|
||||
const resolver = frame.js.local.?.createPromiseResolver();
|
||||
self._finished_resolver = try resolver.persist();
|
||||
return resolver.promise();
|
||||
}
|
||||
return page.js.toLocal(self._finished_resolver).?.promise();
|
||||
return frame.js.toLocal(self._finished_resolver).?.promise();
|
||||
}
|
||||
|
||||
// The ready promise is immediately resolved.
|
||||
pub fn getReady(self: *Animation, page: *Page) !js.Promise {
|
||||
pub fn getReady(self: *Animation, frame: *Frame) !js.Promise {
|
||||
if (self._ready_resolver == null) {
|
||||
const resolver = page.js.local.?.createPromiseResolver();
|
||||
const resolver = frame.js.local.?.createPromiseResolver();
|
||||
resolver.resolve("Animation.getReady", self);
|
||||
self._ready_resolver = try resolver.persist();
|
||||
return resolver.promise();
|
||||
}
|
||||
return page.js.toLocal(self._ready_resolver).?.promise();
|
||||
return frame.js.toLocal(self._ready_resolver).?.promise();
|
||||
}
|
||||
|
||||
pub fn getEffect(self: *const Animation) ?js.Object.Global {
|
||||
@@ -168,7 +168,7 @@ pub fn getStartTime(self: *const Animation) ?f64 {
|
||||
return self._startTime;
|
||||
}
|
||||
|
||||
pub fn setStartTime(self: *Animation, value: ?f64, page: *Page) !void {
|
||||
pub fn setStartTime(self: *Animation, value: ?f64, frame: *Frame) !void {
|
||||
self._startTime = value;
|
||||
|
||||
// if the startTime is null, don't play the animation.
|
||||
@@ -176,7 +176,7 @@ pub fn setStartTime(self: *Animation, value: ?f64, page: *Page) !void {
|
||||
return;
|
||||
}
|
||||
|
||||
return self.play(page);
|
||||
return self.play(frame);
|
||||
}
|
||||
|
||||
pub fn getOnFinish(self: *const Animation) ?js.Function.Temp {
|
||||
@@ -193,7 +193,7 @@ fn update(ctx: *anyopaque) !?u32 {
|
||||
self._playState = .finished;
|
||||
|
||||
var ls: js.Local.Scope = undefined;
|
||||
self._page.js.localScope(&ls);
|
||||
self._frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
|
||||
// resolve finished
|
||||
@@ -211,7 +211,7 @@ fn update(ctx: *anyopaque) !?u32 {
|
||||
}
|
||||
|
||||
// No future change scheduled, set the object weak for garbage collection.
|
||||
self.releaseRef(self._page._session);
|
||||
self.releaseRef(self._frame._session);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
|
||||
const color = @import("../../color.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Canvas = @import("../element/html/Canvas.zig");
|
||||
const ImageData = @import("../ImageData.zig");
|
||||
@@ -41,8 +41,8 @@ pub fn getCanvas(self: *const CanvasRenderingContext2D) *Canvas {
|
||||
return self._canvas;
|
||||
}
|
||||
|
||||
pub fn getFillStyle(self: *const CanvasRenderingContext2D, page: *Page) ![]const u8 {
|
||||
var w = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
pub fn getFillStyle(self: *const CanvasRenderingContext2D, frame: *Frame) ![]const u8 {
|
||||
var w = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
try self._fill_style.format(&w.writer);
|
||||
return w.written();
|
||||
}
|
||||
@@ -67,15 +67,15 @@ pub fn createImageData(
|
||||
maybe_height: ?u32,
|
||||
/// Can be used if width and height provided.
|
||||
maybe_settings: ?ImageData.ConstructorSettings,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*ImageData {
|
||||
switch (width_or_image_data) {
|
||||
.width => |width| {
|
||||
const height = maybe_height orelse return error.TypeError;
|
||||
return ImageData.init(width, height, maybe_settings, page);
|
||||
return ImageData.init(width, height, maybe_settings, frame);
|
||||
},
|
||||
.image_data => |image_data| {
|
||||
return ImageData.init(image_data._width, image_data._height, null, page);
|
||||
return ImageData.init(image_data._width, image_data._height, null, frame);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -88,12 +88,12 @@ pub fn getImageData(
|
||||
_: i32, // sy
|
||||
sw: i32,
|
||||
sh: i32,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*ImageData {
|
||||
if (sw <= 0 or sh <= 0) {
|
||||
return error.IndexSizeError;
|
||||
}
|
||||
return ImageData.init(@intCast(sw), @intCast(sh), null, page);
|
||||
return ImageData.init(@intCast(sw), @intCast(sh), null, frame);
|
||||
}
|
||||
|
||||
pub fn save(_: *CanvasRenderingContext2D) void {}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Blob = @import("../Blob.zig");
|
||||
const OffscreenCanvasRenderingContext2D = @import("OffscreenCanvasRenderingContext2D.zig");
|
||||
@@ -37,8 +37,8 @@ const DrawingContext = union(enum) {
|
||||
@"2d": *OffscreenCanvasRenderingContext2D,
|
||||
};
|
||||
|
||||
pub fn constructor(width: u32, height: u32, page: *Page) !*OffscreenCanvas {
|
||||
return page._factory.create(OffscreenCanvas{
|
||||
pub fn constructor(width: u32, height: u32, frame: *Frame) !*OffscreenCanvas {
|
||||
return frame._factory.create(OffscreenCanvas{
|
||||
._width = width,
|
||||
._height = height,
|
||||
});
|
||||
@@ -60,9 +60,9 @@ pub fn setHeight(self: *OffscreenCanvas, value: u32) void {
|
||||
self._height = value;
|
||||
}
|
||||
|
||||
pub fn getContext(_: *OffscreenCanvas, context_type: []const u8, page: *Page) !?DrawingContext {
|
||||
pub fn getContext(_: *OffscreenCanvas, context_type: []const u8, frame: *Frame) !?DrawingContext {
|
||||
if (std.mem.eql(u8, context_type, "2d")) {
|
||||
const ctx = try page._factory.create(OffscreenCanvasRenderingContext2D{});
|
||||
const ctx = try frame._factory.create(OffscreenCanvasRenderingContext2D{});
|
||||
return .{ .@"2d" = ctx };
|
||||
}
|
||||
|
||||
@@ -71,9 +71,9 @@ pub fn getContext(_: *OffscreenCanvas, context_type: []const u8, page: *Page) !?
|
||||
|
||||
/// 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, page: *Page) !js.Promise {
|
||||
const blob = try Blob.init(null, null, page);
|
||||
return page.js.local.?.resolvePromise(blob);
|
||||
pub fn convertToBlob(_: *OffscreenCanvas, frame: *Frame) !js.Promise {
|
||||
const blob = try Blob.init(null, null, frame._session);
|
||||
return frame.js.local.?.resolvePromise(blob);
|
||||
}
|
||||
|
||||
/// Returns an ImageBitmap with the rendered content (stub).
|
||||
|
||||
@@ -20,7 +20,7 @@ const std = @import("std");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const color = @import("../../color.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const ImageData = @import("../ImageData.zig");
|
||||
|
||||
@@ -32,8 +32,8 @@ const OffscreenCanvasRenderingContext2D = @This();
|
||||
/// TODO: Add support for `CanvasGradient` and `CanvasPattern`.
|
||||
_fill_style: color.RGBA = color.RGBA.Named.black,
|
||||
|
||||
pub fn getFillStyle(self: *const OffscreenCanvasRenderingContext2D, page: *Page) ![]const u8 {
|
||||
var w = std.Io.Writer.Allocating.init(page.call_arena);
|
||||
pub fn getFillStyle(self: *const OffscreenCanvasRenderingContext2D, frame: *Frame) ![]const u8 {
|
||||
var w = std.Io.Writer.Allocating.init(frame.call_arena);
|
||||
try self._fill_style.format(&w.writer);
|
||||
return w.written();
|
||||
}
|
||||
@@ -58,15 +58,15 @@ pub fn createImageData(
|
||||
maybe_height: ?u32,
|
||||
/// Can be used if width and height provided.
|
||||
maybe_settings: ?ImageData.ConstructorSettings,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*ImageData {
|
||||
switch (width_or_image_data) {
|
||||
.width => |width| {
|
||||
const height = maybe_height orelse return error.TypeError;
|
||||
return ImageData.init(width, height, maybe_settings, page);
|
||||
return ImageData.init(width, height, maybe_settings, frame);
|
||||
},
|
||||
.image_data => |image_data| {
|
||||
return ImageData.init(image_data._width, image_data._height, null, page);
|
||||
return ImageData.init(image_data._width, image_data._height, null, frame);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -79,12 +79,12 @@ pub fn getImageData(
|
||||
_: i32, // sy
|
||||
sw: i32,
|
||||
sh: i32,
|
||||
page: *Page,
|
||||
frame: *Frame,
|
||||
) !*ImageData {
|
||||
if (sw <= 0 or sh <= 0) {
|
||||
return error.IndexSizeError;
|
||||
}
|
||||
return ImageData.init(@intCast(sw), @intCast(sh), null, page);
|
||||
return ImageData.init(@intCast(sw), @intCast(sh), null, frame);
|
||||
}
|
||||
|
||||
pub fn save(_: *OffscreenCanvasRenderingContext2D) void {}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
@@ -168,16 +168,16 @@ pub fn getParameter(_: *const WebGLRenderingContext, pname: u32) []const u8 {
|
||||
}
|
||||
|
||||
/// Enables a WebGL extension.
|
||||
pub fn getExtension(_: *const WebGLRenderingContext, name: []const u8, page: *Page) !?Extension {
|
||||
pub fn getExtension(_: *const WebGLRenderingContext, name: []const u8, frame: *Frame) !?Extension {
|
||||
const tag = Extension.find(name) orelse return null;
|
||||
|
||||
return switch (tag) {
|
||||
.WEBGL_debug_renderer_info => {
|
||||
const info = try page._factory.create(Extension.Type.WEBGL_debug_renderer_info{});
|
||||
const info = try frame._factory.create(Extension.Type.WEBGL_debug_renderer_info{});
|
||||
return .{ .WEBGL_debug_renderer_info = info };
|
||||
},
|
||||
.WEBGL_lose_context => {
|
||||
const ctx = try page._factory.create(Extension.Type.WEBGL_lose_context{});
|
||||
const ctx = try frame._factory.create(Extension.Type.WEBGL_lose_context{});
|
||||
return .{ .WEBGL_lose_context = ctx };
|
||||
},
|
||||
inline else => |comptime_enum| @unionInit(Extension, @tagName(comptime_enum), {}),
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const CData = @import("../CData.zig");
|
||||
|
||||
@@ -25,8 +25,8 @@ const Comment = @This();
|
||||
|
||||
_proto: *CData,
|
||||
|
||||
pub fn init(str: ?js.NullableString, page: *Page) !*Comment {
|
||||
const node = try page.createComment(if (str) |s| s.value else "");
|
||||
pub fn init(str: ?js.NullableString, frame: *Frame) !*Comment {
|
||||
const node = try frame.createComment(if (str) |s| s.value else "");
|
||||
return node.as(Comment);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const CData = @import("../CData.zig");
|
||||
|
||||
const Text = @This();
|
||||
|
||||
_proto: *CData,
|
||||
|
||||
pub fn init(str: ?js.NullableString, page: *Page) !*Text {
|
||||
const node = try page.createTextNode(if (str) |s| s.value else "");
|
||||
pub fn init(str: ?js.NullableString, frame: *Frame) !*Text {
|
||||
const node = try frame.createTextNode(if (str) |s| s.value else "");
|
||||
return node.as(Text);
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ pub fn getWholeText(self: *Text) []const u8 {
|
||||
return self._proto._data.str();
|
||||
}
|
||||
|
||||
pub fn splitText(self: *Text, offset: usize, page: *Page) !*Text {
|
||||
pub fn splitText(self: *Text, offset: usize, frame: *Frame) !*Text {
|
||||
const data = self._proto._data.str();
|
||||
|
||||
const byte_offset = CData.utf16OffsetToUtf8(data, offset) catch return error.IndexSizeError;
|
||||
|
||||
const new_data = data[byte_offset..];
|
||||
const new_node = try page.createTextNode(new_data);
|
||||
const new_node = try frame.createTextNode(new_data);
|
||||
const new_text = new_node.as(Text);
|
||||
|
||||
const node = self._proto.asNode();
|
||||
@@ -48,11 +48,11 @@ pub fn splitText(self: *Text, offset: usize, page: *Page) !*Text {
|
||||
// then truncate original node (step 8).
|
||||
if (node.parentNode()) |parent| {
|
||||
const next_sibling = node.nextSibling();
|
||||
_ = try parent.insertBefore(new_node, next_sibling, page);
|
||||
_ = try parent.insertBefore(new_node, next_sibling, frame);
|
||||
|
||||
// splitText-specific range updates (steps 7b-7e)
|
||||
if (parent.getChildIndex(node)) |node_index| {
|
||||
page.updateRangesForSplitText(node, new_node, @intCast(offset), parent, node_index);
|
||||
frame.updateRangesForSplitText(node, new_node, @intCast(offset), parent, node_index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn splitText(self: *Text, offset: usize, page: *Page) !*Text {
|
||||
// Use replaceData instead of setData so live range updates fire
|
||||
// (matters for detached text nodes where steps 7b-7e were skipped).
|
||||
const length = self._proto.getLength();
|
||||
try self._proto.replaceData(offset, length - offset, "", page);
|
||||
try self._proto.replaceData(offset, length - offset, "", frame);
|
||||
|
||||
return new_text;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 GenericIterator = @import("iterator.zig").Entry;
|
||||
|
||||
@@ -39,9 +39,9 @@ pub const KeyIterator = GenericIterator(Iterator, "0");
|
||||
pub const ValueIterator = GenericIterator(Iterator, "1");
|
||||
pub const EntryIterator = GenericIterator(Iterator, null);
|
||||
|
||||
pub fn init(node: *Node, page: *Page) !*ChildNodes {
|
||||
const arena = try page.getArena(.small, "ChildNodes");
|
||||
errdefer page.releaseArena(arena);
|
||||
pub fn init(node: *Node, frame: *Frame) !*ChildNodes {
|
||||
const arena = try frame.getArena(.small, "ChildNodes");
|
||||
errdefer frame.releaseArena(arena);
|
||||
|
||||
const self = try arena.create(ChildNodes);
|
||||
self.* = .{
|
||||
@@ -50,7 +50,7 @@ pub fn init(node: *Node, page: *Page) !*ChildNodes {
|
||||
._last_index = 0,
|
||||
._last_node = null,
|
||||
._last_length = null,
|
||||
._cached_version = page.version,
|
||||
._cached_version = frame.version,
|
||||
};
|
||||
return self;
|
||||
}
|
||||
@@ -59,8 +59,8 @@ pub fn deinit(self: *const ChildNodes, session: *Session) void {
|
||||
session.releaseArena(self._arena);
|
||||
}
|
||||
|
||||
pub fn length(self: *ChildNodes, page: *Page) !u32 {
|
||||
if (self.versionCheck(page)) {
|
||||
pub fn length(self: *ChildNodes, frame: *Frame) !u32 {
|
||||
if (self.versionCheck(frame)) {
|
||||
if (self._last_length) |cached_length| {
|
||||
return cached_length;
|
||||
}
|
||||
@@ -73,8 +73,8 @@ pub fn length(self: *ChildNodes, page: *Page) !u32 {
|
||||
return len;
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *ChildNodes, index: usize, page: *Page) !?*Node {
|
||||
_ = self.versionCheck(page);
|
||||
pub fn getAtIndex(self: *ChildNodes, index: usize, frame: *Frame) !?*Node {
|
||||
_ = self.versionCheck(frame);
|
||||
|
||||
var current = self._last_index;
|
||||
var node: ?*std.DoublyLinkedList.Node = null;
|
||||
@@ -102,20 +102,20 @@ pub fn first(self: *const ChildNodes) ?*std.DoublyLinkedList.Node {
|
||||
return &(self._node._children orelse return null).first()._child_link;
|
||||
}
|
||||
|
||||
pub fn keys(self: *ChildNodes, page: *Page) !*KeyIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn keys(self: *ChildNodes, frame: *Frame) !*KeyIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn values(self: *ChildNodes, page: *Page) !*ValueIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn values(self: *ChildNodes, frame: *Frame) !*ValueIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn entries(self: *ChildNodes, page: *Page) !*EntryIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn entries(self: *ChildNodes, frame: *Frame) !*EntryIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
fn versionCheck(self: *ChildNodes, page: *Page) bool {
|
||||
const current = page.version;
|
||||
fn versionCheck(self: *ChildNodes, frame: *Frame) bool {
|
||||
const current = frame.version;
|
||||
if (current == self._cached_version) {
|
||||
return true;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ fn versionCheck(self: *ChildNodes, page: *Page) bool {
|
||||
}
|
||||
|
||||
const NodeList = @import("NodeList.zig");
|
||||
pub fn runtimeGenericWrap(self: *ChildNodes, _: *const Page) !*NodeList {
|
||||
pub fn runtimeGenericWrap(self: *ChildNodes, _: *const Frame) !*NodeList {
|
||||
const nl = try self._arena.create(NodeList);
|
||||
nl.* = .{
|
||||
._data = .{ .child_nodes = self },
|
||||
@@ -141,9 +141,9 @@ const Iterator = struct {
|
||||
|
||||
const Entry = struct { u32, *Node };
|
||||
|
||||
pub fn next(self: *Iterator, page: *const Page) !?Entry {
|
||||
pub fn next(self: *Iterator, frame: *const Frame) !?Entry {
|
||||
const index = self.index;
|
||||
const node = try self.list.getAtIndex(index, page) orelse return null;
|
||||
const node = try self.list.getAtIndex(index, frame) orelse return null;
|
||||
self.index = index + 1;
|
||||
return .{ index, node };
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 Element = @import("../Element.zig");
|
||||
const GenericIterator = @import("iterator.zig").Entry;
|
||||
|
||||
@@ -44,16 +44,16 @@ const Lookup = std.StringArrayHashMapUnmanaged(void);
|
||||
|
||||
const WHITESPACE = " \t\n\r\x0C";
|
||||
|
||||
pub fn length(self: *const DOMTokenList, page: *Page) !u32 {
|
||||
const tokens = try self.getTokens(page.call_arena);
|
||||
pub fn length(self: *const DOMTokenList, frame: *Frame) !u32 {
|
||||
const tokens = try self.getTokens(frame.call_arena);
|
||||
return @intCast(tokens.count());
|
||||
}
|
||||
|
||||
// TODO: soooo..inefficient
|
||||
pub fn item(self: *const DOMTokenList, index: usize, page: *Page) !?[]const u8 {
|
||||
pub fn item(self: *const DOMTokenList, index: usize, frame: *Frame) !?[]const u8 {
|
||||
var i: usize = 0;
|
||||
|
||||
const allocator = page.call_arena;
|
||||
const allocator = frame.call_arena;
|
||||
var seen: std.StringArrayHashMapUnmanaged(void) = .empty;
|
||||
|
||||
var it = std.mem.tokenizeAny(u8, self.getValue(), WHITESPACE);
|
||||
@@ -79,12 +79,12 @@ pub fn contains(self: *const DOMTokenList, search: []const u8) !bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn add(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !void {
|
||||
pub fn add(self: *DOMTokenList, tokens: []const []const u8, frame: *Frame) !void {
|
||||
for (tokens) |token| {
|
||||
try validateToken(token);
|
||||
}
|
||||
|
||||
const allocator = page.call_arena;
|
||||
const allocator = frame.call_arena;
|
||||
var lookup = try self.getTokens(allocator);
|
||||
try lookup.ensureUnusedCapacity(allocator, tokens.len);
|
||||
|
||||
@@ -92,22 +92,22 @@ pub fn add(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !void {
|
||||
try lookup.put(allocator, token, {});
|
||||
}
|
||||
|
||||
try self.updateAttribute(lookup, page);
|
||||
try self.updateAttribute(lookup, frame);
|
||||
}
|
||||
|
||||
pub fn remove(self: *DOMTokenList, tokens: []const []const u8, page: *Page) !void {
|
||||
pub fn remove(self: *DOMTokenList, tokens: []const []const u8, frame: *Frame) !void {
|
||||
for (tokens) |token| {
|
||||
try validateToken(token);
|
||||
}
|
||||
|
||||
var lookup = try self.getTokens(page.call_arena);
|
||||
var lookup = try self.getTokens(frame.call_arena);
|
||||
for (tokens) |token| {
|
||||
_ = lookup.orderedRemove(token);
|
||||
}
|
||||
try self.updateAttribute(lookup, page);
|
||||
try self.updateAttribute(lookup, frame);
|
||||
}
|
||||
|
||||
pub fn toggle(self: *DOMTokenList, token: []const u8, force: ?bool, page: *Page) !bool {
|
||||
pub fn toggle(self: *DOMTokenList, token: []const u8, force: ?bool, frame: *Frame) !bool {
|
||||
try validateToken(token);
|
||||
|
||||
const has_token = try self.contains(token);
|
||||
@@ -116,30 +116,30 @@ pub fn toggle(self: *DOMTokenList, token: []const u8, force: ?bool, page: *Page)
|
||||
if (f) {
|
||||
if (!has_token) {
|
||||
const tokens_to_add = [_][]const u8{token};
|
||||
try self.add(&tokens_to_add, page);
|
||||
try self.add(&tokens_to_add, frame);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (has_token) {
|
||||
const tokens_to_remove = [_][]const u8{token};
|
||||
try self.remove(&tokens_to_remove, page);
|
||||
try self.remove(&tokens_to_remove, frame);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (has_token) {
|
||||
const tokens_to_remove = [_][]const u8{token};
|
||||
try self.remove(tokens_to_remove[0..], page);
|
||||
try self.remove(tokens_to_remove[0..], frame);
|
||||
return false;
|
||||
} else {
|
||||
const tokens_to_add = [_][]const u8{token};
|
||||
try self.add(tokens_to_add[0..], page);
|
||||
try self.add(tokens_to_add[0..], frame);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8, page: *Page) !bool {
|
||||
pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8, frame: *Frame) !bool {
|
||||
// Validate in spec order: both empty first, then both whitespace
|
||||
if (old_token.len == 0 or new_token.len == 0) {
|
||||
return error.SyntaxError;
|
||||
@@ -151,8 +151,8 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8
|
||||
return error.InvalidCharacterError;
|
||||
}
|
||||
|
||||
const allocator = page.call_arena;
|
||||
var lookup = try self.getTokens(page.call_arena);
|
||||
const allocator = frame.call_arena;
|
||||
var lookup = try self.getTokens(frame.call_arena);
|
||||
|
||||
// Check if old_token exists
|
||||
if (!lookup.contains(old_token)) {
|
||||
@@ -161,7 +161,7 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8
|
||||
|
||||
// If replacing with the same token, still need to trigger mutation
|
||||
if (std.mem.eql(u8, new_token, old_token)) {
|
||||
try self.updateAttribute(lookup, page);
|
||||
try self.updateAttribute(lookup, frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8
|
||||
try new_lookup.put(allocator, token, {});
|
||||
}
|
||||
|
||||
try self.updateAttribute(new_lookup, page);
|
||||
try self.updateAttribute(new_lookup, frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,26 +200,26 @@ pub fn getValue(self: *const DOMTokenList) []const u8 {
|
||||
return self._element.getAttributeSafe(self._attribute_name) orelse "";
|
||||
}
|
||||
|
||||
pub fn setValue(self: *DOMTokenList, value: String, page: *Page) !void {
|
||||
try self._element.setAttribute(self._attribute_name, value, page);
|
||||
pub fn setValue(self: *DOMTokenList, value: String, frame: *Frame) !void {
|
||||
try self._element.setAttribute(self._attribute_name, value, frame);
|
||||
}
|
||||
|
||||
pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn keys(self: *DOMTokenList, frame: *Frame) !*KeyIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn values(self: *DOMTokenList, page: *Page) !*ValueIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn values(self: *DOMTokenList, frame: *Frame) !*ValueIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn entries(self: *DOMTokenList, page: *Page) !*EntryIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn entries(self: *DOMTokenList, frame: *Frame) !*EntryIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, page: *Page) !void {
|
||||
pub fn forEach(self: *DOMTokenList, cb_: js.Function, js_this_: ?js.Object, frame: *Frame) !void {
|
||||
const cb = if (js_this_) |js_this| try cb_.withThis(js_this) else cb_;
|
||||
|
||||
const allocator = page.call_arena;
|
||||
const allocator = frame.call_arena;
|
||||
|
||||
var i: i32 = 0;
|
||||
var seen: std.StringArrayHashMapUnmanaged(void) = .empty;
|
||||
@@ -264,16 +264,16 @@ fn validateToken(token: []const u8) !void {
|
||||
}
|
||||
}
|
||||
|
||||
fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void {
|
||||
fn updateAttribute(self: *DOMTokenList, tokens: Lookup, frame: *Frame) !void {
|
||||
if (tokens.count() > 0) {
|
||||
const joined = try std.mem.join(page.call_arena, " ", tokens.keys());
|
||||
return self._element.setAttribute(self._attribute_name, .wrap(joined), page);
|
||||
const joined = try std.mem.join(frame.call_arena, " ", tokens.keys());
|
||||
return self._element.setAttribute(self._attribute_name, .wrap(joined), frame);
|
||||
}
|
||||
|
||||
// Only remove attribute if it didn't exist before (was null)
|
||||
// If it existed (even as ""), set it to "" to preserve its existence
|
||||
if (self._element.hasAttributeSafe(self._attribute_name)) {
|
||||
try self._element.setAttribute(self._attribute_name, .wrap(""), page);
|
||||
try self._element.setAttribute(self._attribute_name, .wrap(""), frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,9 +283,9 @@ const Iterator = struct {
|
||||
|
||||
const Entry = struct { u32, []const u8 };
|
||||
|
||||
pub fn next(self: *Iterator, page: *Page) !?Entry {
|
||||
pub fn next(self: *Iterator, frame: *Frame) !?Entry {
|
||||
const index = self.index;
|
||||
const node = try self.list.item(index, page) orelse return null;
|
||||
const node = try self.list.item(index, frame) orelse return null;
|
||||
self.index = index + 1;
|
||||
return .{ index, node };
|
||||
}
|
||||
@@ -303,11 +303,11 @@ pub const JsApi = struct {
|
||||
|
||||
pub const length = bridge.accessor(DOMTokenList.length, null, .{});
|
||||
pub const item = bridge.function(_item, .{});
|
||||
fn _item(self: *const DOMTokenList, index: i32, page: *Page) !?[]const u8 {
|
||||
fn _item(self: *const DOMTokenList, index: i32, frame: *Frame) !?[]const u8 {
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return self.item(@intCast(index), page);
|
||||
return self.item(@intCast(index), frame);
|
||||
}
|
||||
|
||||
pub const contains = bridge.function(DOMTokenList.contains, .{ .dom_exception = true });
|
||||
|
||||
@@ -20,7 +20,7 @@ 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 Node = @import("../Node.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const TreeWalker = @import("../TreeWalker.zig");
|
||||
@@ -33,18 +33,18 @@ _last_index: usize,
|
||||
_last_length: ?u32,
|
||||
_cached_version: usize,
|
||||
|
||||
pub fn init(root: *Node, page: *Page) HTMLAllCollection {
|
||||
pub fn init(root: *Node, frame: *Frame) HTMLAllCollection {
|
||||
return .{
|
||||
._last_index = 0,
|
||||
._last_length = null,
|
||||
._tw = TreeWalker.FullExcludeSelf.init(root, .{}),
|
||||
._cached_version = page.version,
|
||||
._cached_version = frame.version,
|
||||
};
|
||||
}
|
||||
|
||||
fn versionCheck(self: *HTMLAllCollection, page: *const Page) bool {
|
||||
if (self._cached_version != page.version) {
|
||||
self._cached_version = page.version;
|
||||
fn versionCheck(self: *HTMLAllCollection, frame: *const Frame) bool {
|
||||
if (self._cached_version != frame.version) {
|
||||
self._cached_version = frame.version;
|
||||
self._last_index = 0;
|
||||
self._last_length = null;
|
||||
self._tw.reset();
|
||||
@@ -53,8 +53,8 @@ fn versionCheck(self: *HTMLAllCollection, page: *const Page) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn length(self: *HTMLAllCollection, page: *const Page) u32 {
|
||||
if (self.versionCheck(page)) {
|
||||
pub fn length(self: *HTMLAllCollection, frame: *const Frame) u32 {
|
||||
if (self.versionCheck(frame)) {
|
||||
if (self._last_length) |cached_length| {
|
||||
return cached_length;
|
||||
}
|
||||
@@ -76,8 +76,8 @@ pub fn length(self: *HTMLAllCollection, page: *const Page) u32 {
|
||||
return l;
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *HTMLAllCollection, index: usize, page: *const Page) ?*Element {
|
||||
_ = self.versionCheck(page);
|
||||
pub fn getAtIndex(self: *HTMLAllCollection, index: usize, frame: *const Frame) ?*Element {
|
||||
_ = self.versionCheck(frame);
|
||||
var current = self._last_index;
|
||||
if (index <= current) {
|
||||
current = 0;
|
||||
@@ -98,15 +98,15 @@ pub fn getAtIndex(self: *HTMLAllCollection, index: usize, page: *const Page) ?*E
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getByName(self: *HTMLAllCollection, name: []const u8, page: *Page) ?*Element {
|
||||
pub fn getByName(self: *HTMLAllCollection, name: []const u8, frame: *Frame) ?*Element {
|
||||
// First, try fast ID lookup using the document's element map
|
||||
if (page.document._elements_by_id.get(name)) |el| {
|
||||
if (frame.document._elements_by_id.get(name)) |el| {
|
||||
return el;
|
||||
}
|
||||
|
||||
// Fall back to searching by name attribute
|
||||
// Clone the tree walker to preserve _last_index optimization
|
||||
_ = self.versionCheck(page);
|
||||
_ = self.versionCheck(frame);
|
||||
var tw = self._tw.clone();
|
||||
tw.reset();
|
||||
|
||||
@@ -127,10 +127,10 @@ const CAllAsFunctionArg = union(enum) {
|
||||
index: u32,
|
||||
id: []const u8,
|
||||
};
|
||||
pub fn callable(self: *HTMLAllCollection, arg: CAllAsFunctionArg, page: *Page) ?*Element {
|
||||
pub fn callable(self: *HTMLAllCollection, arg: CAllAsFunctionArg, frame: *Frame) ?*Element {
|
||||
return switch (arg) {
|
||||
.index => |i| self.getAtIndex(i, page),
|
||||
.id => |id| self.getByName(id, page),
|
||||
.index => |i| self.getAtIndex(i, frame),
|
||||
.id => |id| self.getByName(id, frame),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -175,11 +175,11 @@ pub const JsApi = struct {
|
||||
pub const @"[str]" = bridge.namedIndexed(HTMLAllCollection.getByName, null, null, .{ .null_as_undefined = true });
|
||||
|
||||
pub const item = bridge.function(_item, .{});
|
||||
fn _item(self: *HTMLAllCollection, index: i32, page: *Page) ?*Element {
|
||||
fn _item(self: *HTMLAllCollection, index: i32, frame: *Frame) ?*Element {
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return self.getAtIndex(@intCast(index), page);
|
||||
return self.getAtIndex(@intCast(index), frame);
|
||||
}
|
||||
|
||||
pub const namedItem = bridge.function(HTMLAllCollection.getByName, .{});
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const TreeWalker = @import("../TreeWalker.zig");
|
||||
const NodeLive = @import("node_live.zig").NodeLive;
|
||||
@@ -57,24 +57,24 @@ _data: union(Mode) {
|
||||
empty: void,
|
||||
},
|
||||
|
||||
pub fn length(self: *HTMLCollection, page: *const Page) u32 {
|
||||
pub fn length(self: *HTMLCollection, frame: *const Frame) u32 {
|
||||
return switch (self._data) {
|
||||
.empty => 0,
|
||||
inline else => |*impl| impl.length(page),
|
||||
inline else => |*impl| impl.length(frame),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *HTMLCollection, index: usize, page: *const Page) ?*Element {
|
||||
pub fn getAtIndex(self: *HTMLCollection, index: usize, frame: *const Frame) ?*Element {
|
||||
return switch (self._data) {
|
||||
.empty => null,
|
||||
inline else => |*impl| impl.getAtIndex(index, page),
|
||||
inline else => |*impl| impl.getAtIndex(index, frame),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getByName(self: *HTMLCollection, name: []const u8, page: *Page) ?*Element {
|
||||
pub fn getByName(self: *HTMLCollection, name: []const u8, frame: *Frame) ?*Element {
|
||||
return switch (self._data) {
|
||||
.empty => null,
|
||||
inline else => |*impl| impl.getByName(name, page),
|
||||
inline else => |*impl| impl.getByName(name, frame),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,11 +149,11 @@ pub const JsApi = struct {
|
||||
pub const @"[str]" = bridge.namedIndexed(HTMLCollection.getByName, null, null, .{ .null_as_undefined = true });
|
||||
|
||||
pub const item = bridge.function(_item, .{});
|
||||
fn _item(self: *HTMLCollection, index: i32, page: *Page) ?*Element {
|
||||
fn _item(self: *HTMLCollection, index: i32, frame: *Frame) ?*Element {
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return self.getAtIndex(@intCast(index), page);
|
||||
return self.getAtIndex(@intCast(index), frame);
|
||||
}
|
||||
|
||||
pub const namedItem = bridge.function(HTMLCollection.getByName, .{});
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
|
||||
const NodeList = @import("NodeList.zig");
|
||||
@@ -36,15 +36,15 @@ pub const NamedItemResult = union(enum) {
|
||||
radio_node_list: *RadioNodeList,
|
||||
};
|
||||
|
||||
pub fn length(self: *HTMLFormControlsCollection, page: *Page) u32 {
|
||||
return self._proto.length(page);
|
||||
pub fn length(self: *HTMLFormControlsCollection, frame: *Frame) u32 {
|
||||
return self._proto.length(frame);
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *HTMLFormControlsCollection, index: usize, page: *Page) ?*Element {
|
||||
return self._proto.getAtIndex(index, page);
|
||||
pub fn getAtIndex(self: *HTMLFormControlsCollection, index: usize, frame: *Frame) ?*Element {
|
||||
return self._proto.getAtIndex(index, frame);
|
||||
}
|
||||
|
||||
pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Page) !?NamedItemResult {
|
||||
pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, frame: *Frame) !?NamedItemResult {
|
||||
if (name.len == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -79,13 +79,13 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Pag
|
||||
count += 1;
|
||||
|
||||
if (count == 2) {
|
||||
const radio_node_list = try page._factory.create(RadioNodeList{
|
||||
const radio_node_list = try frame._factory.create(RadioNodeList{
|
||||
._proto = undefined,
|
||||
._form_collection = self,
|
||||
._name = try page.dupeString(name),
|
||||
._name = try frame.dupeString(name),
|
||||
});
|
||||
|
||||
radio_node_list._proto = try page._factory.create(NodeList{ ._data = .{ .radio_node_list = radio_node_list } });
|
||||
radio_node_list._proto = try frame._factory.create(NodeList{ ._data = .{ .radio_node_list = radio_node_list } });
|
||||
|
||||
return .{ .radio_node_list = radio_node_list };
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Node = @import("../Node.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const HTMLCollection = @import("HTMLCollection.zig");
|
||||
@@ -30,17 +30,17 @@ _proto: *HTMLCollection,
|
||||
_select: *@import("../element/html/Select.zig"),
|
||||
|
||||
// Forward length to HTMLCollection
|
||||
pub fn length(self: *HTMLOptionsCollection, page: *Page) u32 {
|
||||
return self._proto.length(page);
|
||||
pub fn length(self: *HTMLOptionsCollection, frame: *Frame) u32 {
|
||||
return self._proto.length(frame);
|
||||
}
|
||||
|
||||
// Forward indexed access to HTMLCollection
|
||||
pub fn getAtIndex(self: *HTMLOptionsCollection, index: usize, page: *Page) ?*Element {
|
||||
return self._proto.getAtIndex(index, page);
|
||||
pub fn getAtIndex(self: *HTMLOptionsCollection, index: usize, frame: *Frame) ?*Element {
|
||||
return self._proto.getAtIndex(index, frame);
|
||||
}
|
||||
|
||||
pub fn getByName(self: *HTMLOptionsCollection, name: []const u8, page: *Page) ?*Element {
|
||||
return self._proto.getByName(name, page);
|
||||
pub fn getByName(self: *HTMLOptionsCollection, name: []const u8, frame: *Frame) ?*Element {
|
||||
return self._proto.getByName(name, frame);
|
||||
}
|
||||
|
||||
// Forward selectedIndex to the owning select element
|
||||
@@ -60,7 +60,7 @@ const AddBeforeOption = union(enum) {
|
||||
};
|
||||
|
||||
// Add a new option element
|
||||
pub fn add(self: *HTMLOptionsCollection, element: *Option, before_: ?AddBeforeOption, page: *Page) !void {
|
||||
pub fn add(self: *HTMLOptionsCollection, element: *Option, before_: ?AddBeforeOption, frame: *Frame) !void {
|
||||
const select_node = self._select.asNode();
|
||||
const element_node = element.asElement().asNode();
|
||||
|
||||
@@ -68,24 +68,24 @@ pub fn add(self: *HTMLOptionsCollection, element: *Option, before_: ?AddBeforeOp
|
||||
if (before_) |before| {
|
||||
switch (before) {
|
||||
.index => |idx| {
|
||||
if (self.getAtIndex(idx, page)) |el| {
|
||||
if (self.getAtIndex(idx, frame)) |el| {
|
||||
before_node = el.asNode();
|
||||
}
|
||||
},
|
||||
.option => |before_option| before_node = before_option.asNode(),
|
||||
}
|
||||
}
|
||||
_ = try select_node.insertBefore(element_node, before_node, page);
|
||||
_ = try select_node.insertBefore(element_node, before_node, frame);
|
||||
}
|
||||
|
||||
// Remove an option element by index
|
||||
pub fn remove(self: *HTMLOptionsCollection, index: i32, page: *Page) void {
|
||||
pub fn remove(self: *HTMLOptionsCollection, index: i32, frame: *Frame) void {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._proto.getAtIndex(@intCast(index), page)) |element| {
|
||||
element.remove(page);
|
||||
if (self._proto.getAtIndex(@intCast(index), frame)) |element| {
|
||||
element.remove(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ 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");
|
||||
|
||||
@@ -57,44 +57,44 @@ pub fn acquireRef(self: *NodeList) void {
|
||||
self._rc.acquire();
|
||||
}
|
||||
|
||||
pub fn length(self: *NodeList, page: *Page) !u32 {
|
||||
pub fn length(self: *NodeList, frame: *Frame) !u32 {
|
||||
return switch (self._data) {
|
||||
.child_nodes => |impl| impl.length(page),
|
||||
.child_nodes => |impl| impl.length(frame),
|
||||
.selector_list => |impl| @intCast(impl.getLength()),
|
||||
.radio_node_list => |impl| impl.getLength(),
|
||||
.name => |*impl| impl.length(page),
|
||||
.name => |*impl| impl.length(frame),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn indexedGet(self: *NodeList, index: usize, page: *Page) !*Node {
|
||||
return try self.getAtIndex(index, page) orelse return error.NotHandled;
|
||||
pub fn indexedGet(self: *NodeList, index: usize, frame: *Frame) !*Node {
|
||||
return try self.getAtIndex(index, frame) orelse return error.NotHandled;
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *NodeList, index: usize, page: *Page) !?*Node {
|
||||
pub fn getAtIndex(self: *NodeList, index: usize, frame: *Frame) !?*Node {
|
||||
return switch (self._data) {
|
||||
.child_nodes => |impl| impl.getAtIndex(index, page),
|
||||
.child_nodes => |impl| impl.getAtIndex(index, frame),
|
||||
.selector_list => |impl| impl.getAtIndex(index),
|
||||
.radio_node_list => |impl| impl.getAtIndex(index, page),
|
||||
.name => |*impl| if (impl.getAtIndex(index, page)) |el| el.asNode() else null,
|
||||
.radio_node_list => |impl| impl.getAtIndex(index, frame),
|
||||
.name => |*impl| if (impl.getAtIndex(index, frame)) |el| el.asNode() else null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn keys(self: *NodeList, page: *Page) !*KeyIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn keys(self: *NodeList, frame: *Frame) !*KeyIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn values(self: *NodeList, page: *Page) !*ValueIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn values(self: *NodeList, frame: *Frame) !*ValueIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn entries(self: *NodeList, page: *Page) !*EntryIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn entries(self: *NodeList, frame: *Frame) !*EntryIterator {
|
||||
return .init(.{ .list = self }, frame);
|
||||
}
|
||||
|
||||
pub fn forEach(self: *NodeList, cb: js.Function, page: *Page) !void {
|
||||
pub fn forEach(self: *NodeList, cb: js.Function, frame: *Frame) !void {
|
||||
var i: i32 = 0;
|
||||
while (true) : (i += 1) {
|
||||
const node = try self.getAtIndex(@intCast(i), page) orelse return;
|
||||
const node = try self.getAtIndex(@intCast(i), frame) orelse return;
|
||||
|
||||
var caught: js.TryCatch.Caught = undefined;
|
||||
cb.tryCall(void, .{ node, i, self }, &caught) catch {
|
||||
@@ -127,9 +127,9 @@ const Iterator = struct {
|
||||
self.list.acquireRef();
|
||||
}
|
||||
|
||||
pub fn next(self: *Iterator, page: *Page) !?Entry {
|
||||
pub fn next(self: *Iterator, frame: *Frame) !?Entry {
|
||||
const index = self.index;
|
||||
const node = try self.list.getAtIndex(index, page) orelse return null;
|
||||
const node = try self.list.getAtIndex(index, frame) orelse return null;
|
||||
self.index = index + 1;
|
||||
return .{ index, node };
|
||||
}
|
||||
@@ -154,9 +154,9 @@ pub const JsApi = struct {
|
||||
pub const forEach = bridge.function(NodeList.forEach, .{});
|
||||
pub const symbol_iterator = bridge.iterator(NodeList.values, .{});
|
||||
|
||||
fn getIndexes(self: *NodeList, page: *Page) !js.Array {
|
||||
const len = try self.length(page);
|
||||
var arr = page.js.local.?.newArray(len);
|
||||
fn getIndexes(self: *NodeList, frame: *Frame) !js.Array {
|
||||
const len = try self.length(frame);
|
||||
var arr = frame.js.local.?.newArray(len);
|
||||
for (0..len) |i| {
|
||||
_ = try arr.set(@intCast(i), i, .{});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
|
||||
const Node = @import("../Node.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
@@ -44,10 +44,10 @@ pub fn getLength(self: *RadioNodeList) !u32 {
|
||||
return i;
|
||||
}
|
||||
|
||||
pub fn getAtIndex(self: *RadioNodeList, index: usize, page: *Page) !?*Node {
|
||||
pub fn getAtIndex(self: *RadioNodeList, index: usize, frame: *Frame) !?*Node {
|
||||
var i: usize = 0;
|
||||
var current: usize = 0;
|
||||
while (self._form_collection.getAtIndex(i, page)) |element| : (i += 1) {
|
||||
while (self._form_collection.getAtIndex(i, frame)) |element| : (i += 1) {
|
||||
if (!self.matches(element)) {
|
||||
continue;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ pub fn getValue(self: *RadioNodeList) ![]const u8 {
|
||||
return "";
|
||||
}
|
||||
|
||||
pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void {
|
||||
pub fn setValue(self: *RadioNodeList, value: []const u8, frame: *Frame) !void {
|
||||
var it = try self._form_collection.iterator();
|
||||
while (it.next()) |element| {
|
||||
const input = element.is(Input) orelse continue;
|
||||
@@ -92,7 +92,7 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void {
|
||||
};
|
||||
|
||||
if (matches_value) {
|
||||
try input.setChecked(true, page);
|
||||
try input.setChecked(true, frame);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
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;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user