- AGENTS.md holds the engineering rules for any coding agent, plus the
repo map, build, patch and test commands that CLAUDE.md used to carry.
CLAUDE.md now only imports it, so there is one set of rules.
ci/tribal-rules.yml is the record of settled decisions it points to.
- ROADMAP.md lists planned work, each item linked to its issue.
- README:
- fpgen and the coherence check replace BrowserForge;
- the patch workflow uses the make targets instead of the removed
developer UI;
- letter-spacing noise is described as off by default, as it is.
- docs/:
- beta-testing-ff146.md removed;
- patch-upgrading-guide rewritten around the make targets;
- per-context-patches without the canvas patch that no longer exists,
and with measured preset counts;
- playwright-maintenance without the JSM wrapper that does not exist;
- smaller fixes in MEDIA-DEVICES, input-dispatch and FONTS.
- ci/README: every job, and the real shard, skiplist and entry-point lists.
- pythonlib, tester and patch-dependency READMEs corrected against the code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 KiB
Firefox Patch Upgrading Guide
How to update Camoufox's patches when upstream.sh moves to a new Firefox
version. Patches break because Firefox renames APIs, moves code and shifts line
numbers; this guide covers finding and fixing those rejects.
Table of Contents
- Understanding the Patch System
- The Source Tree and Its Make Targets
- General Workflow
- Fixing Common Reject Types
- Per-Context Machinery
- Testing and Validation
- Best Practices
Understanding the Patch System
Patch Categories
All patches live under patches/, and scripts/patch.py applies every
*.patch in it (subdirectories included), sorted by file name:
- Playwright:
playwright/0-playwright.patch(Juggler integration) andplaywright/1-leak-fixes.patch. Their names sort first, and every other patch is written against a tree that already has them. - Feature patches:
webrtc-ip-spoofing.patch,anti-font-fingerprinting.patch, etc. Per-user-context (per-Playwright-context) support is built into each one. librewolf/,ghostery/: patches taken from those projects.
Compile-time dependencies between patches (MaskConfig, RoverfoxStorageManager)
are listed in patches/patch-dependencies.md.
Key Infrastructure Files
- RoverfoxStorageManager.cpp/h: Thread-safe key-value storage for per-context data
- Manager Classes: FontSpacingSeedManager, WebRTCIPManager, etc.
- Window.webidl: Exposes the per-context setters to Playwright
The Source Tree and Its Make Targets
The Firefox tree is camoufox-<version>-<release>/ (from upstream.sh). It is
a git repository whose unpatched tag is plain Firefox plus additions/ and
settings/. Run every target from the repository root:
| Target | What it does |
|---|---|
make dir |
Fetches and extracts Firefox if the tree is missing. Otherwise resets it to unpatched, runs mach clobber and git clean -fdx (the object directory goes too), re-copies additions, then applies every patch and lists the ones that left rejects. |
make revert |
git reset --hard unpatched. Untracked files stay, including new files that patches created. |
make clean |
mach clobber, git clean -fdx, then make revert: unpatched Firefox with nothing left over, without re-fetching. |
make patch ./patches/x.patch |
Applies one patch (patch -p1). |
make unpatch ./patches/x.patch |
Reverses one patch. |
make first-checkpoint |
Commits the current tree and tags it first-checkpoint. |
make workspace ./patches/x.patch |
Unapplies x if it is applied, runs first-checkpoint, then applies x again, so the working tree differs from the checkpoint by exactly that patch. |
make diff |
git diff first-checkpoint. Redirect it into the patch file. |
git diff does not show untracked files. Before make diff, mark new files
with git add -N <file> inside the source tree, or they will be missing from
the patch.
General Workflow
Step 1: Bump the Version and Find the Broken Patches
Update version and release in upstream.sh, then:
make dir
patch.py applies every patch and ends with a list of the ones that failed and
their reject files. It deletes the .rej files after listing them, so reproduce
each failure one patch at a time (Step 2).
Step 2: Set Up One Patch
Start from a clean unpatched tree, apply what the patch builds on (at least the
Playwright patches, plus anything from patches/patch-dependencies.md),
checkpoint, then apply the broken patch. Use make clean rather than
make revert here: files that other patches created survive a revert and make
patch stop on "previously applied" prompts.
make clean
make patch ./patches/playwright/0-playwright.patch
make patch ./patches/playwright/1-leak-fixes.patch
make first-checkpoint
make patch ./patches/patch-name.patch # fails, leaving .rej files
Find the reject files:
cd camoufox-<version>-<release>
find . -name '*.rej' -type f
Step 3: Analyze Each Reject File
Read the reject file to understand what failed:
cat path/to/file.cpp.rej
Reject files show:
@@lines: Line numbers where patch expected to apply-lines: What the patch expected to find (old code)+lines: What the patch wanted to add (new code)
Step 4: Locate the Correct Position in Firefox Code
The line numbers in rejects are usually wrong for the new Firefox version. You need to:
- Search for unique context around the reject
- Understand what the patch is doing
- Find equivalent location in new Firefox code
Step 5: Apply Changes Manually
Edit the file to make the rejected change at the correct location.
Step 6: Remove Reject Files
After fixing all rejects, delete the .rej files and any .orig backups
patch left, so they do not end up in the diff:
find . -name '*.rej' -o -name '*.orig' | xargs rm -f
Step 7: Write the Updated Patch
From the repository root:
(cd camoufox-<version>-<release> && git add -N path/to/new/file.cpp) # new files only
make diff > patches/patch-name.patch
Step 8: Verify
Run make dir again. The patch should no longer be listed as failing.
Fixing Common Reject Types
Type 1: Include Directive Rejects
Symptom: Reject shows failed #include additions
Example Reject:
@@ -325,6 +325,7 @@
#include "xpcpublic.h"
+#include "WebRTCIPManager.h"
#include "nsDocShell.h"
How to Fix:
- Read the actual file to find the includes section
- Search for nearby includes (e.g.,
xpcpublic.h) - Add the new include in the appropriate location
- Firefox include order: system headers, then Mozilla headers, alphabetically within groups
Example:
// Find this in the actual file:
#include "xpcpublic.h"
// Add the missing includes after it:
#include "xpcpublic.h"
#include "WebRTCIPManager.h"
#include "nsDocShell.h"
#include "mozilla/OriginAttributes.h"
Type 2: Function Signature Changes
Symptom: Reject shows function call with changed parameters
Example Reject:
- mouseOrPointerEvent.mButton = aButton;
+ mouseOrPointerEvent.mJugglerEventId = aMouseEventData.mJugglerEventId;
Common Causes:
- Firefox refactored the API
- Parameters moved from individual args to struct/data object
- Parameter order changed
How to Fix:
- Search for the function definition in Firefox source
- Understand the new API structure
- Port the patch logic to the new API
Example - Firefox 146 Mouse Event Refactoring:
Old Firefox 144 API (individual parameters):
void SynthesizeMouseEvent(int x, int y, int button, ...)
New Firefox 146 API (structured data):
void SynthesizeMouseEvent(SynthesizeMouseEventData& aData,
SynthesizeMouseEventOptions& aOptions)
Port the patch:
// Old patch code:
mouseEvent.mButton = aButton;
mouseEvent.jugglerEventId = aJugglerEventId;
// New patch code for Firefox 146:
mouseOrPointerEvent.mButton = aMouseEventData.mButton;
mouseOrPointerEvent.mJugglerEventId = aMouseEventData.mJugglerEventId;
mouseOrPointerEvent.convertToPointer = aOptions.mConvertToPointer;
Type 3: Missing Context - Code Moved
Symptom: Reject shows context that doesn't exist in the file
How to Fix:
- Use grep to search for unique function names or variables in the reject
- Find where Firefox moved the code
- Apply the patch to the new location
# Search across the codebase
grep -r "FunctionName" camoufox-<version>/ --include="*.cpp"
Type 4: New Parameter Added to Function Calls
Symptom: Reject shows function call, but Firefox added/removed parameters
Example - MakeTextRun userContextId:
Old call:
MakeTextRun(text, len, drawTarget, appUnitsPerDevPixel, flags, recorder);
New Firefox expects:
MakeTextRun(text, len, drawTarget, appUnitsPerDevPixel, flags, recorder, userContextId);
How to Fix:
- Extract userContextId from available context (Document, PresContext, etc.)
- Add proper extraction code before the call
- Pass userContextId as the last parameter
Standard userContextId Extraction Pattern:
uint32_t userContextId = 0;
if (mozilla::dom::Document* doc = presContext->Document()) {
if (nsIPrincipal* principal = doc->NodePrincipal()) {
auto* bp = mozilla::BasePrincipal::Cast(principal);
if (bp) {
userContextId = bp->OriginAttributesRef().mUserContextId;
}
}
}
// Now pass userContextId to the function
MakeTextRun(..., userContextId);
Type 5: Line Number Shifts (No Code Changes)
Symptom: Reject shows patch tried to apply at wrong line number, but code is identical
How to Fix:
Simply apply the patch manually at the correct line number. The code hasn't changed, just the location.
Per-Context Machinery
Most spoofing patches carry per-context support. When porting one, expect these pieces:
-
Manager classes (e.g., FontSpacingSeedManager, WebRTCIPManager):
- Store per-context settings using RoverfoxStorageManager
- Provide WebIDL-compatible enable/disable checks
- Handle self-destructing functions
-
Window.webidl functions:
- JavaScript APIs exposed to Playwright
- Examples:
setFontSpacingSeed(),setWebRTCIPv4()
-
nsGlobalWindowInner.cpp implementations:
- Extract userContextId from window/document/docshell
- Call manager classes
- Self-destruct logic (remove function after first use)
-
Core logic changes:
- Consult the per-context manager before the global config (MaskConfig)
- Pass userContextId through call chains
See per-context-patches.md for the full list.
Testing and Validation
Minimal Verification
After updating a patch, always verify:
-
Every patch applies cleanly:
make dirlists no failures. -
Build compiles (if feasible):
make build
Full Testing
Run the suites that cover the patch (see ci/README.md):
python3 -m ci.run_patch_guards --binary <camoufox-bin> is the most direct
evidence that a patch which still applies was not neutered by the upgrade.
Best Practices
DO:
- ✅ Start each patch from
make cleanplus the patches it builds on - ✅ Read and understand what the patch is trying to do before fixing rejects
- ✅ Search for API changes in Firefox release notes when functions have changed
- ✅ Use grep/search extensively to find where code moved
- ✅ Extract userContextId properly using the standard pattern
- ✅ Check with
make dirthat the whole stack applies before considering a patch done - ✅ Keep commits atomic - one patch fix per session
- ✅ Document major API changes you discover
DON'T:
- ❌ Don't hand-edit
.patchfiles - edit the tree and regenerate withmake diff - ❌ Don't leave TODO comments - fix things properly as you go
- ❌ Don't guess parameter values - extract them properly or investigate
- ❌ Don't skip verification - always test the patch applies cleanly
- ❌ Don't batch multiple patch updates - do them one at a time
- ❌ Don't assume line numbers are correct in reject files
- ❌ Don't ignore warnings during patch application
Common Pitfalls
- Assuming reject line numbers are accurate: They're usually wrong in new Firefox versions
- Not understanding API changes: Firefox refactors often - read the new code
- Forgetting new files:
git diffskips untracked files;git add -Nthem beforemake diff - Diffing against the wrong base:
make first-checkpointbefore applying the patch you are fixing, ormake diffwill include its dependencies - Leaving reject files: Remove all
.rejand.origfiles after fixing
Appendix: Firefox Source Navigation
Finding Files
# Find files by name
find . -name "Navigator.cpp" -type f
# Find files containing a symbol
grep -r "GetAcceptLanguages" . --include="*.cpp"
# Find class definitions
grep -r "class Navigator" . --include="*.h"
Understanding Firefox Code Structure
dom/: DOM implementationdom/base/: Core DOM classes (Window, Document, Navigator, etc.)dom/webidl/: WebIDL interface definitionsdom/media/webrtc/: WebRTC implementation
gfx/: Graphics and font renderinggfx/thebes/: Text rendering (fonts, glyphs, shaping)
layout/: Layout enginelayout/generic/: Text frameslayout/mathml/: MathML rendering
Common Firefox Patterns
User Context ID Extraction:
uint32_t userContextId = 0;
if (Document* doc = GetDocument()) {
if (nsIPrincipal* principal = doc->NodePrincipal()) {
auto* bp = mozilla::BasePrincipal::Cast(principal);
if (bp) {
userContextId = bp->OriginAttributesRef().mUserContextId;
}
}
}
Three-tier userContextId fallback (for WebIDL functions):
// 1) Document's principal (preferred)
if (Document* doc = win->GetDoc()) {
if (nsIPrincipal* p = doc->NodePrincipal()) {
userContextId = p->OriginAttributesRef().mUserContextId;
}
}
// 2) DocShell origin attributes
if (userContextId == 0) {
if (nsIDocShell* ds = win->GetDocShell()) {
auto* concrete = static_cast<nsDocShell*>(ds);
userContextId = concrete->GetOriginAttributes().mUserContextId;
}
}
// 3) Top browsing context
if (userContextId == 0) {
if (BrowsingContext* bc = win->GetBrowsingContext()) {
RefPtr<BrowsingContext> top = bc->Top();
// ... extract from top window
}
}
Summary Checklist
When updating patches for a new Firefox version:
- Bump
upstream.shand runmake dirto list the failing patches - For each:
make clean, apply its dependencies,make first-checkpoint,make patchit - Analyze each reject to understand what changed
- Search Firefox source for moved/refactored code
- Fix rejects by porting logic to new Firefox APIs
- Extract userContextId properly using standard patterns
- Don't leave TODO comments - fix everything immediately
- Remove all
.rejand.origfiles after fixing git add -Nany new filesmake diff > patches/<name>.patchmake dirapplies the whole stack cleanly- Document any major API changes discovered
Additional Resources
- Firefox source: https://searchfox.org/
- Firefox API documentation: https://firefox-source-docs.mozilla.org/
- Mercurial repository: https://hg.mozilla.org/mozilla-central/