Apply screen constraints on Windows and macOS

get_screen_cons() was gated on DISPLAY being set, which only ever happens on
Linux, so headful runs on Windows and macOS generated fingerprints with no
monitor bound at all.

Fixes #425
This commit is contained in:
Pratyush Sharma
2026-07-28 00:29:03 -06:00
committed by Jake Writer
parent 22c6ffbdda
commit a5afa46cfa
4 changed files with 80 additions and 3 deletions
+14 -1
View File
@@ -12,7 +12,7 @@ macOS (`NSScreen.frame`) and X11 (xrandr) already report CSS pixels, so scaling
only ever applies on Windows.
"""
from typing import Any, NamedTuple, Optional
from typing import Any, Mapping, NamedTuple, Optional
from screeninfo import get_monitors
@@ -33,6 +33,19 @@ class DisplaySize(NamedTuple):
height: int
def has_display(env: Mapping[str, Any]) -> bool:
"""
Whether the host has a desktop session for Camoufox's window to open on.
DISPLAY / WAYLAND_DISPLAY only ever exist on Linux, so they cannot be the
sole probe: keying off DISPLAY alone skipped the screen constraints entirely
on Windows and macOS, where a session is always present.
"""
if OS_NAME != 'lin':
return True
return bool(env.get('DISPLAY') or env.get('WAYLAND_DISPLAY'))
def largest_display() -> Optional[DisplaySize]:
"""
Size of the roomiest attached monitor in CSS pixels, or None when the
+2 -2
View File
@@ -15,7 +15,7 @@ from typing_extensions import TypeAlias
from ua_parser import user_agent_parser
from .addons import DefaultAddons, add_default_addons, confirm_paths
from .display import largest_display
from .display import has_display, largest_display
from .exceptions import (
InvalidOS,
InvalidPropertyType,
@@ -678,7 +678,7 @@ def launch_options(
# 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)
screen_cons = screen or get_screen_cons(headless or has_display(env))
if not _used_preset and fingerprint is None:
# Default: BrowserForge synthetic generation (infinite unique fingerprints)
+21
View File
@@ -90,3 +90,24 @@ class TestLargestDisplay:
monkeypatch.setattr(display, "get_monitors", boom)
assert display.largest_display() is None
class TestHasDisplay:
@pytest.mark.parametrize("os_name", ["win", "mac"])
def test_always_present_off_linux(self, monkeypatch, os_name):
"""Regression: keying off DISPLAY alone skipped Windows/macOS entirely."""
monkeypatch.setattr(display, "OS_NAME", os_name)
assert display.has_display({}) is True
@pytest.mark.parametrize(
("env", "expected"),
[
({}, False),
({"DISPLAY": ":0"}, True),
({"WAYLAND_DISPLAY": "wayland-0"}, True),
({"DISPLAY": ""}, False),
],
)
def test_linux_requires_a_session(self, monkeypatch, env, expected):
monkeypatch.setattr(display, "OS_NAME", "lin")
assert display.has_display(env) is expected
+43
View File
@@ -13,6 +13,7 @@ from unittest import mock
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import orjson # noqa: E402
import pytest # noqa: E402
from browserforge.fingerprints import Screen # noqa: E402
from camoufox import utils # noqa: E402
@@ -68,3 +69,45 @@ class TestVirtualDisplayIsNotAScreen:
for key, value in config.items():
if key.startswith("screen.") or key.startswith("window.outer"):
assert value >= 0, f"{key} is negative: {value}"
# A 1920x1080 panel at 150% Windows scaling, in CSS pixels
SCALED_DISPLAY = Screen(max_width=1280, max_height=720)
# Config key -> the display bound it must respect
BOUNDED_BY = {
"screen.width": "max_width",
"screen.height": "max_height",
"window.outerWidth": "max_width",
"window.outerHeight": "max_height",
}
class TestHeadfulFitsOnDisplay:
"""BrowserForge drops a screen constraint it cannot satisfy, so assert the
outcome rather than trusting the constraint to have been honoured."""
@pytest.mark.parametrize("attempt", range(15))
def test_generated_geometry_never_exceeds_the_display(self, attempt):
with host(SCALED_DISPLAY):
config = launch(headless=False)
for key, bound in BOUNDED_BY.items():
limit = getattr(SCALED_DISPLAY, bound)
assert config[key] <= limit, f"{key}={config[key]} exceeds {limit}"
def test_geometry_stays_internally_consistent(self):
with host(SCALED_DISPLAY):
config = launch(headless=False)
assert config["screen.availWidth"] <= config["screen.width"]
assert config["screen.availHeight"] < config["screen.height"] # taskbar
assert config["window.outerWidth"] <= config["screen.availWidth"]
assert config["window.outerHeight"] <= config["screen.availHeight"]
def test_unprobeable_display_is_not_clamped(self):
"""A host we cannot measure must not silently shrink the fingerprint."""
with host(None), mock.patch.object(utils, "clamp_screen_to_display") as clamp:
launch(headless=False)
clamp.assert_not_called()