Skip to content

Don't hold opening a Firefox tab on its window taking focus - #33

Merged
olehwebdev merged 1 commit into
mainfrom
bugfix/firefox-activate-wait
Sep 29, 2026
Merged

olehwebdev merged 1 commit into
mainfrom
bugfix/firefox-activate-wait

Conversation

@olehwebdev

Copy link
Copy Markdown
Owner

What and why

CI on main failed after #32 merged: driven.firefox's first test ("launches it with a profile of its own…") ran into its 30 s timeout. The later tests in that file passed, including the captures, which bring the tab to the front again.

Opening a page in a driven Firefox navigates the tab, then brings it to the front with browsingContext.activate. Firefox (remote/…/root/browsingContext.sys.mjs and WindowManager.focusWindow) selects the tab at once. It answers only once its window has fired both activate and focus, and it waits for them with no limit. A window still starting up headless may never fire them, so open() could hang on a freshly launched Firefox even though the page itself loaded. Once the window is active, later activations answer at once, which matches the rest of the file passing.

  • DrivenFirefox.activate now waits at most ACTIVATE_WAIT_MS (2 s) for that answer, then goes on. The tab is already selected by then. A refusal that comes sooner, such as a closed tab, still fails it; an answer that comes later is ignored.
  • driven.firefox.test.ts:
    • Its waits give up before vitest's own timeout, with the tabs as last read in the error. Before, both limits were 30 s, so vitest's bare "Test timed out" always won and said nothing of where the test stalled.
    • The first test, a cold launch, gets 60 s: the app itself gives Firefox 30 s to open its port.

I couldn't make the hang happen here: about 30 fresh launches of Firefox 157, 25 of them under CPU load, all answered activate within about 20 ms. So the cause above comes from reading Firefox's source and from the CI run, not from a reproduction. If it stalls again, the new wait errors will say what the tab showed.

How it was tested

  • New unit test (test/unit/bidi.test.ts): a stand-in Firefox that never answers activate. open() resolves once the wait is up; without the fix it times out. A second test checks that a refusal still comes through, and that an unknown tab is still refused as closed.
  • Real Firefox 157: driven.firefox.test.ts passed 4 runs in a row, and driven-firefox.e2e and browsers.e2e passed in the built app.

Checklist

  • Comes from a git flow branch (feature/…, bugfix/…) into main, and does one thing
  • npm run typecheck, npm run lint:fsd, npm run lint:structure, npm run lint, npm run lint:unused, npm run lint:duplicates, npm run lint:secrets, npm test and npm run test:e2e pass (1325 unit and integration tests, 139 end-to-end)
  • docs/SPEC.md describes any behaviour this changes: none. A tab is still brought to the front; only the wait for Firefox's answer is bounded.
  • User-visible changes are noted under [Unreleased] in CHANGELOG.md: none. Opening pages in Firefox hasn't been released yet.

Generated by Claude Code

Firefox answers browsingContext.activate only once its window has taken
focus as well as selected the tab, and waits for that without a limit. A
window still starting up headless may never fire the events it waits for,
so opening the first page in a freshly launched Firefox could hang: the
page loaded, but open() never resolved. CI caught it on main after #32
merged, in driven.firefox's first test.

Bringing a tab to the front now waits two seconds at most for that answer,
then goes on (the tab is in front by then); a refusal that comes sooner
still fails it. A unit test drives a stand-in Firefox that never answers.

The integration test's waits now give up before the test's own timeout,
saying what the tabs showed, and its first test, a cold launch, has room
for the 30 seconds the app gives Firefox to open its port.
@olehwebdev
olehwebdev merged commit 601a049 into main Sep 29, 2026
1 check passed
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