Files
camoufox/pythonlib/tests/test_coherence.py
T
coffeegrind123 c2817c1cae fix(coherence): let macOS draw Intel Mac GPUs, checked as machines
coherence rejected "Intel(R) HD Graphics 400" and "Radeon R9 200
Series" on macOS as a Braswell Atom IGP and a desktop PC card. They are
Firefox's sanitized buckets, not devices: SanitizeRenderer
(FIREFOX_152_0_4_RELEASE) maps an Intel Mac's UHD 630 to the first and
a Radeon Pro 5300M to the second, per Firefox's own TestCiMac and
TestMacAmd. fpgen's pinned model records both from Firefox on macOS at
1.14% each. The rule kept every generated Mac off them, removed 24
real macOS presets in #779 (restored here, 9 + 15), and dropped either
GPU from a caller's Mac preset. ANGLE and llvmpipe stay rejected:
Firefox 152 has no ANGLE-on-Metal path (Bug 2046027 came later).

Intel Macs are now checked as machines instead (intel-mac-hardware),
for every non-Apple GPU on macOS:

- a core count some Intel Mac with that GPU reports. Firefox reports
  physical cores where kern.tcsm_available is set and logical ones
  otherwise, so either counts: 2-8 physical / 4-16 logical for the
  IGP, up to the 2019 Mac Pro for a discrete GPU;
- a screen that is not a notched MacBook's or the 24" iMac's. 45.5% of
  fpgen's Firefox macOS screens are one, and the draw already put an
  Intel or AMD bucket behind 4.9% of them.

The WebGL draw applies the same check, given the core count. The preset
GPU drop moves next to the WebGL draw, after the host core count and
the display clamp: before, a preset's GPU was judged against cores the
launch then replaced. The TypeScript launcher gets the same changes,
and the goldens cover the new check and the narrowed draws.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d6e2adcb6c)
2026-09-27 20:09:03 +00:00

317 lines
17 KiB
Python

