Two-part cleanup:
- CHANGELOG.md #18 entry: rewrite the symptom description without
naming the specific third-party site that originally reported it.
The technical root cause and fix are unchanged.
- README.md: remove the entire "Known issues" section. Its only entry
was the headless=True alt-desktop crash from #18, which was fully
fixed in 0.1.7 / firefox-7. Leaving the workaround instructions in
the README would have misled users into adopting them
unnecessarily. New issues can be added back as they're found; the
default state is "no known issues".
Pre-push: 402 unit + integration tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pin fission.webContentIsolationStrategy=0 (IsolateNothing) in baseline
prefs. FF150 ships strategy=1 (IsolateEverything) by default, which
site-isolates every cross-origin iframe into a separate webIsolated
content process even when fission.autostart=False - different from
FF146 Playwright behavior. The parent's Juggler FrameTree then sees
the iframe placeholder with no docShell, no URL and a stale execution
context, so content_frame() returns None, frame.evaluate() throws
cross-origin permission errors, and frame_locator(...).click() times
out. Reproduced with a local cross-origin HTTP harness (two 127.0.0.1
ports = two SOP origins).
The fix is a single pref because that's the level the original Juggler
code paths assume - vanilla Playwright Firefox 146 ran with the looser
default. Disabling site-isolation costs nothing for a single-user
puppet browser; process-per-browser/profile isolation is unaffected.
Caller can A/B per session via
extra_prefs={"fission.webContentIsolationStrategy": 1}.
Tests: tests/test_cross_origin_iframe.py adds 4 unit sentinels (pref
in baseline, survives translate_profile_to_prefs, extra_prefs override
works) and 5 e2e sentinels that spin up two local HTTP servers on
random free ports and verify the four protocol operations that
regressed (page.frames URL tracking, content_frame(), frame.evaluate(),
frame_locator(...).locator(...)) plus dispatch_event('click')
end-to-end, for plain, sandboxed and titled iframes. A future FF
upgrade or A/B flipping the pref will fail the suite before shipping.
Verified clean: pytest -m unit (342 passed), fppro_full.py (ALL
CRITICAL FLAGS CLEAN), fppro_consistency.py (visitor_id stable).
BINARY_VERSION stays firefox-7 - Python-only release.
Issue: https://github.com/feder-cr/invisible_playwright/issues/20
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps BINARY_VERSION to firefox-7 which ships the real fix for issue #18
(id.sky.com tab crash on Windows headless=True).
firefox-7 contents:
- Canvas2D getImageData stealth spoof moved from read-only mapped surface
to JS Uint8ClampedArray writable buffer (commit 2e17b4871f93 in
invisible-firefox). Fixes the segfault on GPU-backed canvases that hit
during page unload.
Paired with the wrapper-side security.sandbox.content.level=4 workaround
shipped in 2e0adbd (this repo), the combination resolves both halves of
issue #18: cross-process navigation crash in loop AND segfault at close.
End-to-end tested with InvisiblePlaywright headless=True + UK proxy on
id.sky.com: page survives 30s+, no crash in loop, no crash at close.
firefox-6 was rolled back when its partial-fix hypothesis turned out to
be the wrong root cause. Skipping straight to firefox-7.
- Add profile_dir= kwarg to InvisiblePlaywright (sync + async).
Maps to firefox.launch_persistent_context(); returns a BrowserContext.
Cookies / localStorage / extensions / cache / prefs all persisted.
- Drop the firefox-4 era workaround that filtered locale + timezone_id
out of the persistent kwargs. firefox-5 ships the C++
docShell.overrideTimezone IDL method (50 LOC patch in
docshell/base/nsIDocShell.idl + nsDocShell.cpp, see patch.md
section 19 in feder-cr/invisible-firefox), so per-realm overrides
land without crashing the launch handshake.
- Bump BINARY_VERSION firefox-4 -> firefox-5.
- Sentinel unit tests added: persistent kwargs MUST include locale +
timezone_id (defends against re-introducing the workaround) and
must NOT include timezone_id when timezone="" is the "host TZ" sentinel.
Validation: smoke test against the local firefox-5 build, persistent
context UP in 21s (was 180s timeout), Intl.timeZone == Europe/London,
hardwareConcurrency / screen / DPR / locale all reflect the PIN.
Marker release for the #15 checksum parser fix that landed on main.
First-time fetch was broken for every user since checksums.txt
started shipping with sha256sum's binary-mode `*` prefix.
Fixes#13: every page that threw an uncaught JS error (bunny.net is the
reporter's repro) crashed the Playwright client with
"TypeError: Cannot read properties of undefined (reading 'url')".
Root cause: upstream Playwright Juggler added a required `location` field
to the Page.uncaughtError event in their 2026-05-07 patch roll; our fork
was carrying the pre-roll schema in every firefox-N build, and any
Playwright client released after the roll read pageError.location.url
strictly.
Fix is in the patched binary (feder-cr/invisible-firefox@1ba55d93), JS-only
inside chrome/juggler/. xul.dll and firefox.exe are byte-identical to
firefox-3 — only the Juggler protocol files change.
BINARY_VERSION bumped firefox-3 → firefox-4. Package version 0.1.2 → 0.1.4
(0.1.3 was never published to PyPI; the changelog entry is kept as
historical record of the firefox-3 binary release).
BINARY_VERSION bumped from firefox-2 to firefox-3. Both archives on this
release are built from a clean clone of feder-cr/invisible-firefox#stealth/150
(the consolidated source-of-truth fork) at commit 68906f1f9c55.
Changes vs firefox-2:
- Proper C++ jugglerSendMouseEvent (replaces JS workaround from 0.1.1)
- C1+C2: setDownloadInterceptor re-landed
- C4: 5 nsIDocShell stealth attributes
- C5: launcher + wmain juggler-pipe handle inheritance (Win)
- C6: juggler-navigation observer notifications
- C7 (partial): nsIDocShell.languageOverride storage stub
- First native Linux build with the full patch series (previous releases
shipped a stale Linux archive copied from earlier builds)
Verified with the 4-test smoke gate on both Windows and Linux:
- test_launch (pipe stays connected, gap C5 sentinel)
- test_new_page (Page.ready fires, gap C6 sentinel)
- test_mouse (jugglerSendMouseEvent C++)
- test_stealth (navigator.webdriver=false + sannysoft check)
The 0.1.1 release shipped the source-level fix for issue #9 (every
mouse path failing on FF150) but kept BINARY_VERSION at firefox-1
because the archive itself hadn't been refreshed yet. firefox-2 is
now live on GitHub Releases with the JS hot-swap applied to both
the Windows zip and the Linux tarball; users picking up 0.1.2 will
fetch the patched archive on first run.
Archive integrity verified on both platforms before publishing
(Windows boot test, Linux file-level checks, 21/21 assertions).
The Juggler JS in upstream Playwright calls win.windowUtils.jugglerSendMouseEvent
at four sites, but when the Juggler was ported FF146 -> FF150 the matching C++
patch to nsIDOMWindowUtils.idl + nsDOMWindowUtils.cpp was dropped. Result: every
page.mouse.*, page.click(selector), locator.click(), page.hover(), mouse.wheel()
threw "win.windowUtils.jugglerSendMouseEvent is not a function" on first call.
The fix is shipped in the patched Firefox source (feder-cr/firefox-stealth):
six call sites in juggler/protocol/PageHandler.js and juggler/content/PageAgent.js
were swapped to win.synthesizeMouseEvent — a Mozilla chrome-scope helper that is
already present in FF150. scrollRectIntoViewIfNeeded was also guarded at the two
PageHandler.js sites where it was called unconditionally on the FF150
_linkedBrowser, which no longer exposes that method.
This invisible_playwright release adds the regression suite in tests/test_mouse.py
(12 cases inspired by microsoft/playwright-python/tests/async/test_click.py),
the CHANGELOG, and the version bump. The patched Firefox archive on GitHub
Releases must be refreshed before users actually receive the fix; the
BINARY_VERSION bump to firefox-2 will land with that asset.
Reporter: @trob9 (issue #9) — provided ready-to-apply JS patches, 4-line minimal
repro, and confirmed reCAPTCHA v3 = 0.90 holds after the swap.