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);
|
self.arena_pool.reset(self._session.transfer_arena, 4 * 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
pub fn wait(self: *Page, wait_ms: u32) Session.WaitResult {
|
pub fn wait(self: *Page, wait_ms: u32) Session.WaitResult {
|
||||||
return self._wait(wait_ms) catch |err| {
|
return self._wait(wait_ms) catch |err| {
|
||||||
switch (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 {
|
pub fn isGoingAway(self: *const Page) bool {
|
||||||
return self._queued_navigation != null;
|
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", .{});
|
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
||||||
self._session.notification.dispatch(.page_network_idle, &.{
|
self._session.notification.dispatch(.page_network_idle, &.{
|
||||||
.page_id = self.id,
|
.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", .{});
|
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
||||||
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
||||||
.page_id = self.id,
|
.page_id = self.id,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const lp = @import("lightpanda");
|
const lp = @import("lightpanda");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ const Browser = @import("Browser.zig");
|
|||||||
const Notification = @import("../Notification.zig");
|
const Notification = @import("../Notification.zig");
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
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.
|
// Session is like a browser's tab.
|
||||||
// It owns the js env and the loader for all the pages of the session.
|
// 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 {
|
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
||||||
|
var page = &(self.page orelse return .no_page);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (self.page) |*page| {
|
const wait_result = self._wait(page, wait_ms) catch |err| {
|
||||||
switch (page.wait(wait_ms)) {
|
switch (err) {
|
||||||
.done => {
|
error.JsError => {}, // already logged (with hopefully more context)
|
||||||
if (page._queued_navigation == null) {
|
else => log.err(.browser, "session wait", .{ .err = err, }),
|
||||||
return .done;
|
|
||||||
}
|
|
||||||
self.processScheduledNavigation() catch return .done;
|
|
||||||
},
|
|
||||||
else => |result| return result,
|
|
||||||
}
|
}
|
||||||
} else {
|
return .done;
|
||||||
return .no_page;
|
};
|
||||||
|
|
||||||
|
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);
|
defer self.browser.arena_pool.reset(self.transfer_arena, 4 * 1024);
|
||||||
const url, const opts, const page_id = blk: {
|
const url, const opts, const page_id = blk: {
|
||||||
const page = self.page.?;
|
const page = self.page.?;
|
||||||
@@ -226,4 +368,6 @@ fn processScheduledNavigation(self: *Session) !void {
|
|||||||
log.err(.browser, "queued navigation error", .{ .err = err, .url = url });
|
log.err(.browser, "queued navigation error", .{ .err = err, .url = url });
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return page;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ fn run(
|
|||||||
const url = try std.fmt.allocPrintSentinel(arena, "http://localhost:9582/{s}", .{test_file}, 0);
|
const url = try std.fmt.allocPrintSentinel(arena, "http://localhost:9582/{s}", .{test_file}, 0);
|
||||||
try page.navigate(url, .{});
|
try page.navigate(url, .{});
|
||||||
|
|
||||||
_ = page.wait(2000);
|
_ = session.wait(2000);
|
||||||
|
|
||||||
var ls: lp.js.Local.Scope = undefined;
|
var ls: lp.js.Local.Scope = undefined;
|
||||||
page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
|
|||||||
Reference in New Issue
Block a user