Skip to content

Hold JavaScript dialogs open for the agent or the person to answer - #958

Merged
omartelo merged 5 commits into
mainfrom
feat/browser-dialogs
Oct 28, 2026
Merged

omartelo merged 5 commits into
mainfrom
feat/browser-dialogs

Conversation

@omartelo

@omartelo omartelo commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A JavaScript dialog froze the session browser. Measured before (Chromium 155, live test): a click on a button that calls alert('hi') failed with context deadline exceeded after 30.0 s, and every read after it hung another 30.0 s. Nothing handled Page.javascriptDialogOpening.

Now a dialog the page opens (alert, confirm, prompt, beforeunload) stays open until the agent or the person answers it:

  • Agent: the call that opened it answers at once (about 0.16 s) with the page as it stood and a dialog open: line. Any other call on that page fails, naming the dialog. The new browser_dialog tool (lich browser dialog accept|dismiss, text for a prompt) answers it and returns the page after.
  • Person: the pane shows the dialog as a card over the top of the page (option B of the mockup), naming the site, with OK/Cancel (Stay/Leave on a beforeunload) and a field for a prompt. Enter is OK/Leave and Esc is Cancel/Stay. A dialog the person answered reaches the agent's next page answer once, as dialog answered: confirm "Delete?" -> dismissed.
  • Cursor: when the agent answers, the window plays its cursor pressing the card's button, as it does for back and forward. The card stays until that press has played.
  • Whoever answers first wins. The other side's answer is refused, and the window ignores that refusal without logging it.

beforeunload

Accepting a beforeunload sooner than about 450 ms after it opened makes Chromium abort the navigation it held (net::ERR_ABORTED about a second in), reload the page and ask again. Measured on Chrome for Testing 155 and Helium 0.19.2.1: 3 of 3 aborted at 300 ms, 3 of 6 at 400 ms, none of 30 from 500 ms on. An accept is therefore held until one second after the dialog opened. An agent's or a person's answer already takes longer than that.

Page answer language

The prose of a page answer now comes from the prompt catalog in all seven languages: the element-list legend, the filled and checked marks, the more-below note, a running download, a timed-out wait, the count of further console errors, and what an open dialog asks of the agent. The line tags the tool descriptions name (url:, text:, downloaded:, console error:, dialog open: and the rest) stay English. pageText takes the language. That is why the existing pageText tests gained a prompt.English argument; no assertion changed.

Notes

  • New API: browser.AnswerDialog (agent, returns the page) and browser.Answer (window); BrowserState.Dialog, BrowserPageInfo.Dialog and Dialogs; agent acts accept and dismiss. contract.golden is updated.
  • The chromePage gate: run now fails with errDialogOpen at once while a dialog is open, and as soon as one opens during a call. Calls that do not touch the page's script (screencast acks, resize, cookies, focus, the answer itself) go through runFree.
  • TestMCPListsEveryTool gained browser_dialog, because the tool list is the contract.
  • A page holding a dialog cannot lay out: Emulation.setDeviceMetricsOverride and Page.captureScreenshot wait on the dialog (measured on both browsers). A pane opened during a dialog therefore starts its screencast at the page's current size, which still draws, and lays the page out once the dialog closes. Before, the pane stayed black. Screenshots fail until the dialog is answered.
  • The window's own calls on a held page do nothing instead of failing, and the person answering a dialog the agent already answered is not an error, so the window does not match error text.
  • The agent cursor's queue never drops an answer to the dialog, so the card always waits for its press. The UI gallery shows the new Lit button state.
  • Ceilings: a dialog nobody answers holds the page; a pane opened meanwhile shows it at its old size; the accept hold on beforeunload.

Test plan

  • TestChromeADialogWaitsForTheAgentsAnswer (live): alert, confirm (dismiss), prompt (custom text) and leaving through a beforeunload. 5 of 5 runs on Helium and on Chrome for Testing 155.
  • Unit tests: a click that opens a dialog, a call on a held page, the agent's and the person's answers, the prompt default, the answered cap, the gate; pageText in every language; the CLI verb and the MCP tool.
  • TestChromeAPaneOpenedOnADialogShowsThePage (live): a screencast started on a held page delivers a frame at once, and the page takes the pane's width after the answer. 5 of 5 on both browsers.
  • Native: card from the store, keep-until-pressed, button per type and act, key mapping, the cursor queue keeping a dialog answer.
  • Rig (Xvfb, slice backend): the confirm and prompt cards in the light and dark themes; the agent's accept and dismiss light the OK and Cancel buttons, show the press ring, and the card goes.
  • gofmt, go vet ./..., go test ./... green for the backend and for native/.

