diff --git a/src/Notification.zig b/src/Notification.zig index 6adbfd81..e7bf1e33 100644 --- a/src/Notification.zig +++ b/src/Notification.zig @@ -83,6 +83,7 @@ const EventListeners = struct { http_request_auth_required: List = .{}, http_response_data: List = .{}, http_response_header_done: List = .{}, + javascript_dialog_opening: List = .{}, }; const Events = union(enum) { @@ -102,6 +103,7 @@ const Events = union(enum) { http_request_done: *const RequestDone, http_response_data: *const ResponseData, http_response_header_done: *const ResponseHeaderDone, + javascript_dialog_opening: *const JavascriptDialogOpening, }; const EventType = std.meta.FieldEnum(Events); @@ -185,6 +187,12 @@ pub const RequestFail = struct { err: anyerror, }; +pub const JavascriptDialogOpening = struct { + url: [:0]const u8, + message: []const u8, + dialog_type: []const u8, +}; + pub fn init(allocator: Allocator) !*Notification { const notification = try allocator.create(Notification); errdefer allocator.destroy(notification); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index fb3ec8f8..582ed659 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -903,15 +903,31 @@ pub const JsApi = struct { pub const opener = bridge.property(null, .{ .template = false }); pub const alert = bridge.function(struct { - fn alert(_: *const Window, _: ?[]const u8) void {} - }.alert, .{ .noop = true }); + fn alert(_: *const Window, message: ?[]const u8, page: *Page) void { + page._session.notification.dispatch(.javascript_dialog_opening, &.{ + .url = page.url, + .message = message orelse "", + .dialog_type = "alert", + }); + } + }.alert, .{}); pub const confirm = bridge.function(struct { - fn confirm(_: *const Window, _: ?[]const u8) bool { + fn confirm(_: *const Window, message: ?[]const u8, page: *Page) bool { + page._session.notification.dispatch(.javascript_dialog_opening, &.{ + .url = page.url, + .message = message orelse "", + .dialog_type = "confirm", + }); return false; } }.confirm, .{}); pub const prompt = bridge.function(struct { - fn prompt(_: *const Window, _: ?[]const u8, _: ?[]const u8) ?[]const u8 { + fn prompt(_: *const Window, message: ?[]const u8, _: ?[]const u8, page: *Page) ?[]const u8 { + page._session.notification.dispatch(.javascript_dialog_opening, &.{ + .url = page.url, + .message = message orelse "", + .dialog_type = "prompt", + }); return null; } }.prompt, .{}); diff --git a/src/cdp/CDP.zig b/src/cdp/CDP.zig index 09fa8324..89efcef1 100644 --- a/src/cdp/CDP.zig +++ b/src/cdp/CDP.zig @@ -431,6 +431,7 @@ pub const BrowserContext = struct { try notification.register(.page_frame_created, self, onPageFrameCreated); try notification.register(.page_dom_content_loaded, self, onPageDOMContentLoaded); try notification.register(.page_loaded, self, onPageLoaded); + try notification.register(.javascript_dialog_opening, self, onJavascriptDialogOpening); } pub fn deinit(self: *BrowserContext) void { @@ -641,6 +642,11 @@ pub const BrowserContext = struct { return @import("domains/page.zig").pageLoaded(self, msg); } + pub fn onJavascriptDialogOpening(ctx: *anyopaque, msg: *const Notification.JavascriptDialogOpening) !void { + const self: *BrowserContext = @ptrCast(@alignCast(ctx)); + return @import("domains/page.zig").javascriptDialogOpening(self, msg); + } + pub fn onHttpResponseHeadersDone(ctx: *anyopaque, msg: *const Notification.ResponseHeaderDone) !void { const self: *BrowserContext = @ptrCast(@alignCast(ctx)); defer self.resetNotificationArena(); diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index cf3cdd7d..c306ead7 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -48,6 +48,7 @@ pub fn processMessage(cmd: *CDP.Command) !void { close, captureScreenshot, getLayoutMetrics, + handleJavaScriptDialog, }, cmd.input.action) orelse return error.UnknownMethod; switch (action) { @@ -63,6 +64,7 @@ pub fn processMessage(cmd: *CDP.Command) !void { .close => return close(cmd), .captureScreenshot => return captureScreenshot(cmd), .getLayoutMetrics => return getLayoutMetrics(cmd), + .handleJavaScriptDialog => return handleJavaScriptDialog(cmd), } } @@ -642,6 +644,32 @@ fn sendPageLifecycle(bc: *CDP.BrowserContext, name: []const u8, timestamp: u64, }, .{ .session_id = session_id }); } +// https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-handleJavaScriptDialog +fn handleJavaScriptDialog(cmd: *CDP.Command) !void { + // Dialogs auto-dismiss in headless mode. By the time the CDP client + // sends this command, the dialog has already returned and there is + // no pending dialog to accept or dismiss. + _ = try cmd.params(struct { + accept: bool, + promptText: ?[]const u8 = null, + }); + return cmd.sendError(-32000, "No dialog is showing", .{}); +} + +// https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening +pub fn javascriptDialogOpening(bc: anytype, event: *const Notification.JavascriptDialogOpening) !void { + const session_id = bc.session_id orelse return; + var cdp = bc.cdp; + + try cdp.sendEvent("Page.javascriptDialogOpening", .{ + .url = event.url, + .message = event.message, + .type = event.dialog_type, + .hasBrowserHandler = false, + .defaultPrompt = "", + }, .{ .session_id = session_id }); +} + const LifecycleEvent = struct { frameId: []const u8, loaderId: ?[]const u8,