diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index aa5f023a..e6d1ec0b 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -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 { diff --git a/src/browser/Page.zig b/src/browser/Page.zig index b93a9f94..28c1fcac 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -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 { diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 8c37b47e..fddcdfa4 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -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); diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index 6a50576c..71e19286 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -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; diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 1607bb79..2ebae996 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -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; } };