@omartelo
omartelo force-pushed the feat/browser-dialogs branch from 8f91c79 to ae2a66d Compare October 28, 2026 15:16
@omartelo omartelo changed the title Answer JavaScript dialogs in the session browser Hold JavaScript dialogs open for the agent or the person to answer Oct 28, 2026
@omartelo
omartelo force-pushed the feat/browser-dialogs branch from ae2a66d to 9c44545 Compare October 28, 2026 15:38
…swers it

A dialog the page opens now waits for an answer instead of freezing the
page behind it for the 30 s pageWait. The agent's call that opened it
answers at once with a dialog open: line, every other call on that page
fails naming it, and browser_dialog (lich browser dialog) answers it,
which the window plays as the agent's cursor pressing the card's button.
The window shows it and answers through browser.Answer; a dialog the
person answered reaches the agent's next page answer once.

Accepting a beforeunload sooner than a second after it opened is held to
that second: Chrome aborts the navigation it held and reloads the page
otherwise (Chrome for Testing 155 and Helium 0.19.2.1, measured).

The prose of the page answer moves into the prompt catalog; the line tags
the tool descriptions name stay English.
The pane reads the dialog from the browser-state its store already
keeps and draws it as a floating card at the top of the page: who says
it, the message, a prompt's field with its default selected, and OK and
Cancel (Leave and Stay on a beforeunload, OK alone on an alert). Enter
accepts and Esc dismisses (an alert's Esc is OK too); the answer goes
through browser.Answer.

The agent's answer plays as its cursor pressing the card's button, in
turn with its other actions, and the card stays until that press has
played, though the state without the dialog arrives right after.

Input the page refuses while it holds a dialog, and an answer the agent
gave first, are not logged: the card is the answer.
A pane opened while a dialog held the page stayed black: laying the page
out at the pane's size waits on the dialog for the whole pageWait, so the
screencast never started. Resize now fails fast on a held page and lays
it out once the dialog closes; the frames come meanwhile, at the size the
page had (Chrome for Testing 155 and Helium, live test).

The window's own calls on a held page now do nothing instead of failing,
and the person answering a dialog the agent already answered is no
error, so the window no longer recognizes those errors by their text
(quietBrowserError and its test go with it).

The agent cursor's queue keeps an answer to the dialog past its cap, so
the card always waits for its press. The UI gallery shows the lit
buttons.
lich browser info on a page holding a dialog told the reader to call
browser_dialog, the MCP tool. The command line's page answer now names
its own verb, lich browser dialog accept|dismiss, in every language.
@omartelo
omartelo force-pushed the feat/browser-dialogs branch from dec2d12 to 6445211 Compare October 28, 2026 15:50
@omartelo
omartelo merged commit 20d9369 into main Oct 28, 2026
7 checks passed
@omartelo
omartelo deleted the feat/browser-dialogs branch October 28, 2026 16:00
omartelo added a commit that referenced this pull request Oct 28, 2026
TestChromeAPaneOpenedOnADialogShowsThePage failed on every macos-latest
run since #958: headless Chrome on macOS has not painted the page when
Navigate returns, and a dialog held open before the first paint leaves
the screencast no frame to send. The test asserted a frame of a page
that was never drawn, which the contract never promised; a dialog opened
by an agent's click comes after the page has been drawn. Measured on the
runner: with the page drawn first (a screenshot, a prior screencast or
two idle seconds) the held page casts within 25 ms; without, no frame in
15 s.

The gap for a dialog opened before the first paint is written down in
docs/ceilings.md.
omartelo added a commit that referenced this pull request Oct 28, 2026
…#965)

TestChromeAPaneOpenedOnADialogShowsThePage failed on every macos-latest
run since #958: headless Chrome on macOS has not painted the page when
Navigate returns, and a dialog held open before the first paint leaves
the screencast no frame to send. The test asserted a frame of a page
that was never drawn, which the contract never promised; a dialog opened
by an agent's click comes after the page has been drawn. Measured on the
runner: with the page drawn first (a screenshot, a prior screencast or
two idle seconds) the held page casts within 25 ms; without, no frame in
15 s.

The gap for a dialog opened before the first paint is written down in
docs/ceilings.md.
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