explicitly run microtasks

This commit is contained in:
Karl Seguin
2025-11-25 15:54:25 +08:00
parent 218d08b1f6
commit 35a728e69f
5 changed files with 45 additions and 27 deletions

View File

@@ -107,12 +107,19 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void
if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
}
event._target = target;
var was_handled = false;
defer if (was_handled) {
self.page.js.runMicrotasks();
};
switch (target._type) {
.node => |node| try self.dispatchNode(node, event),
.node => |node| try self.dispatchNode(node, event, &was_handled),
.xhr, .window, .abort_signal, .media_query_list => {
const list = self.lookup.getPtr(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event);
try self.dispatchAll(list, target, event, &was_handled);
},
}
}
@@ -135,19 +142,26 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
event._target = target;
}
var was_dispatched = false;
defer if (was_dispatched) {
self.page.js.runMicrotasks();
};
if (function_) |func| {
event._current_target = target;
func.call(void, .{event}) catch |err| {
if (func.call(void, .{event})) {
was_dispatched = true;
} else |err| {
// a non-JS error
log.warn(.event, opts.context, .{ .err = err });
};
}
}
const list = self.lookup.getPtr(@intFromPtr(target)) orelse return;
try self.dispatchAll(list, target, event);
try self.dispatchAll(list, target, event, &was_dispatched);
}
fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled: *bool) !void {
var path_len: usize = 0;
var path_buffer: [128]*EventTarget = undefined;
@@ -175,7 +189,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
i -= 1;
const current_target = path[i];
if (self.lookup.getPtr(@intFromPtr(current_target))) |list| {
try self.dispatchPhase(list, current_target, event, true);
try self.dispatchPhase(list, current_target, event, was_handled, true);
if (event._stop_propagation) {
event._event_phase = .none;
return;
@@ -187,7 +201,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
event._event_phase = .at_target;
const target_et = target.asEventTarget();
if (self.lookup.getPtr(@intFromPtr(target_et))) |list| {
try self.dispatchPhase(list, target_et, event, null);
try self.dispatchPhase(list, target_et, event, was_handled, null);
if (event._stop_propagation) {
event._event_phase = .none;
return;
@@ -200,7 +214,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
event._event_phase = .bubbling_phase;
for (path[1..]) |current_target| {
if (self.lookup.getPtr(@intFromPtr(current_target))) |list| {
try self.dispatchPhase(list, current_target, event, false);
try self.dispatchPhase(list, current_target, event, was_handled, false);
if (event._stop_propagation) {
break;
}
@@ -211,7 +225,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
event._event_phase = .none;
}
fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, comptime capture_only: ?bool) !void {
fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool, comptime capture_only: ?bool) !void {
const page = self.page;
const typ = event._type_string;
@@ -240,6 +254,7 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
}
}
was_handled.* = true;
event._current_target = current_target;
switch (listener.function) {
@@ -261,8 +276,8 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
}
// Non-Node dispatching (XHR, Window without propagation)
fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event) !void {
return self.dispatchPhase(list, current_target, event, null);
fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool) !void {
return self.dispatchPhase(list, current_target, event, was_handled, null);
}
fn removeListener(self: *EventManager, list: *std.DoublyLinkedList, listener: *Listener) void {

View File

@@ -242,21 +242,13 @@ fn registerBackgroundTasks(self: *Page) !void {
const Browser = @import("Browser.zig");
try self.scheduler.add(self._session.browser, struct {
fn runMicrotasks(ctx: *anyopaque) !?u32 {
const b: *Browser = @ptrCast(@alignCast(ctx));
b.runMicrotasks();
return 5;
}
}.runMicrotasks, 5, .{ .name = "page.microtasks" });
try self.scheduler.add(self._session.browser, struct {
fn runMessageLoop(ctx: *anyopaque) !?u32 {
const b: *Browser = @ptrCast(@alignCast(ctx));
b.runMessageLoop();
return 100;
return 250;
}
}.runMessageLoop, 5, .{ .name = "page.messageLoop" });
}.runMessageLoop, 250, .{ .name = "page.messageLoop" });
}
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
@@ -705,10 +697,10 @@ fn _wait(self: *Page, wait_ms: u32) !Session.WaitResult {
}
pub fn tick(self: *Page) void {
self._session.browser.runMicrotasks();
_ = self.scheduler.run() catch |err| {
log.err(.page, "tick", .{ .err = err });
};
self.js.runMicrotasks();
}
pub fn scriptAddedCallback(self: *Page, script: *HtmlScript) !void {

View File

@@ -1162,6 +1162,10 @@ pub fn resolvePromise(self: *Context, value: anytype) !js.Promise {
return resolver.getPromise();
}
pub fn runMicrotasks(self: *Context) void {
self.isolate.performMicrotasksCheckpoint();
}
// creates a PersistentPromiseResolver, taking in a lifetime parameter.
// If the lifetime is page, the page will clean up the PersistentPromiseResolver.
// If the lifetime is self, you will be expected to deinitalize the PersistentPromiseResolver.
@@ -1444,6 +1448,7 @@ fn dynamicModuleSourceCallback(ctx: *anyopaque, fetch_result_: anyerror!ScriptMa
}
fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, module_entry: ModuleEntry) void {
defer self.runMicrotasks();
const ctx = self.v8_context;
const isolate = self.isolate;
const external = v8.External.init(self.isolate, @ptrCast(state));
@@ -1479,6 +1484,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
return;
}
defer caller.context.runMicrotasks();
const namespace = s.module.?.getModuleNamespace();
_ = s.resolver.castToPromiseResolver().resolve(caller.context.v8_context, namespace);
}
@@ -1494,6 +1500,7 @@ fn resolveDynamicModule(self: *Context, state: *DynamicModuleResolveState, modul
if (s.context_id != caller.context.id) {
return;
}
defer caller.context.runMicrotasks();
_ = s.resolver.castToPromiseResolver().reject(caller.context.v8_context, info.getData());
}
}.callback, external);

