Skip to content

fix(settings-ui): replace the native JS dialogs, which do nothing on macOS - #18

Open
maerlin wants to merge 1 commit into
karem505:masterfrom
maerlin:fix/macos-dialogs
Open

fix(settings-ui): replace the native JS dialogs, which do nothing on macOS#18
maerlin wants to merge 1 commit into
karem505:masterfrom
maerlin:fix/macos-dialogs

Conversation

@maerlin

@maerlin maerlin commented Aug 8, 2026

Copy link
Copy Markdown

Problem

Five controls in the settings UI do nothing at all on macOS:

control call what happens on macOS
Accounts → Rename prompt() returns null, nothing drawn
Accounts → Remove confirm() returns false, nothing drawn
Security → Change password… prompt() returns null
Security → Disable… prompt() returns null
Lock screen → "Forgot password? Reset…" confirm() returns false

Every alert(...) error path is swallowed the same way, so failures from add_account, lock_app, set_biometric_enabled and the lock options are invisible.

The cause is not in this repo. WKWebView shows a JavaScript dialog only if the host implements the matching WKUIDelegate panel method (runJavaScriptTextInputPanelWithPrompt:, runJavaScriptConfirmPanelWithMessage:, runJavaScriptAlertPanelWithMessage:). wry implements none of them: src/wkwebview/class/wry_web_view_ui_delegate.rs covers only the open-file panel, media-capture permission, and createWebViewWithConfiguration. WebKit's documented behaviour when the delegate omits these is to complete immediately with the default value, without drawing anything and without raising an error.

Windows (WebView2) and Linux (WebKitGTK) answer these calls natively, which is why the same code works everywhere else and the breakage is macOS-only.

Change

settings-ui/dialog.js and dialog.css: alert, confirm and prompt built from page elements. Same shape as the natives, but async, so call sites become await Dlg.confirm(...) instead of if (confirm(...)).

Behaviour:

  • Escape, Cancel, or a click on the backdrop dismisses; a click inside the box does not.
  • Enter in the text field accepts.
  • Tab is trapped inside the dialog, and focus is restored to whatever opened it.
  • An empty required field shows an inline error rather than resolving an empty string, so prompt cancelling (null) stays distinguishable from an empty answer.
  • Passwords are passed through untrimmed. window.prompt does not trim, and trimming would silently mangle a password with edge whitespace.
  • One dialog at a time; the styles stand on their own so both index.html and lock.html can load them without extending either page's sheet.

Every call site moves over, and the error paths that used alert() now name the action that failed ("Could not rename", "Could not disable the lock") instead of showing a bare message.

Testing

settings-ui/dialog.test.mjs: 9 cases, 25 assertions, run against a stubbed DOM in the same zero-dependency style as bridge.test.mjs and comboToAccelerator.test.mjs. Added as a step in check.yml.

The test exists because of the failure mode itself: a regression here is invisible on the Linux and Windows CI runners and surfaces only as a dead button on macOS.

Also verified by hand in a release build on macOS 15 (Apple Silicon): rename, remove, change password, disable lock, and the reset link all work now.

Alternative considered

tauri-plugin-dialog gives native ask/message, but has no text-input prompt, so rename and the password flows would still need in-page UI. Doing all of them one way keeps the behaviour consistent across the three platforms and adds no dependency.

…macOS

Rename, Remove, Change password, Disable lock, and the lock screen's reset link all went through window.prompt, confirm or alert. WKWebView shows a JavaScript dialog only when the host implements the matching WKUIDelegate panel method, and wry implements none of them. On macOS prompt() therefore returns null, confirm() returns false, and alert() draws nothing, with no error anywhere. Each of those controls was a dead button. Windows (WebView2) and Linux (WebKitGTK) answer the calls natively, which is why the same code works there.

Add settings-ui/dialog.js and dialog.css: alert, confirm and prompt built from page elements, with the same shape as the natives but async (`await Dlg.confirm(...)`). They close on Escape or on a click outside, keep Tab inside the dialog, restore focus to whatever opened them, and refuse an empty required field instead of resolving an empty string. Passwords are passed through untrimmed.

The settings window and the lock screen both load them, and every call site moves over. The error paths that used alert() now say which action failed.

dialog.test.mjs checks the resolve semantics against a stubbed DOM and runs in CI beside the other zero-dependency node tests. Without it, a regression here stays invisible on the Linux and Windows runners and surfaces only as a dead button on macOS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant