mirror of
https://github.com/daijro/camoufox.git
synced 2026-08-18 16:00:58 +00:00
0e4151f820
Fixes the new_page() hang from #666, and restores the pythonlib/ + settings/ halves of #637-#647 that were dropped when those PRs were consolidated into #666 (that PR only carried patches/ + additions/, so these never actually landed). ## new_page() hangs when window.outer* is spoofed (#666) The outer-size hijack in browser-init.patch pinned the chrome documentElement to the spoofed size. That caps .browserStack, which caps the content viewport, so the content window can never reach the size Juggler asks for in updateViewportSize() -- and awaitViewportDimensions awaits exact equality with no timeout, so it deadlocks rather than erroring. The second new_page() hung forever and took the context with it. The pin was never load-bearing: GetOuterWidth/GetOuterHeight already consult MaskConfig unconditionally (fingerprint-injection.patch), so window.outerWidth is spoofed in C++ regardless of the real chrome window size. Resizing is enough. Measured on the official v152.0.4-beta.26 build (headless): config before after none pass pass inner pass pass outer HANG pass both HANG pass (iw:360 ih:740 ow:360 oh:800 -- exact) This corrects the diagnosis in #666, which blamed the inner+outer combination and the `!(outerWidth || outerHeight)` guard. outer* ALONE is sufficient to hang, and dropping inner* does not help, so that guard is not the culprit. Also fixed driver-side: Playwright's implicit 1280x720 viewport is what asks for the impossible size, so the driver now defaults to no_viewport when the config spoofs any window dimension. That fixes the hang on already-released builds without a rebuild. An explicit viewport=/no_viewport= from the caller wins. ## WebRTC ICE prefs (#538) #666 merged the C++ half of the WebRTC fix but not the prefs, so the shipped build still has no_host=true and none of the proxy_only prefs. proxy_only_if_behind_proxy is the pref that actually stops the real-IP leak: it prevents a UDP STUN request routing around a TCP proxy. no_host=false keeps the stock two-candidate shape, which obfuscate_host_addresses makes leak-free. ## Also restored from the consolidation - fix(proxy): dom.security.https_first rewrote http:// before the launch-arg proxy filter saw it, breaking CONNECT-only proxies (#638). - fix(stealth): speech-voice spoofing + stop leaking host voices (#646). - fix(stealth): clamp inner <= outer <= avail <= screen; BrowserForge can emit impossible geometries that leak as tells (#647). Refs: https://github.com/daijro/camoufox/pull/666 Refs: https://github.com/daijro/camoufox/issues/538
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
"""
|
|
Tests for the BrowserForge fingerprint-correction helpers in
|
|
camoufox.fingerprints, ported to parity with the camoufox-js launcher.
|
|
|
|
Run with:
|
|
cd pythonlib && python -m pytest tests/test_fingerprint_fixes.py -v
|
|
|
|
These guard the headless / impossible-geometry tells that BrowserForge
|
|
occasionally ships and that the camoufox-js wrapper already corrected but the
|
|
pythonlib did not.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
|
|
from camoufox.fingerprints import ( # noqa: E402
|
|
clamp_window_dimensions,
|
|
fix_navigator_arch,
|
|
fix_screen_no_taskbar,
|
|
set_media_devices_defaults,
|
|
)
|
|
|
|
|
|
class TestFixNavigatorArch:
|
|
def test_corrects_armv81_to_ua_arch(self):
|
|
c = {
|
|
"navigator.userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:135.0) ...",
|
|
"navigator.platform": "Linux armv81",
|
|
"navigator.oscpu": "Linux armv81",
|
|
}
|
|
fix_navigator_arch(c, "lin")
|
|
assert c["navigator.platform"] == "Linux x86_64"
|
|
assert c["navigator.oscpu"] == "Linux x86_64"
|
|
|
|
def test_noop_when_already_consistent(self):
|
|
c = {
|
|
"navigator.userAgent": "... Linux x86_64 ...",
|
|
"navigator.platform": "Linux x86_64",
|
|
"navigator.oscpu": "Linux x86_64",
|
|
}
|
|
fix_navigator_arch(c, "lin")
|
|
assert c["navigator.platform"] == "Linux x86_64"
|
|
|
|
def test_only_runs_on_linux(self):
|
|
c = {"navigator.userAgent": "... Macintosh ...", "navigator.platform": "MacIntel"}
|
|
fix_navigator_arch(c, "mac")
|
|
assert c["navigator.platform"] == "MacIntel"
|
|
|
|
def test_noop_without_ua(self):
|
|
c = {"navigator.platform": "Linux armv81"}
|
|
fix_navigator_arch(c, "lin")
|
|
assert c["navigator.platform"] == "Linux armv81"
|
|
|
|
|
|
class TestFixScreenNoTaskbar:
|
|
def test_subtracts_taskbar_when_avail_equals_screen(self):
|
|
c = {
|
|
"screen.width": 1920,
|
|
"screen.height": 1080,
|
|
"screen.availWidth": 1920,
|
|
"screen.availHeight": 1080,
|
|
"window.outerHeight": 1080,
|
|
"window.innerHeight": 1040,
|
|
}
|
|
fix_screen_no_taskbar(c, "lin")
|
|
assert c["screen.availHeight"] == 1080 - 27 # linux panel
|
|
assert c["window.outerHeight"] == 1053
|
|
# chrome delta (1080-1040=40) preserved
|
|
assert c["window.innerHeight"] == 1053 - 40
|
|
|
|
def test_per_os_taskbar_height(self):
|
|
for os_name, px in (("win", 40), ("mac", 25), ("lin", 27)):
|
|
c = {
|
|
"screen.width": 1920,
|
|
"screen.height": 1080,
|
|
"screen.availWidth": 1920,
|
|
"screen.availHeight": 1080,
|
|
}
|
|
fix_screen_no_taskbar(c, os_name)
|
|
assert c["screen.availHeight"] == 1080 - px
|
|
|
|
def test_noop_when_avail_already_less_than_screen(self):
|
|
c = {
|
|
"screen.width": 1920,
|
|
"screen.height": 1080,
|
|
"screen.availWidth": 1920,
|
|
"screen.availHeight": 1040,
|
|
}
|
|
fix_screen_no_taskbar(c, "lin")
|
|
assert c["screen.availHeight"] == 1040
|
|
|
|
|
|
class TestClampWindowDimensions:
|
|
def test_clamps_impossible_geometry_both_axes(self):
|
|
c = {
|
|
"screen.width": 1920,
|
|
"screen.height": 1080,
|
|
"screen.availWidth": 2000, # > screen
|
|
"window.outerWidth": 2200, # > avail
|
|
"window.innerWidth": 2100, # > outer
|
|
}
|
|
clamp_window_dimensions(c)
|
|
assert c["screen.availWidth"] == 1920
|
|
assert c["window.outerWidth"] == 1920
|
|
assert c["window.innerWidth"] <= c["window.outerWidth"]
|
|
|
|
def test_preserves_chrome_delta(self):
|
|
c = {
|
|
"screen.width": 1000,
|
|
"window.outerWidth": 1200, # 200 over screen
|
|
"window.innerWidth": 1180, # 20px chrome
|
|
}
|
|
clamp_window_dimensions(c)
|
|
assert c["window.outerWidth"] == 1000
|
|
assert c["window.innerWidth"] == 1000 - 20
|
|
|
|
def test_noop_when_hierarchy_already_valid(self):
|
|
c = {
|
|
"screen.width": 1920,
|
|
"screen.availWidth": 1920,
|
|
"window.outerWidth": 1280,
|
|
"window.innerWidth": 1264,
|
|
}
|
|
clamp_window_dimensions(c)
|
|
assert c["window.outerWidth"] == 1280
|
|
assert c["window.innerWidth"] == 1264
|
|
|
|
|
|
class TestSetMediaDevicesDefaults:
|
|
def test_sets_one_mic_one_cam(self):
|
|
c = {}
|
|
set_media_devices_defaults(c)
|
|
assert c["mediaDevices:enabled"] is True
|
|
assert c["mediaDevices:micros"] == 1
|
|
assert c["mediaDevices:webcams"] == 1
|
|
assert c["mediaDevices:speakers"] == 0
|
|
|
|
def test_respects_user_set_media_devices(self):
|
|
c = {"mediaDevices:webcams": 5}
|
|
set_media_devices_defaults(c)
|
|
assert c == {"mediaDevices:webcams": 5}
|