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 { 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 promise_handle = v8.v8__PromiseRejectMessage__GetPromise(&message_handle).?;
const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?; const v8_isolate = v8.v8__Object__GetIsolate(@ptrCast(promise_handle)).?;
const isolate = js.Isolate{ .handle = v8_isolate }; const isolate = js.Isolate{ .handle = v8_isolate };
@@ -508,7 +513,7 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v
}; };
const page = ctx.page; const page = ctx.page;
page.window.unhandledPromiseRejection(.{ page.window.unhandledPromiseRejection(promise_event == v8.kPromiseRejectWithNoHandler, .{
.local = &local, .local = &local,
.handle = &message_handle, .handle = &message_handle,
}, page) catch |err| { }, page) catch |err| {

View File

@@ -67,7 +67,8 @@ _on_pageshow: ?js.Function.Global = null,
_on_popstate: ?js.Function.Global = null, _on_popstate: ?js.Function.Global = null,
_on_error: ?js.Function.Global = null, _on_error: ?js.Function.Global = null,
_on_message: ?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, _current_event: ?*Event = null,
_location: *Location, _location: *Location,
_timer_id: u30 = 0, _timer_id: u30 = 0,
@@ -222,6 +223,14 @@ pub fn setOnMessage(self: *Window, setter: ?FunctionSetter) void {
self._on_message = getFunctionFromSetter(setter); 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 { pub fn getOnUnhandledRejection(self: *const Window) ?js.Function.Global {
return self._on_unhandled_rejection; 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); 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) { if (comptime IS_DEBUG) {
log.debug(.js, "unhandled rejection", .{ log.debug(.js, "unhandled rejection", .{
.value = rejection.reason(), .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(); const target = self.asEventTarget();
if (page._event_manager.hasDirectListeners(target, "unhandledrejection", self._on_unhandled_rejection)) { if (page._event_manager.hasDirectListeners(target, event_name, attribute_callback)) {
const event = (try @import("event/PromiseRejectionEvent.zig").init("unhandledrejection", .{ const event = (try @import("event/PromiseRejectionEvent.zig").init(event_name, .{
.reason = if (rejection.reason()) |r| try r.temp() else null, .reason = if (rejection.reason()) |r| try r.temp() else null,
.promise = try rejection.promise().temp(), .promise = try rejection.promise().temp(),
}, page)).asEvent(); }, 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 onpopstate = bridge.accessor(Window.getOnPopState, Window.setOnPopState, .{});
pub const onerror = bridge.accessor(Window.getOnError, Window.setOnError, .{}); pub const onerror = bridge.accessor(Window.getOnError, Window.setOnError, .{});
pub const onmessage = bridge.accessor(Window.getOnMessage, Window.setOnMessage, .{}); 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 onunhandledrejection = bridge.accessor(Window.getOnUnhandledRejection, Window.setOnUnhandledRejection, .{});
pub const event = bridge.accessor(Window.getEvent, null, .{ .null_as_undefined = true }); pub const event = bridge.accessor(Window.getEvent, null, .{ .null_as_undefined = true });
pub const fetch = bridge.function(Window.fetch, .{}); pub const fetch = bridge.function(Window.fetch, .{});