Files
camoufox/scripts/patch.py
icepaq dfa62f7070 Juggler fixes (#2)
* 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>
2026-01-04 11:40:05 -05:00

253 lines
8.0 KiB
Python

#!/usr/bin/env python3
"""
The script that patches the Firefox source into the Camoufox source.
Based on LibreWolf's patch script:
https://gitlab.com/librewolf-community/browser/source/-/blob/main/scripts/librewolf-patches.py
Run:
python3 scripts/init-patch.py <version> <release>
"""
import hashlib
import os
import re
import shutil
import subprocess
import sys
from dataclasses import dataclass
from _mixin import (
find_src_dir,
get_moz_target,
get_options,
list_patches,
patch,
run,
temp_cd,
)
options, args = get_options()
"""
Main patcher functions
"""
@dataclass
class Patcher:
"""Patch and prepare the Camoufox source"""
moz_target: str
target: str
def camoufox_patches(self):
"""
Apply all patches
"""
version, release = extract_args()
with temp_cd(find_src_dir('.', version, release)):
# Reset to unpatched state first (like "Find broken patches")
print("Resetting to unpatched state...")
run('git clean -fdx && ./mach clobber && git reset --hard unpatched', exit_on_fail=False)
# Re-copy additions and settings after reset
print("Re-copying additions and settings...")
run(f'bash ../scripts/copy-additions.sh {version} {release}')
# Create the base mozconfig file
run('cp -v ../assets/base.mozconfig mozconfig')
# Set cross building target
print(f'Using target: {self.moz_target}')
self._update_mozconfig()
if not options.mozconfig_only:
# Apply patches with roverfox patches at the very end
all_patches = list_patches()
# Normalize paths and partition into non-roverfox and roverfox
non_roverfox = []
roverfox = []
for p in all_patches:
norm = os.path.normpath(p)
parts = norm.split(os.sep)
if 'roverfox' in parts:
roverfox.append(p)
else:
non_roverfox.append(p)
# Track patch failures
failed_patches = []
# Apply non-roverfox patches first
for patch_file in non_roverfox:
rejects = self._apply_and_check(patch_file)
if rejects:
failed_patches.append((patch_file, rejects))
# Apply roverfox patches last
for patch_file in roverfox:
rejects = self._apply_and_check(patch_file)
if rejects:
failed_patches.append((patch_file, rejects))
# Report failures
if failed_patches:
print('\n' + '='*70)
print(f'ERROR: {len(failed_patches)} patch(es) failed to apply cleanly:')
print('='*70)
for patch_file, rejects in failed_patches:
print(f'\n{patch_file}:')
for reject in rejects:
print(f' - {reject}')
print('='*70)
sys.exit(1)
print('Complete!')
def _apply_and_check(self, patch_file):
"""
Apply a patch and check for reject files.
Returns list of reject files if any, empty list otherwise.
"""
import subprocess
import os
print(f"\n*** -> patch -p1 -i {patch_file}")
sys.stdout.flush()
# Apply patch interactively - don't capture stdout/stderr at all
# This allows prompts to show immediately and user can respond
# --forward flag: skip patches that appear to be already applied
# --binary flag: preserve line endings (helps with CRLF vs LF differences)
# -l flag: ignore whitespace differences
result = subprocess.run(
['patch', '-p1', '--forward', '-l', '--binary', '-i', patch_file],
stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr,
text=True
)
# After patch completes, search for any .rej files created
rejects = []
for root, dirs, files in os.walk('.'):
for file in files:
if file.endswith('.rej'):
# Check if this is a newly created reject file
reject_path = os.path.join(root, file)
# Only include if it was just created (within last minute)
if os.path.exists(reject_path):
import time
if time.time() - os.path.getmtime(reject_path) < 60:
rejects.append(reject_path)
return rejects
def _update_mozconfig(self):
"""
Helper for adding additional mozconfig code from assets/<target>.mozconfig
"""
mozconfig_backup = "mozconfig.backup"
mozconfig = "mozconfig"
mozconfig_hash = "mozconfig.hash"
# Create backup if it doesn't exist
if not os.path.exists(mozconfig_backup):
if os.path.exists(mozconfig):
shutil.copy2(mozconfig, mozconfig_backup)
else:
with open(mozconfig_backup, 'w', encoding='utf-8') as f:
pass
# Read backup content
with open(mozconfig_backup, 'r', encoding='utf-8') as f:
content = f.read()
# Add target option
content += f"\nac_add_options --target={self.moz_target}\n"
# Add target-specific mozconfig if it exists
target_mozconfig = os.path.join("..", "assets", f"{self.target}.mozconfig")
if os.path.exists(target_mozconfig):
with open(target_mozconfig, 'r', encoding='utf-8') as f:
content += f.read()
# Calculate new hash
new_hash = hashlib.sha256(content.encode()).hexdigest()
# Update mozconfig
print(f"-> Updating mozconfig, target is {self.moz_target}")
with open(mozconfig, 'w', encoding='utf-8') as f:
f.write(content)
with open(mozconfig_hash, 'w', encoding='utf-8') as f:
f.write(new_hash)
def add_rustup(*targets):
"""Add rust targets"""
for rust_target in targets:
run(f'~/.cargo/bin/rustup target add "{rust_target}"')
def _update_rustup(target):
"""Add rust targets for the given target"""
if target == "linux":
add_rustup("aarch64-unknown-linux-gnu", "i686-unknown-linux-gnu")
elif target == "windows":
add_rustup("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc", "i686-pc-windows-msvc")
elif target == "macos":
add_rustup("x86_64-apple-darwin", "aarch64-apple-darwin")
"""
Preparation
"""
def extract_args():
"""Get version and release from args"""
if len(args) != 2:
sys.stderr.write('error: please specify version and release of camoufox source')
sys.exit(1)
return args[0], args[1]
AVAILABLE_TARGETS = ["linux", "windows", "macos"]
AVAILABLE_ARCHS = ["x86_64", "arm64", "i686"]
def extract_build_target():
"""Get moz_target if passed to BUILD_TARGET environment variable"""
if os.environ.get('BUILD_TARGET'):
target, arch = os.environ['BUILD_TARGET'].split(',')
assert target in AVAILABLE_TARGETS, f"Unsupported target: {target}"
assert arch in AVAILABLE_ARCHS, f"Unsupported architecture: {arch}"
else:
target, arch = "macos", "arm64"
return target, arch
"""
Launcher
"""
if __name__ == "__main__":
# Extract args
VERSION, RELEASE = extract_args()
TARGET, ARCH = extract_build_target()
MOZ_TARGET = get_moz_target(TARGET, ARCH)
_update_rustup(TARGET)
# Check if the folder exists
if not os.path.exists(f'camoufox-{VERSION}-{RELEASE}/configure.py'):
sys.stderr.write('error: folder doesn\'t look like a Firefox folder.')
sys.exit(1)
# Apply the patches
patcher = Patcher(MOZ_TARGET, TARGET)
patcher.camoufox_patches()
sys.exit(0) # ensure 0 exit code