Merge pull request #1426 from lightpanda-io/update_page_js
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / demo-scripts (push) Blocked by required conditions
e2e-test / cdp-and-hyperfine-bench (push) Blocked by required conditions
e2e-test / perf-fmt (push) Blocked by required conditions
e2e-test / browser fetch (push) Blocked by required conditions
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions

Update page.js based on the current context.
This commit is contained in:
Karl Seguin
2026-02-04 08:51:53 +08:00
committed by GitHub
10 changed files with 50 additions and 28 deletions

View File

@@ -112,6 +112,10 @@ pub fn runMicrotasks(self: *const Browser) void {
self.env.runMicrotasks();
}
pub fn runMacrotasks(self: *Browser) !?u64 {
return try self.env.runMacrotasks();
}
pub fn runMessageLoop(self: *const Browser) void {
while (self.env.pumpMessageLoop()) {
if (comptime IS_DEBUG) {

View File

@@ -33,7 +33,6 @@ const String = @import("../string.zig").String;
const Mime = @import("Mime.zig");
const Factory = @import("Factory.zig");
const Session = @import("Session.zig");
const Scheduler = @import("Scheduler.zig");
const EventManager = @import("EventManager.zig");
const ScriptManager = @import("ScriptManager.zig");
@@ -202,8 +201,6 @@ document: *Document,
// DOM version used to invalidate cached state of "live" collections
version: usize,
scheduler: Scheduler,
_req_id: ?usize = null,
_navigated_options: ?NavigatedOpts = null,
@@ -237,10 +234,6 @@ pub fn deinit(self: *Page) void {
// stats.print(&stream) catch unreachable;
}
// This can release JS objects, so we need to do this while the js.Context
// is still around.
self.scheduler.deinit();
{
// some MicroTasks might be referencing the page, we need to drain it while
// the page still exists
@@ -271,8 +264,6 @@ fn reset(self: *Page, comptime initializing: bool) !void {
const browser = self._session.browser;
if (comptime initializing == false) {
self.scheduler.deinit();
browser.env.destroyContext(self.js);
// We force a garbage collection between page navigations to keep v8
@@ -299,7 +290,6 @@ fn reset(self: *Page, comptime initializing: bool) !void {
}
self._factory = Factory.init(self);
self.scheduler = Scheduler.init(self.arena);
self.version = 0;
self.url = "about:blank";
@@ -381,7 +371,7 @@ fn registerBackgroundTasks(self: *Page) !void {
const Browser = @import("Browser.zig");
try self.scheduler.add(self._session.browser, struct {
try self.js.scheduler.add(self._session.browser, struct {
fn runMessageLoop(ctx: *anyopaque) !?u32 {
const b: *Browser = @ptrCast(@alignCast(ctx));
b.runMessageLoop();
@@ -887,8 +877,8 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
var timer = try std.time.Timer.start();
var ms_remaining = wait_ms;
var scheduler = &self.scheduler;
var http_client = self._session.browser.http_client;
const browser = self._session.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
@@ -941,7 +931,7 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
// 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 scheduler.run();
const ms_to_next_task = try browser.runMacrotasks();
const http_active = http_client.active;
const total_network_activity = http_active + http_client.intercepted;
@@ -1077,16 +1067,16 @@ fn printWaitAnalysis(self: *Page) void {
const now = milliTimestamp(.monotonic);
{
std.debug.print("\nhigh_priority schedule: {d}\n", .{self.scheduler.high_priority.count()});
var it = self.scheduler.high_priority.iterator();
std.debug.print("\nhigh_priority schedule: {d}\n", .{self.js.scheduler.high_priority.count()});
var it = self.js.scheduler.high_priority.iterator();
while (it.next()) |task| {
std.debug.print(" - {s} schedule: {d}ms\n", .{ task.name, task.run_at - now });
}
}
{
std.debug.print("\nlow_priority schedule: {d}\n", .{self.scheduler.low_priority.count()});
var it = self.scheduler.low_priority.iterator();
std.debug.print("\nlow_priority schedule: {d}\n", .{self.js.scheduler.low_priority.count()});
var it = self.js.scheduler.low_priority.iterator();
while (it.next()) |task| {
std.debug.print(" - {s} schedule: {d}ms\n", .{ task.name, task.run_at - now });
}
@@ -1258,7 +1248,7 @@ pub fn notifyPerformanceObservers(self: *Page, entry: *Performance.Entry) !void
}
self._performance_delivery_scheduled = true;
return self.scheduler.add(
return self.js.scheduler.add(
self,
struct {
fn run(_page: *anyopaque) anyerror!?u32 {

View File

@@ -846,7 +846,7 @@ pub const Script = struct {
defer {
// We should run microtasks even if script execution fails.
local.runMicrotasks();
_ = page.scheduler.run() catch |err| {
_ = page.js.scheduler.run() catch |err| {
log.err(.page, "scheduler", .{ .err = err });
};
}

View File

@@ -35,6 +35,7 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
const Caller = @This();
local: js.Local,
prev_local: ?*const js.Local,
prev_context: *Context,
// Takes the raw v8 isolate and extracts the context from it.
pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void {
@@ -53,7 +54,9 @@ pub fn init(self: *Caller, v8_isolate: *v8.Isolate) void {
.isolate = .{ .handle = v8_isolate },
},
.prev_local = ctx.local,
.prev_context = ctx.page.js,
};
ctx.page.js = ctx;
ctx.local = &self.local;
}
@@ -79,6 +82,7 @@ pub fn deinit(self: *Caller) void {
ctx.call_depth = call_depth;
ctx.local = self.prev_local;
ctx.page.js = self.prev_context;
}
pub const CallOpts = struct {

View File

@@ -23,6 +23,7 @@ const log = @import("../../log.zig");
const js = @import("js.zig");
const Env = @import("Env.zig");
const bridge = @import("bridge.zig");
const Scheduler = @import("Scheduler.zig");
const Page = @import("../Page.zig");
const ScriptManager = @import("../ScriptManager.zig");
@@ -118,6 +119,9 @@ module_identifier: std.AutoHashMapUnmanaged(u32, [:0]const u8) = .empty,
// the page's script manager
script_manager: ?*ScriptManager,
// Our macrotasks
scheduler: Scheduler,
const ModuleEntry = struct {
// Can be null if we're asynchrously loading the module, in
// which case resolver_promise cannot be null.
@@ -150,6 +154,14 @@ pub fn fromIsolate(isolate: js.Isolate) *Context {
}
pub fn deinit(self: *Context) void {
var page = self.page;
const prev_context = page.js;
page.js = self;
defer page.js = prev_context;
// This can release JS objects
self.scheduler.deinit();
{
var it = self.identity_map.valueIterator();
while (it.next()) |global| {

View File

@@ -240,6 +240,7 @@ pub fn createContext(self: *Env, page: *Page, enter: bool) !*Context {
.templates = self.templates,
.call_arena = page.call_arena,
.script_manager = &page._script_manager,
.scheduler = .init(context_arena),
};
try context.identity_map.putNoClobber(context_arena, @intFromPtr(page.window), global_global);
@@ -271,6 +272,17 @@ pub fn runMicrotasks(self: *const Env) void {
self.isolate.performMicrotasksCheckpoint();
}
pub fn runMacrotasks(self: *Env) !?u64 {
var ms_to_next_task: ?u64 = null;
for (self.contexts.items) |ctx| {
const ms = (try ctx.scheduler.run()) orelse continue;
if (ms_to_next_task == null or ms < ms_to_next_task.?) {
ms_to_next_task = ms;
}
}
return ms_to_next_task;
}
pub fn pumpMessageLoop(self: *const Env) bool {
var hs: v8.HandleScope = undefined;
v8.v8__HandleScope__CONSTRUCT(&hs, self.isolate.handle);

View File

@@ -19,8 +19,8 @@
const std = @import("std");
const builtin = @import("builtin");
const log = @import("../log.zig");
const milliTimestamp = @import("../datetime.zig").milliTimestamp;
const log = @import("../../log.zig");
const milliTimestamp = @import("../../datetime.zig").milliTimestamp;
const IS_DEBUG = builtin.mode == .Debug;

View File

@@ -99,7 +99,7 @@ pub fn createTimeout(delay: u32, page: *Page) !*AbortSignal {
.signal = try init(page),
};
try page.scheduler.add(callback, TimeoutCallback.run, delay, .{
try page.js.scheduler.add(callback, TimeoutCallback.run, delay, .{
.name = "AbortSignal.timeout",
});

View File

@@ -65,7 +65,7 @@ pub fn postMessage(self: *MessagePort, message: js.Value.Global, page: *Page) !v
.message = message,
});
try page.scheduler.add(callback, PostMessageCallback.run, 0, .{
try page.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
.name = "MessagePort.postMessage",
.low_priority = false,
});

View File

@@ -366,7 +366,7 @@ pub fn postMessage(self: *Window, message: js.Value.Global, target_origin: ?[]co
.message = message,
.origin = try arena.dupe(u8, origin),
};
try page.scheduler.add(callback, PostMessageCallback.run, 0, .{
try page.js.scheduler.add(callback, PostMessageCallback.run, 0, .{
.name = "postMessage",
.low_priority = false,
.finalizer = PostMessageCallback.cancelled,
@@ -447,7 +447,7 @@ 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.scheduler.add(
try page.js.scheduler.add(
page,
struct {
fn dispatch(_page: *anyopaque) anyerror!?u32 {
@@ -471,7 +471,7 @@ pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
.{ .low_priority = true },
);
// We dispatch scrollend event asynchronously after 20ms.
try page.scheduler.add(
try page.js.scheduler.add(
page,
struct {
fn dispatch(_page: *anyopaque) anyerror!?u32 {
@@ -545,7 +545,7 @@ fn scheduleCallback(self: *Window, cb: js.Function.Temp, delay_ms: u32, opts: Sc
};
gop.value_ptr.* = callback;
try page.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{
try page.js.scheduler.add(callback, ScheduleCallback.run, delay_ms, .{
.name = opts.name,
.low_priority = opts.low_priority,
.finalizer = ScheduleCallback.cancelled,