View File

@@ -107,6 +107,7 @@ pub const PersistentPromiseResolver = struct {
pub fn resolve(self: PersistentPromiseResolver, value: anytype) !void {
const context = self.context;
const js_value = try context.zigValueToJs(value, .{});
defer context.runMicrotasks();
// resolver.resolve will return null if the promise isn't pending
const ok = self.resolver.castToPromiseResolver().resolve(context.v8_context, js_value) orelse return;
@@ -118,6 +119,7 @@ pub const PersistentPromiseResolver = struct {
pub fn reject(self: PersistentPromiseResolver, value: anytype) !void {
const context = self.context;
const js_value = try context.zigValueToJs(value, .{});
defer context.runMicrotasks();
// resolver.reject will return null if the promise isn't pending
const ok = self.resolver.castToPromiseResolver().reject(context.v8_context, js_value) orelse return;

View File

@@ -321,14 +321,15 @@ const ScheduleCallback = struct {
fn run(ctx: *anyopaque) !?u32 {
const self: *ScheduleCallback = @ptrCast(@alignCast(ctx));
const page = self.page;
if (self.removed) {
_ = self.page.window._timers.remove(self.timer_id);
_ = page.window._timers.remove(self.timer_id);
self.deinit();
return null;
}
if (self.animation_frame) {
self.cb.call(void, .{self.page.window._performance.now()}) catch |err| {
self.cb.call(void, .{page.window._performance.now()}) catch |err| {
// a non-JS error
log.warn(.js, "window.RAF", .{ .name = self.name, .err = err });
};
@@ -342,9 +343,10 @@ const ScheduleCallback = struct {
if (self.repeat_ms) |ms| {
return ms;
}
defer self.deinit();
_ = self.page.window._timers.remove(self.timer_id);
self.deinit();
_ = page.window._timers.remove(self.timer_id);
page.js.runMicrotasks();
return null;
}
};