"""Every identity Camoufox can produce has to be a machine that could exist.
The pools are sampled independently -- navigator and screen from fpgen, the GPU
from fpgen's WebGL records, fonts and voices from their own catalogues -- so an
incoherent identity is assembled rather than inherited, and cleaning the pools
cannot prevent it. These tests run the assembled identity, from every source, past
camoufox.coherence.
Measured before the layer existed (2026-09-17): 7% of macOS identities paired
Apple Silicon with colorDepth 24; 2% of Windows identities reported
maxTouchPoints 256; and bundled presets carried screens no desktop has.
"Intel(R) HD Graphics 400" and "Radeon R9 200 Series" on macOS are not in that
list: they are the buckets Firefox reports for an Intel Mac's UHD 630 and a
Radeon Pro 5300M (its own TestCiMac and TestMacAmd cases). What an Intel Mac
cannot have is a notched Apple Silicon panel, or a core count no Intel Mac with
that GPU reports.
"""
import re
import sys
from os.path import dirname, join
import pytest
sys.path.insert(0, join(dirname(__file__), ".."))
from camoufox import coherence # noqa: E402
from camoufox import fingerprints as fp # noqa: E402
from camoufox.utils import get_target_os # noqa: E402
from camoufox.webgl import sample_webgl_for_screen # noqa: E402
from test_identity_salt import launch # noqa: E402
def _firefox_bucket(raw):
"""The AMD and Intel branches of Firefox's SanitizeRenderer for a macOS
GL_RENDERER (dom/canvas/SanitizeRenderer.cpp, FIREFOX_152_0_4_RELEASE)."""
device = re.sub(r" OpenGL Engine$", "", raw)
if any(part in device for part in ("REMBRANDT", "RENOIR", "Vega", "VII", "Fury")):
return "Radeon R9 200 Series, or similar"
radeon = re.search(r"Radeon.*?((R[579X]|HD) )?([0-9][0-9][0-9]+)", device)
if radeon:
if radeon.group(2) == "HD":
model = int(radeon.group(3))
return ("Radeon HD 5850" if model >= 5000 else "Radeon HD 3200 Graphics") + ", or similar"
return "Radeon R9 200 Series, or similar"
intel = re.search(r"Intel.*Graphics( P?([0-9][0-9][0-9]+))?", device)
if intel:
if not intel.group(1):
return "Intel(R) HD Graphics, or similar"
model = int(intel.group(2))
return ("Intel(R) HD Graphics" if 1000 <= model < 5000 else "Intel(R) HD Graphics 400") + ", or similar"
raise ValueError(f"not an AMD or Intel renderer: {raw!r}")
# What Intel Macs' GPUs report through Firefox. An Intel model number under
# 1000 or of 5000 and over lands in "HD Graphics 400"; a Radeon without an "HD"
# prefix lands in "R9 200 Series".
MAC_RENDERERS = [
("Intel(R) UHD Graphics 630", "Intel(R) HD Graphics 400, or similar"), # Mac mini 2018, 15"/16" MacBook Pro
("Intel(R) Iris(TM) Plus Graphics 655", "Intel(R) HD Graphics 400, or similar"), # 13" MacBook Pro 2018-19
("Intel HD Graphics 6000", "Intel(R) HD Graphics 400, or similar"), # MacBook Air 2015-17
("Intel(R) Iris(TM) Plus Graphics", "Intel(R) HD Graphics, or similar"), # MacBook Air 2020
("Intel HD Graphics 4000", "Intel(R) HD Graphics, or similar"), # MacBook Air / Mac mini 2012
("AMD Radeon Pro 5300M OpenGL Engine", "Radeon R9 200 Series, or similar"), # 16" MacBook Pro
("AMD Radeon Pro 560X OpenGL Engine", "Radeon R9 200 Series, or similar"), # 15" MacBook Pro 2018
("AMD Radeon Pro Vega 56 OpenGL Engine", "Radeon R9 200 Series, or similar"), # iMac Pro
]
class TestRules:
"""Each rule, against the value that motivated it."""
def test_apple_silicon_never_has_fewer_than_eight_cores(self):
config = {"webGl:renderer": "Apple M1, or similar", "navigator.hardwareConcurrency": 2}
assert [v.rule for v in coherence.validate(config, "mac")] == ["apple-silicon-cores"]
assert coherence.apply(config, "mac") == []
assert config["navigator.hardwareConcurrency"] == 8
def test_the_bucket_port_agrees_with_firefoxs_own_mac_cases(self):
# dom/canvas/gtest/TestSanitizeRenderer.cpp, FIREFOX_152_0_4_RELEASE.
assert _firefox_bucket("AMD Radeon Pro 5300M OpenGL Engine") == "Radeon R9 200 Series, or similar"
assert _firefox_bucket("Intel(R) UHD Graphics 630") == "Intel(R) HD Graphics 400, or similar"
@pytest.mark.parametrize("raw, bucket", MAC_RENDERERS)
def test_an_intel_mac_reports_its_firefox_bucket(self, raw, bucket):
assert _firefox_bucket(raw) == bucket
assert coherence.gpu_fits_os(bucket, "mac"), raw
def test_a_mac_cannot_report_an_angle_renderer(self):
# Firefox 152 renders WebGL on macOS through CGL, and its sanitizer has
# no ANGLE-on-Metal branch (Bug 2046027 added one after 152).
config = {"webGl:renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0), or similar"}
assert [v.rule for v in coherence.validate(config, "mac")] == ["gpu-matches-os"]
def test_a_mac_cannot_report_a_software_rasteriser(self):
assert not coherence.gpu_fits_os("llvmpipe, or similar", "mac")
def test_an_intel_igp_mac_reports_its_physical_or_logical_cores(self):
igp = "Intel(R) HD Graphics 400, or similar"
for cores in (2, 4, 6, 8, 12, 16):
assert coherence.validate({"webGl:renderer": igp, "navigator.hardwareConcurrency": cores}, "mac") == [], cores
# No Mac that runs WebGL on its Intel IGP has more than 8 cores.
for cores in (10, 14, 20, 24, 32):
config = {"webGl:renderer": igp, "navigator.hardwareConcurrency": cores}
assert [v.rule for v in coherence.validate(config, "mac")] == ["intel-mac-hardware"], cores
def test_a_discrete_gpu_mac_reaches_the_mac_pro(self):
amd = "Radeon R9 200 Series, or similar"
for cores in (2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 48, 56):
assert coherence.validate({"webGl:renderer": amd, "navigator.hardwareConcurrency": cores}, "mac") == [], cores
config = {"webGl:renderer": amd, "navigator.hardwareConcurrency": 22}
assert [v.rule for v in coherence.validate(config, "mac")] == ["intel-mac-hardware"]
@pytest.mark.parametrize("renderer", [
"Intel(R) HD Graphics 400, or similar",
"Intel(R) HD Graphics, or similar",
"Radeon R9 200 Series, or similar",
"Radeon HD 3200 Graphics, or similar",
])
def test_an_intel_mac_has_no_notched_panel(self, renderer):
for width, height in ((1470, 956), (1512, 982), (1728, 1117), (2056, 1329), (1710, 1107)):
config = {"webGl:renderer": renderer, "screen.width": width, "screen.height": height}
assert [v.rule for v in coherence.validate(config, "mac")] == ["intel-mac-hardware"], (width, height)
# The same panels are what Apple Silicon MacBooks report.
assert coherence.validate({"webGl:renderer": "Apple M1, or similar", "screen.width": 1470,
"screen.height": 956}, "mac") == []
def test_a_preset_gpu_the_final_identity_contradicts_is_dropped(self):
config = {"webGl:vendor": "Intel Inc.", "webGl:renderer": "Intel(R) HD Graphics 400, or similar",
"navigator.hardwareConcurrency": 20}
assert [v.rule for v in coherence.drop_incoherent_source_values(config, "mac")] == ["intel-mac-hardware"]
assert "webGl:renderer" not in config and "webGl:vendor" not in config
def test_windows_renders_through_angle(self):
assert coherence.gpu_fits_os("ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11), or similar", "win")
assert not coherence.gpu_fits_os("Apple M1, or similar", "win")
def test_apple_silicon_reports_deep_colour(self):
# Measured on jobharvest-mac: colorDepth 30, (color: 10).
config = {"webGl:renderer": "Apple M1, or similar", "screen.colorDepth": 24}
assert [v.rule for v in coherence.validate(config, "mac")] == ["color-depth"]
assert coherence.apply(config, "mac") == []
assert config["screen.colorDepth"] == 30
assert config["screen.pixelDepth"] == 30
def test_colour_depth_is_24_or_30(self):
config = {"screen.colorDepth": 32}
assert [v.rule for v in coherence.validate(config, "lin")] == ["color-depth"]
assert coherence.apply(config, "lin") == []
assert config["screen.colorDepth"] == 24
def test_touch_points_are_a_digitiser_count(self):
config = {"navigator.maxTouchPoints": 256}
assert [v.rule for v in coherence.validate(config, "win")] == ["touch-points"]
assert coherence.apply(config, "win") == []
assert config["navigator.maxTouchPoints"] == 0
# 5 is real -- measured on win-i9, a touchscreen laptop -- and so are
# 2 and 10; fpgen offers none of 2/5 and the presets carry 40.
for real in (0, 1, 2, 5, 10):
assert coherence.validate({"navigator.maxTouchPoints": real}, "win") == [], real
def test_a_mac_has_no_touchscreen(self):
config = {"navigator.maxTouchPoints": 5}
assert [v.rule for v in coherence.validate(config, "mac")] == ["touch-points"]
def test_device_pixel_ratio_is_a_real_display_mode(self):
# 1.818 is a scraped artefact: a zoom level folded into the ratio.
config = {"window.devicePixelRatio": 1.8181818181818181}
assert [v.rule for v in coherence.validate(config, "win")] == ["device-pixel-ratio"]
assert coherence.apply(config, "win") == []
assert config["window.devicePixelRatio"] == 1.75
# The three measured machines: 1 on linux, 2.5 on win-i9, 2 on the Mac.
assert coherence.validate({"window.devicePixelRatio": 1}, "lin") == []
assert coherence.validate({"window.devicePixelRatio": 2.5}, "win") == []
assert coherence.validate({"window.devicePixelRatio": 2}, "mac") == []
# macOS has no fractional scaling.
assert [v.rule for v in coherence.validate({"window.devicePixelRatio": 1.5}, "mac")] == [
"device-pixel-ratio"
]
def test_desktop_screens_are_landscape(self):
config = {"screen.width": 1440, "screen.height": 2560}
assert [v.rule for v in coherence.validate(config, "win")] == ["screen-shape"]
assert coherence.repair_screen_orientation(config)
assert (config["screen.width"], config["screen.height"]) == (2560, 1440)
def test_a_phone_viewport_is_not_a_desktop_screen(self):
# One bundled preset reports this.
assert [v.rule for v in coherence.validate({"screen.width": 736, "screen.height": 414}, "mac")] == [
"screen-shape"
]
def test_avail_never_exceeds_the_screen(self):
config = {"screen.width": 1920, "screen.height": 1080, "screen.availWidth": 2000}
assert [v.rule for v in coherence.validate(config, "lin")] == ["avail-bounds"]
assert coherence.apply(config, "lin") == []
assert config["screen.availWidth"] == 1920
def test_platform_agrees_with_the_user_agent_arch(self):
config = {
"navigator.userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0",
"navigator.platform": "Linux armv81",
}
assert [v.rule for v in coherence.validate(config, "lin")] == ["arch-agreement"]
def test_the_real_machines_pass(self):
"""The three machines captured on 2026-09-17, as themselves."""
real = [
("lin", {"navigator.userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0",
"navigator.platform": "Linux x86_64", "navigator.hardwareConcurrency": 16,
"navigator.maxTouchPoints": 0, "screen.width": 1920, "screen.height": 1080,
"screen.colorDepth": 24, "webGl:renderer": "Radeon HD 3200 Graphics, or similar"}),
("win", {"navigator.platform": "Win32", "navigator.hardwareConcurrency": 16,
"navigator.maxTouchPoints": 5, "screen.width": 1382, "screen.height": 864,
"screen.colorDepth": 24,
"webGl:renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar"}),
("mac", {"navigator.platform": "MacIntel", "navigator.hardwareConcurrency": 10,
"navigator.maxTouchPoints": 0, "screen.width": 2560, "screen.height": 1440,
"screen.colorDepth": 30, "webGl:renderer": "Apple M1, or similar"}),
# fpgen model-2/2026's Radeon R9 200 macOS record: a 16" MacBook Pro
# (6 physical cores) at its default scaled resolution.
("mac", {"navigator.platform": "MacIntel", "navigator.hardwareConcurrency": 6,
"navigator.maxTouchPoints": 0, "screen.width": 1792, "screen.height": 1120,
"screen.colorDepth": 30, "webGl:renderer": "Radeon R9 200 Series, or similar"}),
]
for target_os, config in real:
assert coherence.validate(config, target_os) == [], target_os
INTEL_MAC_BUCKETS = {"Intel(R) HD Graphics 400, or similar", "Radeon R9 200 Series, or similar"}
class TestIntelMacDraws:
"""The draw offers Intel Macs, but only beside a core count and a screen an
Intel Mac could have."""
def test_every_intel_mac_bucket_is_drawn(self):
# fpgen model-2/2026 records each at 1.1% of Firefox on macOS.
drawn = {sample_webgl_for_screen("mac", 2560, 1440, seed=s)["webGl:renderer"] for s in range(300)}
assert INTEL_MAC_BUCKETS <= drawn
def test_no_intel_mac_behind_a_notched_panel(self):
for seed in range(300):
renderer = sample_webgl_for_screen("mac", 1470, 956, seed=seed, cores=8)["webGl:renderer"]
assert "Apple M" in renderer, (seed, renderer)
def test_no_intel_igp_beside_a_core_count_no_intel_mac_reports(self):
drawn = {sample_webgl_for_screen("mac", 2560, 1440, seed=s, cores=20)["webGl:renderer"] for s in range(300)}
assert not any("Intel" in renderer for renderer in drawn), drawn
def test_a_preset_gpu_is_checked_against_the_host_core_count(self, monkeypatch):
"""A launch replaces the preset's core count with the host's, after the
preset's GPU has been read: the GPU has to fit the count that ships."""
preset = dict(fp.load_presets("150")["presets"]["macos"][0])
preset["navigator"] = {**preset["navigator"], "hardwareConcurrency": 8}
preset["screen"] = {**preset["screen"], "width": 1440, "height": 900, "availWidth": 1440,
"availHeight": 875}
preset["webgl"] = {**preset["webgl"], "unmaskedVendor": "Intel Inc.",
"unmaskedRenderer": "Intel(R) HD Graphics 400, or similar"}
monkeypatch.setattr(fp, "host_cpu_count", lambda: 20)
config = launch(os="macos", fingerprint_preset=preset)
assert config["navigator.hardwareConcurrency"] == 20
assert "Intel" not in config["webGl:renderer"]
assert coherence.validate(config, "mac") == []
class TestEveryIdentityIsCoherent:
"""The assembled identity, from each source Camoufox draws one from."""
@pytest.mark.parametrize("os_name", ["macos", "windows", "linux"])
def test_generated_identities(self, os_name):
for _ in range(40):
config = launch(os=os_name)
assert coherence.validate(config, get_target_os(config)) == []
@pytest.mark.parametrize("os_name", ["macos", "windows", "linux"])
def test_every_bundled_preset(self, os_name):
for i, preset in enumerate(fp.load_presets("150")["presets"][os_name]):
config = launch(os=os_name, fingerprint_preset=preset)
assert coherence.validate(config, get_target_os(config)) == [], (os_name, i)
_MIDPOINT_REPAIRS = """
from camoufox import coherence
out = []
for os_key, steps in sorted(coherence.PLAUSIBLE_DPR.items()):
steps = sorted(steps)
for low, high in zip(steps, steps[1:]):
config = {"window.devicePixelRatio": (low + high) / 2}
coherence.apply(config, os_key)
out.append(config["window.devicePixelRatio"])
print(out)
"""
def test_a_midpoint_repairs_to_the_lower_step_whether_or_not_bytecode_is_cached(tmp_path):
"""The steps were frozensets, and min() keeps the first of equal distances.
A frozenset literal iterates in one order when compiled from source and in
another when loaded back from a .pyc, so the same identity repaired
differently on its first launch than on later ones."""
import subprocess
import sys
from pathlib import Path
env = {"PYTHONPYCACHEPREFIX": str(tmp_path), "PYTHONPATH": str(Path(coherence.__file__).parents[1])}
runs = [
subprocess.run([sys.executable, "-c", _MIDPOINT_REPAIRS], env=env, capture_output=True,
text=True, check=True).stdout
for _ in range(2) # the first compiles and writes the .pyc, the second loads it
]
assert runs[0] == runs[1]
lower = [low for _, steps in sorted(coherence.PLAUSIBLE_DPR.items())
for low in sorted(steps)[:-1]]
assert runs[0].strip() == str(lower)