Improve unhandled rejection

We now pay attention to the type of event that causes the unhandled exception.
This allows us to trigger the window.rejectionhandled event when that is the
correct type. It also lets us no-op for other event types which should not
trigger rejectionhandled or unhandledrejection.

Fixes stackoverflow in github integration.
This commit is contained in:
Karl Seguin
2026-03-19 11:36:39 +08:00
parent edd0c5c83f
commit db01158d2d
2 changed files with 28 additions and 6 deletions

View File

@@ -495,6 +495,11 @@ pub fn terminate(self: *const Env) void {
}
fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) void {
const promise_event = v8.v8__PromiseRejectMessage__GetEvent(&message_handle);
if (promise_event != v8.kPromiseRejectWithNoHandler and promise_event != v8.kPromiseHandlerAddedAfterReject) {
return;
}
const promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
const isolate = js.Isolate{ .handle = v8_isolate };
@@ -508,7 +513,7 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v
};
const page = ctx.page;
page.window.unhandledPromiseRejection(.{
page.window.unhandledPromiseRejection(promise_event == v8.kPromiseRejectWithNoHandler, .{
.local = &local,
.handle = &message_handle,
}, page) catch |err| {

View File

@@ -67,7 +67,8 @@ _on_pageshow: ?js.Function.Global = null,
_on_popstate: ?js.Function.Global = null,
_on_error: ?js.Function.Global = null,
_on_message: ?js.Function.Global = null,
_on_unhandled_rejection: ?js.Function.Global = null, // TODO: invoke on error
_on_rejection_handled: ?js.Function.Global = null,
_on_unhandled_rejection: ?js.Function.Global = null,
_current_event: ?*Event = null,
_location: *Location,
_timer_id: u30 = 0,
@@ -222,6 +223,14 @@ pub fn setOnMessage(self: *Window, setter: ?FunctionSetter) void {
self._on_message = getFunctionFromSetter(setter);
}
pub fn getOnRejectionHandled(self: *const Window) ?js.Function.Global {
return self._on_rejection_handled;
}
pub fn setOnRejectionHandled(self: *Window, setter: ?FunctionSetter) void {
self._on_rejection_handled = getFunctionFromSetter(setter);
}
pub fn getOnUnhandledRejection(self: *const Window) ?js.Function.Global {
return self._on_unhandled_rejection;
}
@@ -572,7 +581,7 @@ pub fn scrollBy(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !void {
return self.scrollTo(.{ .x = absx }, absy, page);
}
pub fn unhandledPromiseRejection(self: *Window, rejection: js.PromiseRejection, page: *Page) !void {
pub fn unhandledPromiseRejection(self: *Window, no_handler: bool, rejection: js.PromiseRejection, page: *Page) !void {
if (comptime IS_DEBUG) {
log.debug(.js, "unhandled rejection", .{
.value = rejection.reason(),
@@ -580,13 +589,20 @@ pub fn unhandledPromiseRejection(self: *Window, rejection: js.PromiseRejection,
});
}
const event_name, const attribute_callback = blk: {
if (no_handler) {
break :blk .{ "unhandledrejection", self._on_unhandled_rejection };
}
break :blk .{ "rejectionhandled", self._on_rejection_handled };
};
const target = self.asEventTarget();
if (page._event_manager.hasDirectListeners(target, "unhandledrejection", self._on_unhandled_rejection)) {
const event = (try @import("event/PromiseRejectionEvent.zig").init("unhandledrejection", .{
if (page._event_manager.hasDirectListeners(target, event_name, attribute_callback)) {
const event = (try @import("event/PromiseRejectionEvent.zig").init(event_name, .{
.reason = if (rejection.reason()) |r| try r.temp() else null,
.promise = try rejection.promise().temp(),
}, page)).asEvent();
try page._event_manager.dispatchDirect(target, event, self._on_unhandled_rejection, .{ .context = "window.unhandledrejection" });
try page._event_manager.dispatchDirect(target, event, attribute_callback, .{ .context = "window.unhandledrejection" });
}
}
@@ -813,6 +829,7 @@ pub const JsApi = struct {
pub const onpopstate = bridge.accessor(Window.getOnPopState, Window.setOnPopState, .{});
pub const onerror = bridge.accessor(Window.getOnError, Window.setOnError, .{});
pub const onmessage = bridge.accessor(Window.getOnMessage, Window.setOnMessage, .{});
pub const onrejectionhandled = bridge.accessor(Window.getOnRejectionHandled, Window.setOnRejectionHandled, .{});
pub const onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{});
pub const event = bridge.accessor(Window.getEvent, null, .{ .null_as_undefined = true });
pub const fetch = bridge.function(Window.fetch, .{});