microsoft / microsoft/playwright
[Bug]: Firefox page.goto never resolves on the first navigation to a page with COOP same-origin
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 96.3k
- Forks
- 6.5k
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 180
Description
Version
1.63.0 (Firefox 155.0, build 1543)
Steps to reproduce
Save this as repro.mjs in a folder with playwright installed. Run node repro.mjs coop 200, then node repro.mjs none 200 to compare.
import http from 'node:http';
import { firefox } from 'playwright';
const mode = process.argv[2] ?? 'coop';
const trials = Number(process.argv[3] ?? 200);
const html = `<!doctype html><title>t</title><h1>hello</h1>
<script type="module">history.replaceState({ key: 1 }, '', location.href);</script>`;
const server = http.createServer((req, res) => {
const headers = { 'content-type': 'text/html' };
if (mode === 'coop') headers['cross-origin-opener-policy'] = 'same-origin';
res.writeHead(req.url === '/' ? 200 : 404, headers);
res.end(req.url === '/' ? html : '');
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const url = `http://127.0.0.1:${server.address().port}/`;
const browser = await firefox.launch();
let stalls = 0;
let done = 0;
async function worker() {
while (done < trials) {
done++;
const context = await browser.newContext();
const page = await context.newPage();
try {
await page.goto(url, { timeout: 10_000 });
} catch (error) {
if (String(error).includes('Timeout')) stalls++;
else throw error;
}
await context.close();
}
}
await Promise.all([worker(), worker(), worker(), worker()]);
console.log(`mode=${mode} trials=${done} goto timeouts=${stalls}`);
await browser.close();
server.close();
Expected behavior
Every goto resolves once the page has loaded.
Actual behavior
With the COOP header some navigations never resolve and time out, even though the page loaded fine. On my machine that was 12 of 200. Without the header it was 0 of 200.
When it happens, every request for the page has already finished. A later page.waitForLoadState('load') returns right away and says load already fired. Starting a second goto on the same page gets it moving again.
Additional context
This looks like the same problem as #42183, which was closed without a fix. Here is what I found digging into it.
A new page starts on about:blank. Navigating it to a document with Cross-Origin-Opener-Policy: same-origin makes Firefox replace the browsing context. Playwright's Firefox profile sets fission.webContentIsolationStrategy to 0, so the new context stays in the same content process.
Juggler creates a new SimpleChannel for the new context. Its uid is 'process-' + Services.appinfo.processID (juggler/content/main.js), which is the same as the old channel's uid, and message ids start again at 1. The browser side only clears its cache of answered request ids when the uid changes. So if the old channel's last response has not been acked yet, the new channel's message with the same id gets answered from that cache and is never delivered.
In our runs the lost message is usually Page.navigationCommitted, so goto waits forever. If the number of earlier messages shifts by one, a different message gets lost instead (executionContextCreated), and page.evaluate hangs.
Setting browser.tabs.remote.useCrossOriginOpenerPolicy to false through firefoxUserPrefs makes it go away. In a longer run that was 0 hangs in 111, against 25 in 111 without it. Sending COEP as well also makes it go away, since the new context then lands in a new process.
A uid that is unique to each channel, for example one that includes the browsing context id, looks like it would fix it. The replacement would then go through the same handshake a cross-process swap already uses. I have not built or tested that change.
Environment
- OS: Windows 11
- Node.js: 24.14.1
- Playwright: 1.63.0, Firefox 155.0
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the supplied repro.mjs with the COOP and non-COOP modes to reproduce the Firefox navigation timeout. Then inspect juggler/content/main.js and the channel request-id cache and handshake involved when Firefox replaces the browsing context. Done means repeated COOP navigations complete without timeouts while the comparison case remains working.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, typescript
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 47/100