mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Move page.wait to session.wait
page.wait is the only significant difference between the "root" page and a page for an iframe. I think it's more explicit to move this out of the page and into the session, which was already the sole entry-point for page.wait.
This commit is contained in:
@@ -942,6 +942,7 @@ fn clearTransferArena(self: *Page) void {
|
||||
self.arena_pool.reset(self._session.transfer_arena, 4 * 1024);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
pub fn wait(self: *Page, wait_ms: u32) Session.WaitResult {
|
||||
return self._wait(wait_ms) catch |err| {
|
||||
switch (err) {
|
||||
@@ -1173,6 +1174,8 @@ fn printWaitAnalysis(self: *Page) void {
|
||||
}
|
||||
}
|
||||
|
||||
=======
|
||||
>>>>>>> 3bd80eb3 (Move page.wait to session.wait)
|
||||
pub fn isGoingAway(self: *const Page) bool {
|
||||
return self._queued_navigation != null;
|
||||
}
|
||||
@@ -1565,7 +1568,7 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn notifyNetworkIdle(self: *Page) void {
|
||||
pub fn notifyNetworkIdle(self: *Page) void {
|
||||
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
||||
self._session.notification.dispatch(.page_network_idle, &.{
|
||||
.page_id = self.id,
|
||||
@@ -1574,7 +1577,7 @@ fn notifyNetworkIdle(self: *Page) void {
|
||||
});
|
||||
}
|
||||
|
||||
fn notifyNetworkAlmostIdle(self: *Page) void {
|
||||
pub fn notifyNetworkAlmostIdle(self: *Page) void {
|
||||
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
||||
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
||||
.page_id = self.id,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const log = @import("../log.zig");
|
||||
|
||||
@@ -31,7 +32,7 @@ const Browser = @import("Browser.zig");
|
||||
const Notification = @import("../Notification.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
// Session is like a browser's tab.
|
||||
// It owns the js env and the loader for all the pages of the session.
|
||||
@@ -176,26 +177,167 @@ pub fn findPage(self: *Session, id: u32) ?*Page {
|
||||
}
|
||||
|
||||
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
||||
var page = &(self.page orelse return .no_page);
|
||||
while (true) {
|
||||
if (self.page) |*page| {
|
||||
switch (page.wait(wait_ms)) {
|
||||
.done => {
|
||||
if (page._queued_navigation == null) {
|
||||
return .done;
|
||||
}
|
||||
self.processScheduledNavigation() catch return .done;
|
||||
},
|
||||
else => |result| return result,
|
||||
const wait_result = self._wait(page, wait_ms) catch |err| {
|
||||
switch (err) {
|
||||
error.JsError => {}, // already logged (with hopefully more context)
|
||||
else => log.err(.browser, "session wait", .{ .err = err, }),
|
||||
}
|
||||
} else {
|
||||
return .no_page;
|
||||
return .done;
|
||||
};
|
||||
|
||||
switch (wait_result) {
|
||||
.done => {
|
||||
if (page._queued_navigation == null) {
|
||||
return .done;
|
||||
}
|
||||
page = self.processScheduledNavigation() catch return .done;
|
||||
},
|
||||
else => |result| return result,
|
||||
}
|
||||
// if we've successfull navigated, we'll give the new page another
|
||||
// page.wait(wait_ms)
|
||||
}
|
||||
}
|
||||
|
||||
fn processScheduledNavigation(self: *Session) !void {
|
||||
fn _wait(self: *Session, page: *Page, wait_ms: u32) !WaitResult {
|
||||
var timer = try std.time.Timer.start();
|
||||
var ms_remaining = wait_ms;
|
||||
|
||||
const browser = self.browser;
|
||||
var http_client = browser.http_client;
|
||||
|
||||
// I'd like the page to know NOTHING about cdp_socket / CDP, but the
|
||||
// fact is that the behavior of wait changes depending on whether or
|
||||
// not we're using CDP.
|
||||
// If we aren't using CDP, as soon as we think there's nothing left
|
||||
// to do, we can exit - we'de done.
|
||||
// But if we are using CDP, we should wait for the whole `wait_ms`
|
||||
// because the http_click.tick() also monitors the CDP socket. And while
|
||||
// we could let CDP poll http (like it does for HTTP requests), the fact
|
||||
// is that we know more about the timing of stuff (e.g. how long to
|
||||
// poll/sleep) in the page.
|
||||
const exit_when_done = http_client.cdp_client == null;
|
||||
|
||||
while (true) {
|
||||
switch (page._parse_state) {
|
||||
.pre, .raw, .text, .image => {
|
||||
// The main page hasn't started/finished navigating.
|
||||
// There's no JS to run, and no reason to run the scheduler.
|
||||
if (http_client.active == 0 and exit_when_done) {
|
||||
// haven't started navigating, I guess.
|
||||
return .done;
|
||||
}
|
||||
// Either we have active http connections, or we're in CDP
|
||||
// mode with an extra socket. Either way, we're waiting
|
||||
// for http traffic
|
||||
if (try http_client.tick(@intCast(ms_remaining)) == .cdp_socket) {
|
||||
// exit_when_done is explicitly set when there isn't
|
||||
// an extra socket, so it should not be possibl to
|
||||
// get an cdp_socket message when exit_when_done
|
||||
// is true.
|
||||
if (IS_DEBUG) {
|
||||
std.debug.assert(exit_when_done == false);
|
||||
}
|
||||
|
||||
// data on a socket we aren't handling, return to caller
|
||||
return .cdp_socket;
|
||||
}
|
||||
},
|
||||
.html, .complete => {
|
||||
if (page._queued_navigation != null) {
|
||||
return .done;
|
||||
}
|
||||
|
||||
// The HTML page was parsed. We now either have JS scripts to
|
||||
// download, or scheduled tasks to execute, or both.
|
||||
|
||||
// scheduler.run could trigger new http transfers, so do not
|
||||
// store http_client.active BEFORE this call and then use
|
||||
// it AFTER.
|
||||
const ms_to_next_task = try browser.runMacrotasks();
|
||||
|
||||
const http_active = http_client.active;
|
||||
const total_network_activity = http_active + http_client.intercepted;
|
||||
if (page._notified_network_almost_idle.check(total_network_activity <= 2)) {
|
||||
page.notifyNetworkAlmostIdle();
|
||||
}
|
||||
if (page._notified_network_idle.check(total_network_activity == 0)) {
|
||||
page.notifyNetworkIdle();
|
||||
}
|
||||
|
||||
if (http_active == 0 and exit_when_done) {
|
||||
// we don't need to consider http_client.intercepted here
|
||||
// because exit_when_done is true, and that can only be
|
||||
// the case when interception isn't possible.
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(http_client.intercepted == 0);
|
||||
}
|
||||
|
||||
const ms = ms_to_next_task orelse blk: {
|
||||
if (wait_ms - ms_remaining < 100) {
|
||||
if (comptime builtin.is_test) {
|
||||
return .done;
|
||||
}
|
||||
// Look, we want to exit ASAP, but we don't want
|
||||
// to exit so fast that we've run none of the
|
||||
// background jobs.
|
||||
break :blk 50;
|
||||
}
|
||||
// No http transfers, no cdp extra socket, no
|
||||
// scheduled tasks, we're done.
|
||||
return .done;
|
||||
};
|
||||
|
||||
if (ms > ms_remaining) {
|
||||
// Same as above, except we have a scheduled task,
|
||||
// it just happens to be too far into the future
|
||||
// compared to how long we were told to wait.
|
||||
return .done;
|
||||
}
|
||||
|
||||
// We have a task to run in the not-so-distant future.
|
||||
// You might think we can just sleep until that task is
|
||||
// ready, but we should continue to run lowPriority tasks
|
||||
// in the meantime, and that could unblock things. So
|
||||
// we'll just sleep for a bit, and then restart our wait
|
||||
// loop to see if anything new can be processed.
|
||||
std.Thread.sleep(std.time.ns_per_ms * @as(u64, @intCast(@min(ms, 20))));
|
||||
} else {
|
||||
// We're here because we either have active HTTP
|
||||
// connections, or exit_when_done == false (aka, there's
|
||||
// an cdp_socket registered with the http client).
|
||||
// We should continue to run lowPriority tasks, so we
|
||||
// minimize how long we'll poll for network I/O.
|
||||
const ms_to_wait = @min(200, @min(ms_remaining, ms_to_next_task orelse 200));
|
||||
if (try http_client.tick(ms_to_wait) == .cdp_socket) {
|
||||
// data on a socket we aren't handling, return to caller
|
||||
return .cdp_socket;
|
||||
}
|
||||
}
|
||||
},
|
||||
.err => |err| {
|
||||
page._parse_state = .{ .raw_done = @errorName(err) };
|
||||
return err;
|
||||
},
|
||||
.raw_done => {
|
||||
if (exit_when_done) {
|
||||
return .done;
|
||||
}
|
||||
// we _could_ http_client.tick(ms_to_wait), but this has
|
||||
// the same result, and I feel is more correct.
|
||||
return .no_page;
|
||||
},
|
||||
}
|
||||
|
||||
const ms_elapsed = timer.lap() / 1_000_000;
|
||||
if (ms_elapsed >= ms_remaining) {
|
||||
return .done;
|
||||
}
|
||||
ms_remaining -= @intCast(ms_elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
fn processScheduledNavigation(self: *Session) !*Page {
|
||||
defer self.browser.arena_pool.reset(self.transfer_arena, 4 * 1024);
|
||||
const url, const opts, const page_id = blk: {
|
||||
const page = self.page.?;
|
||||
@@ -226,4 +368,6 @@ fn processScheduledNavigation(self: *Session) !void {
|
||||
log.err(.browser, "queued navigation error", .{ .err = err, .url = url });
|
||||
return err;
|
||||
};
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ fn run(
|
||||
const url = try std.fmt.allocPrintSentinel(arena, "http://localhost:9582/{s}", .{test_file}, 0);
|
||||
try page.navigate(url, .{});
|
||||
|
||||
_ = page.wait(2000);
|
||||
_ = session.wait(2000);
|
||||
|
||||
var ls: lp.js.Local.Scope = undefined;
|
||||
page.js.localScope(&ls);
|
||||
|
||||
Reference in New Issue
Block a user