diff --git a/fingerprint_client.py b/fingerprint_client.py index 29c2ebc..11e295c 100644 --- a/fingerprint_client.py +++ b/fingerprint_client.py @@ -241,6 +241,20 @@ def playwright_context_kwargs(fp: dict) -> dict: kwargs["viewport"] = {"width": outer_w, "height": max(400, outer_h)} kwargs["screen"] = {"width": width, "height": height} + # The device pixel ratio, which Playwright takes as its own context + # option and which was previously dropped on the floor. Measured + # 2026-09-11 against the live API and a live browser: a fingerprint + # stating `deviceScaleFactor: 1.25` produced a browser reporting + # `window.devicePixelRatio === 1`, so the identity contradicted itself + # on an axis any fingerprinter reads for free -- and a contradiction is + # exactly what this flag exists to avoid. Same shape as the three + # defects the family notes already list for this file: a key the API + # returns that nothing applies. Found by comparing a live browser + # against the fingerprint rather than by reading the code. + scale = (fp.get("screen") or {}).get("deviceScaleFactor") + if isinstance(scale, (int, float)) and not isinstance(scale, bool) and scale > 0: + kwargs["device_scale_factor"] = float(scale) + intl = fp.get("intl") or {} # The fingerprint's OWN locale. This used to be built as # f"en-{country}", which produced "en-DE" for a German fingerprint — an diff --git a/smoke_test.py b/smoke_test.py index db22703..8763802 100644 --- a/smoke_test.py +++ b/smoke_test.py @@ -1407,7 +1407,8 @@ def wait_for_timeout(self, ms): pass from fingerprint_client import playwright_context_kwargs, playwright_init_script FP = {"id": "fp_test", "country": "us", - "screen": {"width": 1920, "height": 1080}, + "screen": {"width": 1920, "height": 1080, + "deviceScaleFactor": 1}, "userAgent": {"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/145.0.0.0"}, "navigator": {"platform": "Win32", "hardwareConcurrency": 8, "deviceMemory": 8}, "webgl": {"vendor": 'Google Inc. "quoted"', "renderer": "ANGLE (RTX 3060)"}} @@ -1421,6 +1422,13 @@ def wait_for_timeout(self, ms): pass and kw["screen"]["width"] == 1920) js = playwright_init_script(FP) + # The device pixel ratio, which Playwright takes as its own option + # and which was dropped on the floor until a live browser was + # compared against the fingerprint: one stating 1.25 produced a + # browser reporting `devicePixelRatio === 1`, an identity + # contradicting itself on an axis a fingerprinter reads for free. + ok &= check("fingerprint: the device scale factor is carried", + kw.get("device_scale_factor") == 1) ok &= check("fingerprint: values are JSON-encoded, so a quote in an API string " "cannot break out of the script", '\\"quoted\\"' in js and "Google Inc. \"quoted\"" not in js)