Skip to content

Clamp headful window geometry to the real display - #674

Open
pratyush618 wants to merge 2 commits into
daijro:mainfrom
pratyush618:fix/499-window-exceeds-display
Open

Clamp headful window geometry to the real display#674
pratyush618 wants to merge 2 commits into
daijro:mainfrom
pratyush618:fix/499-window-exceeds-display

Conversation

@pratyush618

Copy link
Copy Markdown
Contributor

Fixes #499.

Problem

In headful mode the browser window is sometimes larger than the monitor, with rendering artifacts along the right and bottom margins. Reported on a 1366x768 laptop.

Cause

get_screen_cons() already computes the right bound — Screen(max_width=1366, max_height=768) — and hands it to BrowserForge. BrowserForge then throws it away.

FingerprintGenerator.partial_csp filters the fingerprint pool by the screen constraint, and when that filtering fails it swallows the error and drops the constraint entirely unless strict=True:

try:
    return get_possible_values(self.fingerprint_generator_network, filtered_values)
except Exception as e:
    if strict:
        raise e
    del filtered_values['screen']
return None

Camoufox doesn't pass strict, so the bound is silently discarded and a 1366x768 laptop gets a 2560x1440 fingerprint with window.outerWidth 1920. browser-init.patch then calls window.resizeTo(outerWidth, outerHeight) on the real chrome window, so the oversized value becomes a real oversized window.

Measured on an Xvfb sized to the reporter's 1366x768, the real X window reached 2225x1238.

Change

Re-apply the bound after generation rather than trusting BrowserForge with it:

  • clamp_screen_to_display() — shrinks screen.width/height to the display, preserving the taskbar delta so fix_screen_no_taskbar's avail < height invariant still holds. The existing clamp_window_dimensions() then cascades the new bounds down to avail/outer/inner.
  • clamp_window_position() — pulls screenX/screenY back inside the screen. Shrinking the screen invalidates the positions BrowserForge generated against the original one, which would otherwise leave the window box partly outside its own reported screen.

Headful only

The clamp is gated on headless is False, which matters more than it looks:

  • headless has no window to overflow.
  • headless='virtual' launches Xvfb with -screen 0 1x1x24 (virtdisplay.py), and env is os.environ, so env['DISPLAY'] = virtual_display mutates the caller's environment. Under that display get_screen_cons(True) returns Screen(max_width=1, max_height=1) — verified. An unconditional clamp would shrink those fingerprints to a 1x1 screen. It is harmless today only because BrowserForge discards the constraint.

Verification

Against the released binary on an Xvfb at 1366x768:

before after
generated geometry fits the display 7/40 40/40
full geometry invariants hold 17/60 50/50

Invariants checked: inner <= outer <= avail <= screen, screen <= display, avail < screen (noTaskbar tell), all dimensions positive, and 0 <= screenX/Y <= screen - outer.

Real window after the fix: visible chrome 1366x720 at +5+5, inside the display. The toplevel X window reads 1376x730 because Firefox carries a 5px invisible shadow border on each side (there is no window manager under Xvfb, and no _GTK_FRAME_EXTENTS); the child window matches window.outerWidth exactly.

Headless and headless='virtual' are unaffected — sampled under the 1x1 Xvfb they still produce 3072x1728 and 3440x1440, so no fingerprint diversity is lost.

Tests

pythonlib/tests/test_fingerprint_fixes.py, 12 -> 20 passing. Covers the shrink, no-op when already within the display, unset bounds, the avail floor, composition with clamp_window_dimensions (the ordering is load-bearing), the position clamp, and the negative guard.

Full pythonlib suite: 53 passed. The 2 test_virtdisplay.py failures are pre-existing and reproduce unchanged on a clean tree.

Not addressed

BrowserForge silently dropping non-strict constraints is an upstream bug. This change defends against it rather than depending on it being fixed.

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 daijro#499
headless='virtual' reaches launch_options as headless=False with
virtual_display set (async_api rewrites it), so the headful gate fired and
clamped the fingerprint to Xvfb's 1x1 stub. fix_screen_no_taskbar then drove
availHeight to -39 and validate_config rejected the launch outright.
@pratyush618

Copy link
Copy Markdown
Contributor Author

Follow-up pushed: headless='virtual' reaches launch_options as headless=False with virtual_display set (async_api.py rewrites it before the call), so the headless is False gate fired for it after all.

Under the 1x1 Xvfb the clamp shrank screen.height to 1, fix_screen_no_taskbar then subtracted the 40px taskbar, and validate_config rejected the launch:

camoufox.exceptions.InvalidPropertyType: Invalid type for property screen.availHeight. Expected uint, got int

Gate now also excludes virtual_display, with a launch-level regression test in tests/test_launch_geometry.py (fails with the exception above without the fix).

#694 is stacked on this branch.

@JWriter20 JWriter20 mentioned this pull request Jul 30, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Window rendering defect. Flickering effect on right and bottom margins.

1 participant