Google, Microsoft, Okta and most modern login pages never fire a DOM
submit event — they POST via fetch/XHR and manipulate the DOM directly.
The old approach (submit listener) missed every one of them.
Three triggers now cover the common outcomes after a successful login:
A. MutationObserver — password field removed from DOM. Primary path.
Most SPAs tear down the login form on success.
B. History API interception (pushState / replaceState / popstate) —
URL changes before the DOM settles. 100 ms grace period, then
checks whether the field is still present before firing.
C. Traditional form submit — kept for regressions (non-SPA sites).
Clears pwCapture so Trigger A cannot double-fire.
Failed-login heuristic: if the password field is still in the DOM when a
trigger fires, the credential is not offered. Avoids false positives on
wrong-password attempts.
`filling` flag prevents the capture loop from running while __workAppFill
is programmatically writing into fields.
Two bugs made 'Fill saved password' do nothing:
1. HOST MISMATCH. Saving used location.hostname at submit time (e.g.
accounts.google.com); filling derived the host from the app's
configured URL (mail.google.com). The Keychain lookup always found
nothing for any service with a separate auth domain. Fixed by storing
the save-time hostname in App.saved_host and reading it back at fill
time, falling back to default_scope for entries saved before this.
2. SILENT FAILURE. Every error path in the fillpw sentinel called only
eprintln!, which is invisible to the user. A feature that fails silently
is indistinguishable from one that is not wired up. Fixed by emitting a
fill-error event; the shell shows it as a toast in the nav column, which
is left of the stage and therefore always above native views.
On a machine where rustup has no default toolchain, tauri-cli calls
`cargo metadata` without specifying one and panics in its metadata thread
with no useful error — just an abort. The file lives at the project root
(not src-tauri/) because tauri-cli invokes cargo from there; rustup walks
up from the CWD, so the root copy is the one that gets picked up.
Also notes this in FORK.md so a future forker knows what they would hit
and why the file must exist.