Four deadlocks shipped between 2026-04 and 2026-09 -- exact-edge coordinates
(9270618), humanized trajectory points that bypassed the endpoint's guard
(541ffca, #225/#677), a zero-displacement move (16e5a13), and the near edge
(014cc65, #751/#752). Each was fixed by adding one more coordinate guard at
one more call site. That does not converge, for two reasons.
The trigger set is not enumerable. Whether relative y == 0 reaches the
renderer is decided by Math.round(boundingBox.top) < boundingBox.top -- a
rounding accident in the fractional height of browser chrome, which varies
with the spoofed OS. No review catches that.
And every miss costs the whole process. activateAndRun() serializes input on
a chain shared by every tab; EventWatcher.ensureEvent() waited forever. One
missing ack wedged every later input event in the process, permanently, at 0%
CPU with no diagnostic. #677 shows why review is not the answer: restoring the
humanize trajectory meant writing a bounds check, and the one written was a
copy of the pre-#225 form, reintroducing a fixed deadlock one day before it
was re-fixed.
Three changes, in order of leverage.
1. Bound the waits. EventWatcher.ensureEventWithin() gives up instead of
waiting forever; MouseDispatch.sendAcked() uses it, drops the event and
logs the type, coordinate and browser rect. This alone closes all four
historical deadlocks, including on a build with no coordinate fix at all.
The 5s deadline is sized from measurement, not intuition. Over 1000+
dispatches: idle content thread p50 0ms / p99 1ms / max 12ms; a thread
burning 8ms per event p50 8ms / max 12ms. But the ack is delivered FROM
the content main thread, so a page running a 3s synchronous script delayed
a legitimate ack by 2849ms. Block length is page-controlled and unbounded,
and silently dropping real input on a slow page is the #752 symptom, so
the deadline sits above the slowest legitimate ack rather than near the
typical one.
Bounding each ack is not enough to bound the work: a humanized curve is
~110 points in a single activation-chain slot. sendTrajectoryAcked()
abandons the rest of a curve after the first undelivered point -- not a
wall-clock budget, which would false-fire on exactly the slow pages the
deadline exists to tolerate. activateAndRun() carries a 30s backstop for
the other unbounded waits reachable from the same slot
(apz-repaints-flushed, TabSwitchDone, the drag path's waits), none of
which has failed yet.
2. One chokepoint. additions/juggler/input/MouseDispatch.js owns the
relative-to-absolute conversion, the in-viewport predicate and the ack
wait. PageHandler's three independent bounds checks and its raw
jugglerSendMouseEvent/sendWheelEvent calls are gone; it now passes
relative coordinates and never sees a bounding box. Net effect on that
vendored file is 92 lines removed against 22 added -- a smaller diff
against upstream juggler, since the logic moved into a file we own.
Wheel events go through the same conversion, so a wheel at relative y == 0
no longer scrolls the tab strip.
3. Enforcement. scripts/check-input-dispatch.py fails the build if anything
outside the chokepoint dispatches synthesized input or does
browser-relative coordinate arithmetic. It needs no browser build, so
.github/workflows/lint.yml gates every pull request -- nothing was
checking PRs before. Two exemptions, both content-process: PageAgent
(drag events, already content-relative, no ack) and FrameTree (the ack
producer). docs/input-dispatch.md states the invariant.
tests/patches/mouse-boundary-sweep.py replaces hand-picked edge targets,
which are what let each of the four through: humanize-edge-deadlock.py
probes only the far edges, and humanize-mouse-trajectory.py pins
os="linux" -- the one fingerprint immune to #751. It sweeps the whole
viewport ring across every spoofed OS with humanize on and off, asserting
each point is acked AND observed by the page. It depends on change 1 to
run at all: without the backstop the first bad coordinate wedges the
browser and the sweep dies there.
tests/patches/input-ack-backstop.py covers the bounded wait itself, by
blocking the content main thread far longer than the deadline -- a
legitimate late ack, with no test-only hook in production code.
The sweep immediately found a fifth instance, pre-existing and unreported:
boundingBox.height is consistently 0.5 CSS px less than the innerHeight the
page reports, so the page's last row is half covered. With the box at
1920x977.5 +0+56.5, relative y == 977 -- innerHeight - 1, well inside the
viewport as far as the page is concerned -- dispatches at 1033.5, rounds to
1034, and the content ends at 1034. Deterministic, 4/4, and it deadlocks a
stock build. Fixed by clamping to the last whole pixel inside the element,
symmetric with the near-edge snap; both live in the one conversion now.
All seven patch tests pass: mouse-boundary-sweep (150 ring coordinates over
6 scenarios), near-edge-mouse-deadlock, input-ack-backstop,
humanize-edge-deadlock, humanize-mouse-trajectory, noop-mousemove-deadlock,
trusted-events.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GQgHHGRXNp29jr4xQjK7iv
(cherry picked from commit 827b98d31e)
`mach build` leaves dist/bin/fonts holding only TwemojiMozilla.ttf -- the font
bundles and fontconfig are staged by scripts/package.py, so they exist only in
packaged builds. Anything launching the objdir binary through the Python
wrapper therefore starts a browser with no usable content font, because the
wrapper sets FONTCONFIG_FILE to a file that is not there.
It fails confusingly: the browser chrome still has system fonts, so the only
symptom is tofu boxes in page content, and through AsyncCamoufox it surfaces as
a TargetClosedError with no indication of the cause. That cost real time while
writing the tests/patches scripts, hence the note in their docstrings.
`make stage-fonts` copies them in. Idempotent, and a no-op when nothing is
built yet. Not needed by `make run` or `make tests`, which launch the binary
directly and fall back to the system fontconfig.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
package-windows pulls VCRUNTIME140/VCRUNTIME140_1/MSVCP140 out of the mozbuild
Visual Studio tree through a shell glob that pins one redist version
(14.38.33135) and one toolset (VC143). Windows builds cross-compile on Linux
and get their toolchain from mozbootstrap, so a different version there leaves
the glob unexpanded -- and add_includes_to_package() skipped anything that did
not exist, with no warning.
The package then ships without the CRT. camoufox.exe imports those DLLs, so on
any machine without the Visual C++ Redistributable installed the process dies
immediately and Playwright surfaces only "spawn UNKNOWN".
- glob the redist and toolset versions instead of pinning them
- treat a missing --includes entry as fatal, so an unexpanded glob fails the
build instead of silently shipping a broken package
Consolidates the following individual fixes into one changeset, all rebased
onto the current Firefox 152 base and validated together via a full build:
- webrtc: stop real-IP leak under a proxy; sanitize getStats IP + fabricate a
synthetic srflx when ICE produces none (TCP-proxy case).
- proxy: re-enable launch-arg proxy by disabling https_first.
- screen: make MaskConfig the single source for screen + CSS device dims so
matchMedia(device-width) agrees with screen.width.
- stealth: spoof CSS2 system-font keyword resolution (font-system-fonts-css2).
- juggler: filepicker reliability + skip mouse-move coalescing for synthetic
(juggler) events so input dispatch can't stall under parallel load.
- juggler: emit a single input event for insertText on form fields.
- juggler: reload about:blank via browsingContext.reload.
- locale: propagate launch-arg locale to BrowsingContext.
- build: create v150-removed dirs before copy-additions.
- stealth: BrowserForge headless / impossible-geometry tell corrections and
speech-voice spoofing (host-voice leak fix).
Supersedes daijro/camoufox #637-#647.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix v150 patches
* screen related patch fixes
* fix juggler issues with 150
* Update grading.py
improved build tester scoring
* fix windows build for v150
- scripts/_mixin.py: switch moz_target from x86_64-pc-mingw32 (no longer
supported in FF150) to x86_64-pc-windows-msvc
- additions/juggler/screencast/HeadlessWindowCapturer.h: typedef pid_t
on XP_WIN; libwebrtc headers (video_capture.h, desktop_capturer.h)
reference pid_t which is POSIX-only
- patches/anti-font-fingerprinting.patch: include mozilla/dom/Document.h
in gfxTextRun.cpp; on Windows it is not transitively included so
doc->GetInnerWindow() failed with "incomplete type 'Document'"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* make service test use local binary
* updated ff fingerprint versions
* Update README.md
---------
Co-authored-by: Ubuntu <ubuntu@ip-172-31-15-96.us-east-2.compute.internal>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Allow disabling font spacing perturbation (seed=0 no-op, matching audio)
The audio fingerprint manager treats seed==0 as "no perturbation", but
font spacing had a hardcoded fallback (0x6D2B79F5u) that made it always
active — even when no seed was explicitly set.
C++ change: remove the hardcoded fallback and add `if (seed != 0)` guard
around the LCG + glyph offset loop (mirrors AudioFingerprintManager).
Also removes the debug printf that fired on every ShapeText() call.
To disable font spacing from the Python API, set fonts:spacing_seed to 0
in the config/preset — same convention as audio:seed and canvas:seed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add local channel install script for custom builds
Installs build artifacts to browsers/local/ instead of overwriting
official slots. Survives camoufox fetch. Handles permissions and
version.json automatically.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* example files
* contributing guides
* simple service test
* run tests in sync
* update pr template
* pip updates
* Update README.md
* typo fixes
* undo pip package update lol
* upgraded service test
* undo injections
* test with proxies
* auto set timezone and proxy url
* delete checks bundle
* split up service tests
* split up build tests
* rename service tests to service tester
* Update CONTRIBUTING.md
* fix entry vs exit ip
* allow alpha versions
* fix patch issues on macos
* bidirectional patch
* Add note on experimental pip package
* fix: migrate Services import to lazy loading pattern and disable dark theme to prevent XPIProvider crash
- Changed Services from ChromeUtils.defineLazyGetter to ChromeUtils.defineESModuleGetters with lazy object
- Updated all Services references to lazy.Services throughout Juggler.js
- Added re-copying of additions and settings after git reset in patch.py
- Removed juggler components.conf copy logic from patch.py (now handled by copy-additions.sh)
- Disabled dark theme preference in camoufox.cfg to
* fix: revert Services import to direct ESM import and re-enable dark theme preference
- Changed Services from lazy loading pattern back to direct ChromeUtils.importESModule()
- Removed lazy object wrapper and ChromeUtils.defineESModuleGetters usage
- Updated all lazy.Services references to Services throughout Juggler.js
- Re-enabled dark theme preference in camoufox.cfg (extensions.activeThemeID)
- Removed XPIProvider crash prevention comment as issue is resolved
* fix: remove redundant Services import as it's available globally in XPCOM component context
- Removed ChromeUtils.importESModule imports for XPCOMUtils, ComponentUtils, and Services
- Added comment explaining Services is available as a global in XPCOM component context
- Services.scriptloader.loadSubScript call continues to work with global Services reference
* fix: correct JugglerFrameChild.sys.mjs path by removing duplicate content directory
- Changed esModuleURI from 'chrome://juggler/content/content/JugglerFrameChild.sys.mjs' to 'chrome://juggler/content/JugglerFrameChild.sys.mjs'
- Removes erroneous duplicate 'content' directory in the child actor module path
* fix: add ESM module entries and correct JugglerFrameChild.jsm path in jar.mn
- Added JugglerFrameParent.sys.mjs entry to juggler.jar manifest
- Added JugglerFrameChild.sys.mjs entry to juggler.jar manifest
- Fixed JugglerFrameChild.jsm path from 'content/content/JugglerFrameChild.jsm' to 'content/JugglerFrameChild.jsm' removing duplicate content directory
* fix: remove redundant Services lazy getter as it's available globally in content process context
- Removed ChromeUtils.defineLazyGetter for Services in main.js
- Added comment explaining Services is available as a global
- Services reference continues to work with global availability in content process
---------
Co-authored-by: Nirupam Bhowmick <48842933+heydryft@users.noreply.github.com>
- Added --binary flag to preserve line endings (CRLF vs LF)
- Removed -F3 (fuzz factor) flag
- Helps with cross-platform patching where line endings may differ
- Changed --fuzz=3 to -F3 for consistent short option format
- Added -l flag to ignore whitespace differences when applying patches
- Maintains --forward flag to skip already applied patches
- Removed MOZ_APP_VENDOR and MOZ_APP_PROFILE from configure.sh branding file
- Added note that these must be set via imply_option() in browser/moz.configure
- Updated disable-data-reporting-at-compile-time.patch to set MOZ_APP_VENDOR="Camoufox" and MOZ_APP_PROFILE="camoufox"
- Changed juggler components.conf to use "type" instead of "constructor" for Firefox 146+ compatibility
- Added automatic
Add comprehensive task list documenting the Firefox upgrade from v135.0.1 to v147.0b3 (12 major versions). Includes status tracking for all 45 patches after syncing with upstream LibreWolf (Firefox 146) and Playwright repositories.
Key changes:
- 25 broken patches identified (56% failure rate)
- 20 patches applying cleanly (44% success rate)
- LibreWolf patches updated from upstream: 17 patches synced, 5 deleted, 2 auto-fixed
-
- Added ability to spoof webgl2 supported extensions
- Added ability to block parameters that aren't defined in config
- Passing null in config will block the value
- Added more parameters to the demo site
Experimental WebGL fingerprint injection.
- Allows the ability to set all WebGL parameters, supported extension list, shader precision formats, & context attributes.
- Added a demo website under scripts/examples/webgl.html that can be used to generate Camoufox config data