# v0.6.1 — A "Leave page?" prompt can no longer wedge the server

## TL;DR
A `beforeunload` prompt raised during `navigate_page`, `new_page`, `close_page` or `navigate_history` used to hang the WebDriver command forever and take the entire MCP server down with it. `accept_dialog` and `dismiss_dialog` could not clear it. Two independent guards now prevent and contain that failure: the prompt is suppressed at the platform level, and every unload-triggering command is time-bounded with an error that names the recovery.

## Why
Reproduced live on a shared instance: arm a `beforeunload` handler, grant sticky activation with a real click, then navigate cross-document. The result was not a slow call — it was total loss of the process:

| Call | Result before this release |
|---|---|
| `navigate_page` | never returns; the proxy in front marks the whole upstream dead |
| `page_info` | `Error: unknown error` — no hint a modal exists |
| `dismiss_dialog` | `Error: unknown error` — **the documented escape hatch also fails** |
| `list_pages` / `select_page` | still work (they never touch the blocked tab's content) |

Because that instance multiplexes many unrelated servers behind one proxy entry, a single wedged browser tab took **120 tools** offline. The agent driving it had no way to learn a dialog was the cause.

Script dialogs (`alert`/`confirm`/`prompt`) were never affected: geckodriver's default `unhandledPromptBehavior` of *dismiss and notify* auto-clears those and reports `Unexpected confirm dialog detected`. `beforeunload` is the gap that behaviour does not cover.

## What changed

**Prevention — `dom.disable_beforeunload`.** Set once per session, lazily, before the first unload-triggering command. Page `beforeunload` handlers still run, so analytics and state-saving side effects are unaffected; only the modal is suppressed.

Neutralising the handlers from page JavaScript was evaluated and rejected: `beforeunload` is dispatched at `window`, where listeners run in registration order regardless of the capture flag, so an injected listener cannot pre-empt one the page registered earlier — and nothing can un-cancel an event another listener already cancelled. The pref is the only reliable suppression point.

**Containment — navigation watchdog.** `navigate_page`, `new_page`, `close_page` and `navigate_history` are bounded by `--nav-timeout-ms` (default 20000, env `FIREFOX_NAV_TIMEOUT_MS`, `0` disables). On timeout the call rejects with the cause and the fix rather than hanging:

> `navigate_page → https://… did not finish within 20000ms. The tab is most likely blocked by a modal the driver cannot reach - a "Leave page?" prompt is the usual cause. accept_dialog and dismiss_dialog cannot clear that one. Recover by selecting a different tab with select_page, then dropping the blocked tab with close_page.`

The watchdog stays useful even where the pref cannot be set (no `MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1`): suppression degrades to best-effort and is never fatal, but the bound still holds.

## Also fixed
Both found while building the above.

- **`get_firefox_prefs` returned nothing but errors.** The privileged read was assembled as `` `return ${script}` `` where `script` began with a newline, so automatic semicolon insertion terminated the `return` before the expression. Every lookup failed with `Cannot read properties of null (reading 'exists')`. Now trimmed.
- **Privileged calls could strand the driver in chrome scope.** Content scope was restored only when the session already had a remembered content context, so a `get`/`set_firefox_prefs` issued before any tab was selected left every later content tool failing with `Only supported in content context`. Restoration is now unconditional and falls back to any available content tab.

## Files changed
- `src/utils/unload-guard.ts` *(new)* — memoised, best-effort prompt suppression
- `src/utils/nav-watchdog.ts` *(new)* — timeout + `NavigationStalledError`
- `src/utils/chrome-scope.ts` *(new)* — privileged execution that always restores content scope
- `src/tools/pages.ts`, `src/tools/utilities.ts` — guard + watchdog on the four unload paths
- `src/tools/firefox-prefs.ts` — semicolon-insertion fix, unconditional scope restore
- `src/cli.ts`, `src/index.ts` — `--nav-timeout-ms` wiring

No breaking changes. No tool signatures altered. Bridge package untouched.
