Clamp headful window geometry to the real display

get_screen_cons() bounds the generated fingerprint to the monitor, but
BrowserForge honours a Screen constraint only when its pool has a match:
FingerprintGenerator.partial_csp catches the filtering failure and deletes the
constraint unless strict=True. So a 1366x768 laptop routinely gets a 2560x1440
fingerprint with window.outerWidth 1920, and browser-init resizes the real
chrome window to it -- rendering past the edge of the monitor.

Re-apply the bound after generation instead of trusting BrowserForge with it,
and pull screenX/screenY back inside the shrunken screen.

Headful only. headless has no window to overflow, and headless='virtual' runs a
1x1 Xvfb whose "monitor" would otherwise shrink the fingerprint to 1x1.

Fixes #499
This commit is contained in:
Pratyush Sharma
2026-07-28 00:29:03 -06:00
committed by Jake Writer
parent 63860c9cab
commit 2266f27501
3 changed files with 154 additions and 2 deletions
+46
View File
@@ -406,6 +406,52 @@ def clamp_window_dimensions(config: Dict[str, Any]) -> None:
config[f'window.inner{axis}'] = outer_clamped
def clamp_screen_to_display(
config: Dict[str, Any],
max_width: Optional[int],
max_height: Optional[int],
) -> None:
"""Shrink screen.width/height down to the bounds of the real display.
BrowserForge takes a Screen constraint but drops it silently whenever it
filters the fingerprint pool too far: FingerprintGenerator.partial_csp
swallows the resulting failure and deletes the constraint unless strict=True.
So the bound from get_screen_cons() is best-effort only, and a 1366x768
laptop routinely gets a 2560x1440 fingerprint. browser-init.patch resizes the
real chrome window to window.outerWidth/outerHeight, so an unbounded value
renders past the edge of the monitor (daijro/camoufox#499).
Keeps the taskbar delta (screen - avail) intact so fix_screen_no_taskbar's
invariant survives. Callers must run clamp_window_dimensions afterwards to
cascade the new bounds down to avail/outer/inner.
"""
for axis, cap in (('width', max_width), ('height', max_height)):
screen = config.get(f'screen.{axis}')
if not (screen and cap) or screen <= cap:
continue
avail_key = 'screen.availWidth' if axis == 'width' else 'screen.availHeight'
avail = config.get(avail_key)
config[f'screen.{axis}'] = cap
if avail:
config[avail_key] = max(1, cap - max(0, screen - avail))
def clamp_window_position(config: Dict[str, Any]) -> None:
"""Keep the window box inside the screen: 0 <= screenX/Y <= screen - outer.
BrowserForge's screenX/screenY are consistent with the screen it generated
them against, so clamp_screen_to_display invalidates them. A window
positioned partly off its own reported screen is an impossible geometry.
"""
for axis, pos_key in (('Width', 'window.screenX'), ('Height', 'window.screenY')):
screen = config.get(f'screen.{axis.lower()}')
outer = config.get(f'window.outer{axis}')
pos = config.get(pos_key)
if pos is None or not (screen and outer):
continue
config[pos_key] = max(0, min(pos, screen - outer))
def set_media_devices_defaults(config: Dict[str, Any]) -> None:
"""Spoof navigator.mediaDevices.enumerateDevices() so headless contexts
expose a plausible device list.
+13 -2
View File
@@ -21,7 +21,7 @@ from .exceptions import (
InvalidPropertyType,
NonFirefoxFingerprint,
)
from .fingerprints import from_browserforge, from_preset, generate_fingerprint, get_random_preset, _generate_random_font_subset, _generate_random_voice_subset, fix_navigator_arch, fix_screen_no_taskbar, clamp_window_dimensions, set_media_devices_defaults
from .fingerprints import from_browserforge, from_preset, generate_fingerprint, get_random_preset, _generate_random_font_subset, _generate_random_voice_subset, fix_navigator_arch, fix_screen_no_taskbar, clamp_screen_to_display, clamp_window_dimensions, clamp_window_position, set_media_devices_defaults
from .geolocation import geoip_allowed, get_geolocation
from .ip import Proxy, public_ip, valid_ipv4, valid_ipv6
from .locales import handle_locales
@@ -679,10 +679,14 @@ def launch_options(
merge_into(config, from_preset(preset, ff_version_str))
_used_preset = True
# Bound the geometry to the real display. BrowserForge only honours this when
# its pool has a match, so it is re-applied after generation as well.
screen_cons = screen or get_screen_cons(headless or 'DISPLAY' in env)
if not _used_preset and fingerprint is None:
# Default: BrowserForge synthetic generation (infinite unique fingerprints)
fingerprint = generate_fingerprint(
screen=screen or get_screen_cons(headless or 'DISPLAY' in env),
screen=screen_cons,
window=window,
os=os,
)
@@ -701,8 +705,15 @@ def launch_options(
if not _user_set_navigator:
fix_navigator_arch(config, target_os)
if not _user_set_screen_window:
# Headful only: this bound exists so the real window fits the monitor.
# headless has no window to overflow, and headless='virtual' runs a 1x1
# Xvfb (see virtdisplay.py) that would otherwise shrink the whole
# fingerprint to 1x1.
if headless is False and screen_cons:
clamp_screen_to_display(config, screen_cons.max_width, screen_cons.max_height)
fix_screen_no_taskbar(config, target_os)
clamp_window_dimensions(config)
clamp_window_position(config)
# Set a random window.history.length
set_into(config, 'window.history.length', randrange(1, 6)) # nosec
+95
View File
@@ -16,7 +16,9 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from camoufox.fingerprints import ( # noqa: E402
clamp_screen_to_display,
clamp_window_dimensions,
clamp_window_position,
fix_navigator_arch,
fix_screen_no_taskbar,
set_media_devices_defaults,
@@ -128,6 +130,99 @@ class TestClampWindowDimensions:
assert c["window.innerWidth"] == 1264
class TestClampScreenToDisplay:
def test_shrinks_screen_to_display(self):
# BrowserForge routinely ships a 2560x1440 fingerprint to a 1366x768
# laptop; browser-init then resizes the real window past the monitor.
c = {
"screen.width": 2560,
"screen.height": 1440,
"screen.availWidth": 2560,
"screen.availHeight": 1400,
}
clamp_screen_to_display(c, 1366, 768)
assert c["screen.width"] == 1366
assert c["screen.height"] == 768
# taskbar delta (1440-1400=40) preserved, so fix_screen_no_taskbar's
# avail < height invariant still holds
assert c["screen.availWidth"] == 1366
assert c["screen.availHeight"] == 768 - 40
def test_noop_when_already_within_display(self):
c = {"screen.width": 1280, "screen.height": 720, "screen.availHeight": 700}
clamp_screen_to_display(c, 1366, 768)
assert c["screen.width"] == 1280
assert c["screen.height"] == 720
assert c["screen.availHeight"] == 700
def test_ignores_unset_bounds(self):
c = {"screen.width": 2560, "screen.height": 1440}
clamp_screen_to_display(c, None, None)
assert c["screen.width"] == 2560
assert c["screen.height"] == 1440
def test_avail_never_drops_below_one(self):
# taskbar delta larger than the display must not produce a <= 0 avail
c = {"screen.height": 2000, "screen.availHeight": 100}
clamp_screen_to_display(c, None, 768)
assert c["screen.availHeight"] >= 1
def test_clamped_result_survives_cascade(self):
c = {
"screen.width": 2560,
"screen.height": 1440,
"screen.availWidth": 2560,
"screen.availHeight": 1400,
"window.outerWidth": 1920,
"window.outerHeight": 1055,
"window.innerWidth": 1920,
"window.innerHeight": 1000,
}
clamp_screen_to_display(c, 1366, 768)
clamp_window_dimensions(c)
assert c["window.outerWidth"] <= c["screen.availWidth"] <= c["screen.width"] == 1366
assert c["window.outerHeight"] <= c["screen.availHeight"] <= c["screen.height"] == 768
assert c["window.innerWidth"] <= c["window.outerWidth"]
assert c["window.innerHeight"] <= c["window.outerHeight"]
class TestClampWindowPosition:
def test_pulls_window_back_inside_screen(self):
c = {
"screen.width": 1366,
"screen.height": 768,
"window.outerWidth": 1366,
"window.outerHeight": 728,
"window.screenX": 250,
"window.screenY": 281,
}
clamp_window_position(c)
assert c["window.screenX"] == 0
assert c["window.screenY"] == 40
def test_noop_when_window_already_inside(self):
c = {
"screen.width": 1920,
"screen.height": 1080,
"window.outerWidth": 1280,
"window.outerHeight": 720,
"window.screenX": 100,
"window.screenY": 50,
}
clamp_window_position(c)
assert c["window.screenX"] == 100
assert c["window.screenY"] == 50
def test_never_negative(self):
c = {
"screen.width": 800,
"window.outerWidth": 1000, # wider than screen
"window.screenX": 50,
}
clamp_window_position(c)
assert c["window.screenX"] == 0
class TestSetMediaDevicesDefaults:
def test_sets_one_mic_one_cam(self):
c = {}