mirror of
https://github.com/daijro/camoufox.git
synced 2026-10-03 16:00:19 +00:00
fix(juggler): never enter Responsive Design Mode, and warn about is_mobile
#798 left RDM on for isMobile, as Playwright's Juggler does. RDM matches no real browser: Firefox for Android never runs it, and under touch emulation it drops a mouse click's pointer events, which no device does. Camoufox only has desktop identities, so an is_mobile context was a desktop UA, platform, fonts and GPU with devtools' mobile mode on top. Juggler now keeps inRDMPane off for every page, is_mobile included, and the isMobile plumbing #798 added is gone again. viewport-no-rdm requires is_mobile=True to keep the platform's scrollbars too. Both launchers warn instead (warnings.yml is_mobile): on new_page() / new_context(is_mobile=True) of a Camoufox browser, and on is_mobile passed to launch_options() for a persistent context. has_touch, device_scale_factor and viewport keep working without RDM. Upstream playwright-python skips its isMobile tests on Firefox in 1.61-1.63, so no skiplist entries are needed. On beta.31 with this Juggler in omni.ja, viewport-no-rdm passes: 12/12 px of scrollbar with no viewport, with a viewport, and with is_mobile, and a has_touch click fires pointerdown and pointerup. pythonlib 411 passed; typescript 584 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
22a9bc581f
commit
8081061156
@@ -17,6 +17,7 @@ from .ip import Proxy, proxy_exit_geo
|
||||
from .utils import (
|
||||
async_attach_vd,
|
||||
attach_no_viewport_default,
|
||||
attach_desktop_only_warning,
|
||||
attach_stock_media_defaults,
|
||||
launch_options,
|
||||
STOCK_MEDIA_DEFAULTS,
|
||||
@@ -173,6 +174,7 @@ async def _launch(
|
||||
if no_viewport_default:
|
||||
attach_no_viewport_default(browser)
|
||||
attach_stock_media_defaults(browser)
|
||||
attach_desktop_only_warning(browser)
|
||||
return await async_attach_vd(browser, virtual_display)
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from .fingerprints import generate_context_fingerprint
|
||||
from .ip import Proxy, proxy_exit_geo
|
||||
from .utils import (
|
||||
attach_no_viewport_default,
|
||||
attach_desktop_only_warning,
|
||||
attach_stock_media_defaults,
|
||||
launch_options,
|
||||
STOCK_MEDIA_DEFAULTS,
|
||||
@@ -148,6 +149,7 @@ def NewBrowser(
|
||||
if no_viewport_default:
|
||||
attach_no_viewport_default(browser)
|
||||
attach_stock_media_defaults(browser)
|
||||
attach_desktop_only_warning(browser)
|
||||
return sync_attach_vd(browser, virtual_display)
|
||||
finally:
|
||||
if pid:
|
||||
|
||||
@@ -683,6 +683,27 @@ def attach_stock_media_defaults(target: Any) -> Any:
|
||||
return target
|
||||
|
||||
|
||||
def attach_desktop_only_warning(target: Any) -> Any:
|
||||
"""Warn when new_page()/new_context() asks for is_mobile: Camoufox only has
|
||||
desktop identities, and Juggler ignores the option (TargetRegistry.js)."""
|
||||
for name in ('new_page', 'new_context'):
|
||||
original = getattr(target, name, None)
|
||||
if original is None:
|
||||
continue
|
||||
|
||||
def wrap(original: Any) -> Any:
|
||||
@wraps(original)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
if kwargs.get('is_mobile'):
|
||||
LeakWarning.warn('is_mobile')
|
||||
return original(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
setattr(target, name, wrap(original))
|
||||
return target
|
||||
|
||||
|
||||
def attach_no_viewport_default(target: Any) -> Any:
|
||||
"""
|
||||
Default new_page()/new_context() to no_viewport=True.
|
||||
@@ -1417,6 +1438,9 @@ def launch_options(
|
||||
if disable_coop:
|
||||
LeakWarning.warn('disable_coop', i_know_what_im_doing)
|
||||
firefox_user_prefs['browser.tabs.remote.useCrossOriginOpenerPolicy'] = False
|
||||
# A persistent context takes its context options here.
|
||||
if launch_options.get('is_mobile'):
|
||||
LeakWarning.warn('is_mobile', i_know_what_im_doing)
|
||||
|
||||
# Drop values the source supplied that this identity cannot keep, before the
|
||||
# WebGL pool below defers to them (a preset's own GPU pair wins over
|
||||
|
||||
@@ -41,6 +41,13 @@ instant_animations: >-
|
||||
never waits on one. A page can see it: getComputedTiming() reports a duration
|
||||
of 0 where stock Firefox reports the real one.
|
||||
|
||||
is_mobile: >-
|
||||
Camoufox is built for desktops and only draws desktop identities, so is_mobile
|
||||
cannot produce a believable phone and will leak: the page still sees a desktop
|
||||
User-Agent, platform, fonts, GPU and screen. Camoufox ignores is_mobile rather
|
||||
than put the page in Responsive Design Mode, which a page can read too.
|
||||
Use has_touch, device_scale_factor and viewport for the parts you need.
|
||||
|
||||
block_webgl: >-
|
||||
Disabling WebGL is not recommended. Many WAFs will check if WebGL is enabled.
|
||||
|
||||
|
||||
@@ -191,3 +191,11 @@ def test_instant_animations_warn_that_they_are_detectable():
|
||||
|
||||
with pytest.warns(LeakWarning, match="getComputedTiming"):
|
||||
launch(config={"instantAnimations": True}, i_know_what_im_doing=False)
|
||||
|
||||
|
||||
def test_is_mobile_warns_that_camoufox_is_desktop_only():
|
||||
"""A persistent context takes its context options at launch."""
|
||||
from camoufox._warnings import LeakWarning
|
||||
|
||||
with pytest.warns(LeakWarning, match="built for desktops"):
|
||||
launch(is_mobile=True, i_know_what_im_doing=False)
|
||||
|
||||
@@ -388,3 +388,32 @@ class TestStockMediaDefaults:
|
||||
assert browser.kwargs["forced_colors"] == "active"
|
||||
# the ones the caller left alone still follow the host
|
||||
assert browser.kwargs["reduced_motion"] == "no-override"
|
||||
|
||||
|
||||
class TestDesktopOnlyWarning:
|
||||
"""Camoufox has only desktop identities and Juggler never enters Responsive
|
||||
Design Mode, so is_mobile on new_page()/new_context() gets a LeakWarning."""
|
||||
|
||||
class FakeBrowser:
|
||||
def new_page(self, **kwargs):
|
||||
return kwargs
|
||||
|
||||
def new_context(self, **kwargs):
|
||||
return kwargs
|
||||
|
||||
def test_is_mobile_warns(self):
|
||||
from camoufox._warnings import LeakWarning
|
||||
|
||||
browser = utils.attach_desktop_only_warning(self.FakeBrowser())
|
||||
for create in (browser.new_page, browser.new_context):
|
||||
with pytest.warns(LeakWarning, match="built for desktops"):
|
||||
assert create(is_mobile=True) == {"is_mobile": True}
|
||||
|
||||
def test_a_desktop_context_does_not(self):
|
||||
import warnings
|
||||
|
||||
browser = utils.attach_desktop_only_warning(self.FakeBrowser())
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
browser.new_context(viewport={"width": 800, "height": 600}, is_mobile=False)
|
||||
browser.new_page()
|
||||
|
||||
Reference in New Issue
Block a user