mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-03 22:13:46 +00:00
per-context scheduler
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
@@ -273,8 +266,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);
|
||||
|
||||
// removing a context can trigger finalizers, so we can only check for
|
||||
@@ -297,7 +288,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";
|
||||
@@ -379,7 +369,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();
|
||||
@@ -891,8 +881,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
|
||||
@@ -945,7 +935,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;
|
||||
@@ -1081,16 +1071,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 });
|
||||
}
|
||||
@@ -1262,7 +1252,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 {
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -155,6 +159,9 @@ pub fn deinit(self: *Context) void {
|
||||
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| {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user