Split pure-config into invisible-core; keep back-compat shims

The seed->fingerprint->prefs logic, binary download, and proxy/geo config
now live in a standalone invisible-core package (zero Playwright), so a
profile manager can reuse it without pulling Playwright in. The wrapper
depends on invisible-core and replaces the moved modules with full-alias
shims: existing imports (public API, submodules, private names) and
isinstance checks keep working unchanged. tests/test_backcompat.py locks
that contract (6 guards). Bumps to 0.3.0.
This commit is contained in:
feder-cr 2026-07-03 19:56:24 +02:00
parent d434ee4059
commit 76e41e2789
36 changed files with 136 additions and 11109 deletions

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "invisible-playwright"
version = "0.2.0"
version = "0.3.0"
description = "Playwright wrapper for a patched Firefox with deterministic stealth profile."
readme = "README.md"
requires-python = ">=3.11"
@ -20,13 +20,11 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
dependencies = [
# Pure config (seed -> fingerprint -> prefs, binary download, proxy, geo) lives
# in invisible-core since the 2026-07-03 split; it pulls platformdirs/requests/
# maxminddb/tzdata/tqdm/pywin32 transitively. The wrapper only adds Playwright.
"invisible-core @ git+https://github.com/feder-cr/invisible_core.git",
"playwright>=1.40,<1.61",
"platformdirs>=4",
"requests[socks]>=2.31",
"maxminddb>=2.2",
"tzdata>=2024.1",
"tqdm>=4.66",
"pywin32>=306; sys_platform == 'win32'",
]
[project.optional-dependencies]
@ -57,6 +55,7 @@ Issues = "https://github.com/feder-cr/invisible_playwright/issues"
[tool.hatch.build.targets.wheel]
packages = ["src/invisible_playwright"]
exclude = ["*.bak", "*-bak"]
[tool.hatch.build.targets.sdist]
include = ["src/invisible_playwright", "tests", "README.md", "LICENSE", "pyproject.toml"]

View file

@ -1,26 +1,17 @@
"""Internal Bayesian fingerprint generator used by invisible_playwright.
"""Backward-compat shim — spostato in invisible_core._fpforge (alias completo).
Private module do not import from user code. Use
invisible_playwright.InvisiblePlaywright(seed=..., pin=...) instead.
Aliasa il package E i suoi submodule agli stessi oggetti del core, cosi'
``from invisible_playwright._fpforge.profile import Profile`` e
``from invisible_core._fpforge.profile import Profile`` sono la STESSA classe
(isinstance funziona) e i nomi privati restano accessibili.
"""
from .profile import (
AudioProfile,
CodecProfile,
GPUProfile,
HardwareProfile,
Profile,
ScreenProfile,
WebGLProfile,
generate_profile,
)
import sys as _sys
import invisible_core._fpforge as _pkg
from invisible_core._fpforge import profile as _profile
from invisible_core._fpforge import _sampler as _sampler_mod
from invisible_core._fpforge import _network as _network_mod
__all__ = [
"generate_profile",
"Profile",
"GPUProfile",
"ScreenProfile",
"HardwareProfile",
"AudioProfile",
"CodecProfile",
"WebGLProfile",
]
_sys.modules[__name__] = _pkg
_sys.modules[__name__ + ".profile"] = _profile
_sys.modules[__name__ + "._sampler"] = _sampler_mod
_sys.modules[__name__ + "._network"] = _network_mod

View file

@ -1,144 +0,0 @@
# -*- coding: utf-8 -*-
"""Generic Bayesian network for fingerprint sampling.
A Node has:
- name
- parents (list of parent node names)
- CPT: either
* marginal (no parents): flat [{value, prob}, ...]
* conditional: {parent_tuple: [{value, prob}, ...]}
- OR deterministic: a classifier function `(context) -> value` (no CPT)
Sampling:
- Nodes are topologically sorted
- For each node, look up the conditional distribution given parent values
already sampled in `context`, then weighted-pick
- Deterministic nodes apply their classifier directly
Values can be ANY JSON-serializable type (int, str, dict, list, bool).
Complex values (e.g. screen joint {w, h, dpr}) are stored as dicts in the
CPT and returned as-is in the context.
"""
import json
import random
from typing import Any, Callable, Dict, List, Optional, Tuple
class Node:
"""Single Bayesian node."""
__slots__ = ("name", "parents", "cpt", "classifier", "_marginal")
def __init__(
self,
name: str,
parents: Optional[List[str]] = None,
cpt: Optional[Any] = None,
classifier: Optional[Callable[[Dict[str, Any]], Any]] = None,
):
self.name = name
self.parents = list(parents or [])
self.cpt = cpt
self.classifier = classifier
# Precompute: for no-parent nodes, cpt is the marginal list
self._marginal = cpt if not self.parents and classifier is None else None
def sample(self, context: Dict[str, Any], rng: random.Random) -> Any:
# Deterministic nodes don't sample
if self.classifier is not None:
return self.classifier(context)
if not self.parents:
# Marginal root
return _weighted_pick(self._marginal, rng)
# Conditional node: build the key from parent values
key = _parent_key(self.parents, context)
if key not in self.cpt:
# Fallback: concatenate all parents' tables (uniform over union)
# Keeps sampler from crashing if data doesn't cover some combo.
pool = []
for v in self.cpt.values():
pool.extend(v)
if not pool:
raise ValueError(
f"Node {self.name!r}: no CPT entries for {self.parents}={key}"
)
return _weighted_pick(pool, rng)
return _weighted_pick(self.cpt[key], rng)
class Network:
"""Collection of nodes with topological sampling."""
def __init__(self, nodes: List[Node]):
self.nodes = _topsort(nodes)
self.by_name = {n.name: n for n in self.nodes}
def sample(
self,
rng: random.Random,
evidence: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Sample the network. ``evidence`` fixes named nodes BEFORE their children
sample, so the children RE-CONDITION on the fixed value (not relabel after).
Used to pin ``gpu_class`` to the validated WebGL persona's class so the whole
bundle (cores/screen/fonts) stays coherent with the GPU we expose. Earlier
nodes still sample (RNG stream preserved per-seed determinism)."""
evidence = evidence or {}
context: Dict[str, Any] = {}
for node in self.nodes:
if node.name in evidence:
context[node.name] = evidence[node.name]
else:
context[node.name] = node.sample(context, rng)
return context
# ── Helpers ─────────────────────────────────────────────────────────────
def _weighted_pick(table: List[Dict[str, Any]], rng: random.Random) -> Any:
"""`table` is a list of {value, prob} dicts. Returns one value."""
values = [e["value"] for e in table]
probs = [float(e["prob"]) for e in table]
if not values:
raise ValueError("Empty CPT entry")
total = sum(probs)
if total <= 0:
return rng.choice(values)
# Normalize to be safe (CPTs can be unnormalized)
probs = [p / total for p in probs]
return rng.choices(values, weights=probs, k=1)[0]
def _parent_key(parents: List[str], context: Dict[str, Any]) -> str:
"""Build a JSON-stable key from parent values in declared order."""
if len(parents) == 1:
v = context[parents[0]]
return v if isinstance(v, str) else json.dumps(v, sort_keys=True)
return json.dumps([context[p] for p in parents], sort_keys=True)
def _topsort(nodes: List[Node]) -> List[Node]:
"""Topological sort by parent-before-child."""
by_name = {n.name: n for n in nodes}
visited: set = set()
order: List[Node] = []
def visit(n: Node, path: set):
if n.name in visited:
return
if n.name in path:
raise ValueError(f"Cycle at {n.name}")
path.add(n.name)
for p in n.parents:
if p not in by_name:
raise ValueError(f"Node {n.name} has unknown parent {p}")
visit(by_name[p], path)
path.discard(n.name)
visited.add(n.name)
order.append(n)
for n in nodes:
visit(n, set())
return order

View file

@ -1,445 +0,0 @@
# -*- coding: utf-8 -*-
"""stealth_forge — Bayesian fingerprint generator for Firefox 150 Windows.
Everything the Firefox build exposes to JS (screen, hardwareConcurrency,
WebGL, audio, MSAA, theme, media codecs) is sampled from a Bayesian network
with coherent cross-field dependencies. Identity (userAgent, platform,
oscpu, webdriver=false, maxTouchPoints=0) is locked by the compiled build.
Graph:
gpu (root, 474 real Windows ANGLE renderers)
> gpu_class (deterministic classifier, 6 classes)
> hw_concurrency (CPT per class)
> screen (w/h/dpr/av) (CPT per class)
> msaa_samples (CPT per class)
audio (root, joint rate+latency+channels marginal)
dark_theme (marginal)
av1_enabled (marginal)
webm_encoder_enabled (marginal)
font_exclude deterministic hash of stealth_seed (seed-derived)
CPTs live in `data/*.json` (easy to tune without code changes).
Sampling is deterministic per stealth_seed via a private random.Random.
"""
import json
import os
import re
from typing import Any, Dict, Optional
from ._network import Network, Node
_HERE = os.path.dirname(os.path.abspath(__file__))
def _load(filename: str) -> Any:
with open(os.path.join(_HERE, "data", filename), "r", encoding="utf-8") as f:
return json.load(f)
# ═══════════════════════════════════════════════════════════════════════
# LOCKED IDENTITY (compiled into our Firefox 150 build — never varies)
# ═══════════════════════════════════════════════════════════════════════
_LOCKED: Dict[str, Any] = {
"user_agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) "
"Gecko/20100101 Firefox/150.0.1"
),
"platform": "Win32",
"oscpu": "Windows NT 10.0; Win64; x64",
"app_code_name": "Mozilla",
"app_version": "5.0 (Windows)",
"product_sub": "20100101",
"webdriver": False,
"max_touch_points": 0,
}
# ═══════════════════════════════════════════════════════════════════════
# DATA
# ═══════════════════════════════════════════════════════════════════════
_GPU_POOL = _load("webgl_renderer_pool.json")["entries"]
# hwc/screen/storage now keyed on (gpu_class, intra_tier) for triangulation
_CPT_HWC = _load("cpt_hwc_given_class_tier.json")["table"]
_CPT_SCREEN = _load("cpt_screen_given_class_tier.json")["table"]
_CPT_STORAGE = _load("cpt_storage_given_class_tier.json")["table"]
# Hidden tier variable that makes hwc/screen/storage jointly coherent
_CPT_INTRA_TIER = _load("cpt_intra_tier_given_class.json")["table"]
# MSAA depends on (gpu_class, screen_tier) — 4K gaming → MSAA=0, 1080p+GPU → MSAA=4
_CPT_MSAA = _load("cpt_msaa_given_class_screen.json")["table"]
# Codec unchanged
_CPT_CODEC = _load("cpt_codec_given_class.json")["table"]
# Audio now conditional on gpu_class (workstation → pro audio, old → 44.1kHz onboard)
_CPT_AUDIO = _load("cpt_audio_given_class.json")["table"]
_INDEP = _load("priors_independent.json")
_FONT_POOL = _load("font_pool.json")
# hardwareConcurrency: grounded in the REAL Windows marginal (browserforge Windows UAs).
# cores are OS-level, ~independent of GPU given the OS (browserforge confirms), so this is a
# root marginal — NOT conditioned on gpu_class/intra_tier. Fixes the old CPT over-representing
# 6 cores (~28% vs real ~2%). NB: screen size + dpr are intentionally LEFT on their existing
# nodes (user 2026-06-18: "non modificare dpr e le size degli screen, rompono sempre").
_CORES_MARGINAL = [
{"value": int(e["value"]), "prob": e["prob"]}
for e in _load("win_hw_marginals.json")["cores"]
if 2 <= int(e["value"]) <= 64 and e["prob"] >= 0.004
]
# Each entry is a dict {"name": "<lowercase family>", "factor": float}.
# - name: the font family advertised to the page.
# - factor: per-family width scale used by the consumer to make the family
# detectable by width-diff probes.
# Core = always-included; Optional = sampled with P(font | gpu_class).
_FONT_CORE: list = _FONT_POOL["core"]
_FONT_OPTIONAL: list = _FONT_POOL["optional"]
_CPT_FONTS_OPT = _load("cpt_fonts_optional_given_class.json")["table"] # legacy (per-font sampling, superseded by profiles)
# Realistic Windows font PROFILES (2026-06-18): each = a real machine's optional-font set
# (validated to NOT over-claim on FP Pro). Profile-level variation (machines differ in
# Office/extra fonts) instead of per-font random sampling, which produced unrealistic
# combinations (exotic fonts -> FP Pro over-detection -> tampering_ml tell).
_FONT_PROFILES: list = _FONT_POOL.get("profiles", [])
_OPT_BY_NAME = {e["name"]: e for e in _FONT_OPTIONAL}
# Browsing-history pool + CPT (per-class probabilities for visited sites).
# Drives _recaptcha_seed's cookie pre-seed: each persona ends up with a
# coherent list of ~15-30 visited sites whose categories correlate with
# gpu_class (workstation → dev-heavy, integrated_old → shop+news-heavy).
_BROWSING_POOL: list = _load("browsing_pool.json")["entries"]
_CPT_BROWSING = _load("cpt_browsing_given_class.json")["table"]
# ═══════════════════════════════════════════════════════════════════════
# GPU CLASSIFIER (deterministic function of gpu → gpu_class)
# ═══════════════════════════════════════════════════════════════════════
_GPU_CLASSES = (
"integrated_old", "integrated_modern", "low_end",
"mid_range", "high_end", "workstation",
)
def classify_gpu(gpu_value: Dict[str, str]) -> str:
"""Deterministic: maps (renderer, vendor) dict to one of 6 classes.
See data/cpt_*.json each CPT table has an entry for every class.
"""
r = gpu_value.get("renderer", "")
if re.search(r"Intel.*HD Graphics (3000|4000|2500)", r):
return "integrated_old"
# Discrete Intel Arc DESKTOP/dGPU cards (A-series / B-series, e.g. A750,
# A770, B580) are discrete GPUs (~RTX 3060 tier for A7xx), NOT the
# integrated "Arc 130T/140T/Graphics" iGPUs in Core Ultra chips. Route the
# discrete SKUs to a coherent discrete-GPU class so the conditioned bundle
# (cores, screen, storage) matches a real discrete-GPU machine; A3xx are
# entry discrete -> low_end, A5xx/A7xx/Bxxx -> mid_range. Bare "Arc 1x0(T/V)"
# integrated names do NOT match and fall through to integrated_modern below.
m = re.search(r"Intel.*\bArc(?:\(TM\))?\s+([AB])(\d)\d\d\b", r)
if m:
return "low_end" if m.group(2) == "3" else "mid_range"
if re.search(
r"Intel.*(HD Graphics (4[56]|5\d\d|6\d\d)|UHD Graphics|Graphics Family|Iris|Arc)",
r,
):
return "integrated_modern"
if re.search(
r"AMD.*(Radeon(\(TM\))? (Graphics|6\d\dM|7\d\dM|8\d\dM)|Vega [0-9]|"
r"Renoir|Rembrandt|TM Graphics)",
r, re.IGNORECASE,
):
return "integrated_modern"
# NVIDIA: Firefox SanitizeRenderer.cpp collapses every GeForce into one of
# 3 vintage buckets (8800 GTX / GTX 480 / GTX 980). The renderer string
# exposed to JS is therefore vintage; pairing it with modern cores/screen
# creates an internal mismatch that FP Pro's tampering_ml flags. We pick
# `low_end` for all 3 buckets so cores stay 4-12 and screen 1080-1440p,
# consistent with what a real user with each of those (vintage) cards
# would have. Workstation overrides keep their high-tier classification.
if re.search(
r"(GeForce (8\d\d\d?|9\d\d\d?|GTX 980|GTX 480|GT 1030|GT 710|GT 730|"
r"GT 220|GT 240|210|310)|Quadro K\d|Radeon HD [1234]\d\d\d)", r,
):
return "low_end"
# NVIDIA discrete (any other GeForce — should be rare after the pool was
# collapsed to the 3 sanitize buckets, but kept as a safety net).
m = re.search(r"GeForce\s+(?:GTX\s+|RTX\s+)?(\d{3,4})", r)
if m:
if "Quadro" in r or "Workstation" in r:
return "workstation"
# Anything that survives the sanitize collapse stays low_end to avoid
# the modern-cores/vintage-renderer pairing.
return "low_end"
# AMD discrete
m = re.search(r"Radeon[^0-9]*(\d{3,4})", r)
if m:
n = int(m.group(1))
if "FirePro" in r or "Radeon Pro" in r:
return "workstation"
if n >= 5700:
return "high_end"
if 5500 <= n <= 5600 or 580 <= n <= 590:
return "mid_range"
return "low_end"
# Fallback
return "mid_range"
# ═══════════════════════════════════════════════════════════════════════
# NETWORK CONSTRUCTION
# ═══════════════════════════════════════════════════════════════════════
# Build once at import — the network is stateless, only the RNG varies.
def _gpu_marginal():
"""Build marginal distribution over GPU pool (uniform for now)."""
n = len(_GPU_POOL)
p = 1.0 / n
return [{"value": g, "prob": p} for g in _GPU_POOL]
def _cpt_from_table(table: Dict[str, Any]) -> Dict[str, list]:
"""CPT for conditional nodes: `{class_name: [{value, prob}, ...]}`."""
return dict(table)
def _screen_tier(ctx):
"""Classify screen width into tier for (gpu_class, screen_tier) CPTs."""
s = ctx.get("screen", {}) or {}
w = int(s.get("w", 1920))
h = int(s.get("h", 1080))
# Ultrawide: aspect ratio > 2.1 (e.g. 3440x1440, 5120x1440)
if h > 0 and (w / h) > 2.1:
return "ultrawide"
if w <= 1920:
return "1080p"
if w <= 2560:
return "1440p"
if w <= 3840:
return "2160p"
return "ultrawide"
_NETWORK = Network([
Node("gpu", parents=[], cpt=_gpu_marginal()),
Node("gpu_class", parents=["gpu"], classifier=lambda ctx: classify_gpu(ctx["gpu"])),
# Hidden variable: within a gpu_class, user's OTHER components (RAM, SSD,
# cores, screen) correlate — a 'premium' mid_range user has more cores,
# larger SSD, higher-res screen than a 'budget' mid_range user. Without
# this, hwc/screen/storage would be independent given gpu_class (noisy).
Node("intra_tier", parents=["gpu_class"], cpt=_cpt_from_table(_CPT_INTRA_TIER)),
# hw_concurrency: REAL Windows marginal (root, OS-level, not GPU-conditioned). screen +
# storage stay jointly coherent via (gpu_class, intra_tier) — screen size deliberately
# unchanged (user: dpr + screen sizes break things; leave them).
Node("hw_concurrency", parents=[], cpt=_CORES_MARGINAL),
Node("screen", parents=["gpu_class", "intra_tier"],
cpt=_cpt_from_table(_CPT_SCREEN)),
# Derive screen_tier from screen for msaa parent lookup.
Node("screen_tier", parents=["screen"], classifier=_screen_tier),
# MSAA: realistic combo (4K + high_end GPU → MSAA=0 due to perf cost;
# 1080p + high_end → MSAA=4 common; 1080p + integrated → MSAA=0).
Node("msaa_samples", parents=["gpu_class", "screen_tier"],
cpt=_cpt_from_table(_CPT_MSAA)),
# Joint codec distribution (gpu_class only).
Node("codec", parents=["gpu_class"], cpt=_cpt_from_table(_CPT_CODEC)),
# Storage quota: coherent within gpu_class × intra_tier (premium workstation
# user → 2-3TB SSD; budget workstation user → 512GB; budget integrated_old
# → 128GB).
Node("storage_quota_mb", parents=["gpu_class", "intra_tier"],
cpt=_cpt_from_table(_CPT_STORAGE)),
# Audio: pro users (workstation) → 48/96kHz 6-8ch; old onboard → 44.1kHz
# 2ch high latency. Workstation GPU + 44.1kHz mono was previously
# implausible; now blocked by the CPT.
Node("audio", parents=["gpu_class"], cpt=_cpt_from_table(_CPT_AUDIO)),
Node("dark_theme", parents=[], cpt=_INDEP["dark_theme"]["table"]),
])
# ═══════════════════════════════════════════════════════════════════════
# FONT LIST (Bayesian: core sampled_optional | gpu_class)
# ═══════════════════════════════════════════════════════════════════════
# The browser sees ONLY these families (everything else hidden) and renders
# them from the REAL Windows font files the binary bundles in <GRE>/fonts
# (MOZ_BUNDLED_FONTS). No fabricated widths: per-session metric uniqueness
# comes from the HarfBuzz per-glyph jitter (shared fpp.hw_seed), not here.
# Core (~112): always included — fresh Win11 + Office 2021 English.
# Optional (~40): one realistic Windows profile sampled per seed (weighted,
# deterministic) → ~3-8 optional families differ per session while staying
# centered on 'typical Windows user'.
def derive_font_prefs(gpu_class: str, rng) -> Dict[str, str]:
"""Build the session's font family list.
Profile-based (not per-font random):
- Core families always included (OS defaults + CSS-generic backers).
- Optional families come from ONE realistic Windows profile picked per
seed (weighted, deterministic).
Returns ``{"whitelist": "arial,calibri,marlett,..."}`` the comma-joined
family list to advertise. The binary applies it to the native system font
allow-list AT CONSTRUCTION and renders each family from the bundled real
Windows file, so glyphs and widths are genuine. To add a family, just add
an entry to font_pool.json:core/optional no special-case code needed.
"""
# Profile-based (2026-06-18): pick ONE realistic Windows font profile (weighted,
# deterministic per seed). Per-font random sampling is superseded — it produced
# unrealistic optional combinations (exotic fonts) that FP Pro over-detected
# (detected-set 26 vs real 20 -> tampering_ml ~0.72). Profiles are validated subsets
# of a real machine's set, so the detected-set matches a genuine Windows install.
included: list = list(_FONT_CORE) # core: always present (OS defaults + generic backers)
profile = None
if _FONT_PROFILES:
total = sum(p.get("weight", 1) for p in _FONT_PROFILES)
anchor = rng.random() * total
cum = 0.0
for p in _FONT_PROFILES:
cum += p.get("weight", 1)
if anchor < cum:
profile = p
break
if profile is None:
profile = _FONT_PROFILES[-1]
if profile is not None:
for name in profile.get("optional", []):
entry = _OPT_BY_NAME.get(name)
if entry is not None:
included.append(entry)
else:
included.extend(_FONT_OPTIONAL) # fallback (no profiles defined): all optional
# Dedup by name (a profile may list a font that is also in core, e.g. after a
# standard font is promoted core→always-present) so the list never carries a
# duplicate family.
_seen: set = set()
_uniq: list = []
for e in included:
if e["name"] not in _seen:
_seen.add(e["name"])
_uniq.append(e)
included = _uniq
# Deterministic ordering: sort by name
included.sort(key=lambda e: e["name"])
whitelist = ",".join(e["name"] for e in included)
return {"whitelist": whitelist}
# Back-compat shim: legacy callers still import derive_font_whitelist.
def derive_font_whitelist(gpu_class: str, rng) -> str:
return derive_font_prefs(gpu_class, rng)["whitelist"]
# ═══════════════════════════════════════════════════════════════════════
# BROWSING HISTORY (Bayesian: per-site P(visited|gpu_class))
# ═══════════════════════════════════════════════════════════════════════
def derive_browsing_history(gpu_class: str, rng) -> list:
"""Sample which sites this persona has visited recently.
Each site in the pool has a per-class probability (CPT). We sample
independently per-site, producing a list of dicts:
[{"name": "github.com", "category": "dev", "cookie_profile": "ga_cf"}, ...]
Sum of CPT probabilities per class is tuned to land ~15-30 visited sites
on average an established-user signature. Sorted by name for stable
output across runs of the same seed.
"""
cpt = _CPT_BROWSING.get(gpu_class)
if cpt is None:
cpt = _CPT_BROWSING["mid_range"]
visited: list = []
for entry in _BROWSING_POOL:
name = entry["name"]
p = cpt.get(name, 0.3) # default 0.3 for missing CPT row
if rng.random() < p:
visited.append(dict(entry)) # copy to avoid mutating pool
visited.sort(key=lambda e: e["name"])
return visited
# ═══════════════════════════════════════════════════════════════════════
# PUBLIC API: Forge
# ═══════════════════════════════════════════════════════════════════════
import random
class Forge:
"""Fingerprint forge — single seed → coherent bundle."""
def __init__(self, seed: int):
self.seed = int(seed)
self._rng = random.Random(self.seed)
def sample(self, fixed_gpu_class: Optional[str] = None) -> Dict[str, Any]:
# fixed_gpu_class pins gpu_class so the WHOLE bundle (cores/screen/fonts) is
# drawn coherently for the WebGL persona's class we expose on Windows/mac.
# The default (no fix) path calls _NETWORK.sample(rng) with one arg so existing
# monkeypatches/tests keep working.
if fixed_gpu_class:
bundle = _NETWORK.sample(self._rng, evidence={"gpu_class": fixed_gpu_class})
else:
bundle = _NETWORK.sample(self._rng)
gpu = bundle["gpu"]
screen = bundle["screen"]
audio = bundle["audio"]
codec = bundle["codec"]
return {
# Seed tracking
"stealth_seed": self.seed,
# Locked identity
**_LOCKED,
# GPU (coherent pair from 474 pool)
"webgl_renderer": gpu["renderer"],
"webgl_vendor": gpu["vendor"],
"gpu_class": bundle["gpu_class"],
# Hidden-variable debug metadata (not a Firefox pref, just for
# analysis / test result correlation tracking)
"intra_tier": bundle["intra_tier"],
"screen_tier": bundle["screen_tier"],
# Screen (coherent with GPU class)
"screen_w": int(screen["w"]),
"screen_h": int(screen["h"]),
"screen_avail_w": int(screen.get("aw", screen["w"])),
"screen_avail_h": int(screen.get("ah", screen["h"] - 40)),
"dpr": float(screen["dpr"]),
# Hardware (coherent with GPU class)
"hw_concurrency": int(bundle["hw_concurrency"]),
# WebGL MSAA (coherent with GPU class)
"msaa_samples": int(bundle["msaa_samples"]),
# Audio (independent joint)
"audio_sample_rate": int(audio["rate"]),
"audio_output_latency_ms": int(audio["latency"]),
"audio_max_channel_count": int(audio["channels"]),
# Codec prefs (joint, coherent with GPU class). All 5 are
# JS-visible: av1/webm_encoder via canPlayType/MediaRecorder,
# mediasource_* via MediaSource.isTypeSupported, webspeech_synth
# via 'speechSynthesis' in window (CreepJS voices probe).
"av1_enabled": bool(codec["av1_enabled"]),
"webm_encoder_enabled": bool(codec["webm_encoder_enabled"]),
"mediasource_webm": bool(codec["mediasource_webm"]),
"mediasource_mp4": bool(codec["mediasource_mp4"]),
"webspeech_synth": bool(codec["webspeech_synth"]),
# Storage quota MB (coherent with GPU class — workstation larger SSDs).
"storage_quota_mb": int(bundle["storage_quota_mb"]),
# Independent marginals
"dark_theme": int(bundle["dark_theme"]),
# Bayesian font prefs (coherent pair: whitelist + per-family
# width scale metrics, both sampled from the same font_pool.json
# and conditioned on gpu_class).
**{
f"font_{k}": v
for k, v in derive_font_prefs(
bundle["gpu_class"], self._rng
).items()
},
# Bayesian browsing history (per-class P(visited|gpu_class)).
# Consumed by _recaptcha_seed.py to seed coherent cookie history
# when invisible_playwright is launched with prep_recaptcha=True.
"browsing_history": derive_browsing_history(
bundle["gpu_class"], self._rng
),
}
def sample(seed: int, fixed_gpu_class: Optional[str] = None) -> Dict[str, Any]:
"""Convenience: `Forge(seed).sample(fixed_gpu_class)`."""
return Forge(seed).sample(fixed_gpu_class)

View file

@ -1,64 +0,0 @@
{
"_comment": [
"Pool of everyday websites used by the browsing_history node.",
"Each entry: { name, category, cookie_profile }.",
"- name: bare domain (no scheme, no leading dot).",
"- category: dev / shop / news / reference / media / community / misc.",
"- cookie_profile: short tag pointing to a cookie-template recipe used by",
" _recaptcha_seed.py to generate concrete cookies (so heavy-analytics sites",
" get _ga+_gid+OneTrust, simple sites get just _ga, dev tools get GH-style).",
"Add new entries here + add per-class probabilities in cpt_browsing_given_class.json."
],
"entries": [
{"name": "youtube.com", "category": "media", "cookie_profile": "ga_only"},
{"name": "wikipedia.org", "category": "reference", "cookie_profile": "minimal"},
{"name": "mozilla.org", "category": "reference", "cookie_profile": "ga_consent"},
{"name": "w3schools.com", "category": "dev", "cookie_profile": "ga_consent_clarity"},
{"name": "mdn.io", "category": "dev", "cookie_profile": "minimal"},
{"name": "duckduckgo.com", "category": "reference", "cookie_profile": "minimal"},
{"name": "github.com", "category": "dev", "cookie_profile": "ga_cf"},
{"name": "stackoverflow.com", "category": "dev", "cookie_profile": "ga_consent_clarity"},
{"name": "npmjs.com", "category": "dev", "cookie_profile": "ga_consent"},
{"name": "gitlab.com", "category": "dev", "cookie_profile": "ga_cf"},
{"name": "pypi.org", "category": "dev", "cookie_profile": "minimal"},
{"name": "docs.python.org", "category": "dev", "cookie_profile": "minimal"},
{"name": "rust-lang.org", "category": "dev", "cookie_profile": "ga_consent"},
{"name": "go.dev", "category": "dev", "cookie_profile": "ga_consent"},
{"name": "amazon.com", "category": "shop", "cookie_profile": "ga_consent_clarity"},
{"name": "ebay.com", "category": "shop", "cookie_profile": "ga_consent"},
{"name": "etsy.com", "category": "shop", "cookie_profile": "ga_consent_clarity"},
{"name": "bestbuy.com", "category": "shop", "cookie_profile": "ga_consent_clarity"},
{"name": "target.com", "category": "shop", "cookie_profile": "ga_consent_clarity"},
{"name": "nytimes.com", "category": "news", "cookie_profile": "ga_consent_clarity"},
{"name": "cnn.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "bbc.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "theguardian.com", "category": "news", "cookie_profile": "ga_consent_clarity"},
{"name": "reuters.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "apnews.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "washingtonpost.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "techcrunch.com", "category": "news", "cookie_profile": "ga_consent_clarity"},
{"name": "theverge.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "arstechnica.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "wired.com", "category": "news", "cookie_profile": "ga_consent_clarity"},
{"name": "engadget.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "9to5mac.com", "category": "news", "cookie_profile": "ga_consent"},
{"name": "medium.com", "category": "community", "cookie_profile": "ga_consent"},
{"name": "dev.to", "category": "community", "cookie_profile": "ga_consent"},
{"name": "reddit.com", "category": "community", "cookie_profile": "ga_cf"},
{"name": "news.ycombinator.com", "category": "community", "cookie_profile": "minimal"},
{"name": "quora.com", "category": "community", "cookie_profile": "ga_consent_clarity"},
{"name": "stackexchange.com", "category": "community", "cookie_profile": "ga_consent_clarity"},
{"name": "imdb.com", "category": "media", "cookie_profile": "ga_consent_clarity"},
{"name": "rottentomatoes.com", "category": "media", "cookie_profile": "ga_consent"},
{"name": "metacritic.com", "category": "media", "cookie_profile": "ga_consent"},
{"name": "allrecipes.com", "category": "misc", "cookie_profile": "ga_consent_clarity"},
{"name": "epicurious.com", "category": "misc", "cookie_profile": "ga_consent"},
{"name": "tripadvisor.com", "category": "misc", "cookie_profile": "ga_consent_clarity"},
{"name": "weather.com", "category": "reference", "cookie_profile": "ga_consent"},
{"name": "timeanddate.com", "category": "reference", "cookie_profile": "ga_consent"},
{"name": "thesaurus.com", "category": "reference", "cookie_profile": "ga_consent_clarity"},
{"name": "kayak.com", "category": "shop", "cookie_profile": "ga_consent_clarity"},
{"name": "booking.com", "category": "shop", "cookie_profile": "ga_consent_clarity"},
{"name": "airbnb.com", "category": "shop", "cookie_profile": "ga_consent"}
]
}

View file

@ -1,217 +0,0 @@
{
"_meta": "audio (rate/latency/channels) given gpu_class. NOTE 2026-06-14: maxChannelCount reflects the OS DEFAULT OUTPUT DEVICE (stereo for the vast majority of users), NOT the GPU — so channels=2 dominates every class (~78-92%) with only a small 6/8 surround tail. The previous tables emitted 45-100% surround on mid/high/workstation, which is unrealistic and lifted FP Pro tampering_ml (surround on a typical consumer profile reads as a coherence anomaly). Rate/latency tuples are unchanged.",
"table": {
"integrated_old": [
{
"value": {
"rate": 44100,
"latency": 50,
"channels": 2
},
"prob": 0.7
},
{
"value": {
"rate": 48000,
"latency": 50,
"channels": 2
},
"prob": 0.3
}
],
"integrated_modern": [
{
"value": {
"rate": 48000,
"latency": 30,
"channels": 2
},
"prob": 0.62
},
{
"value": {
"rate": 44100,
"latency": 40,
"channels": 2
},
"prob": 0.3
},
{
"value": {
"rate": 48000,
"latency": 25,
"channels": 6
},
"prob": 0.08
}
],
"low_end": [
{
"value": {
"rate": 48000,
"latency": 40,
"channels": 2
},
"prob": 0.6
},
{
"value": {
"rate": 44100,
"latency": 50,
"channels": 2
},
"prob": 0.32
},
{
"value": {
"rate": 48000,
"latency": 30,
"channels": 6
},
"prob": 0.08
}
],
"mid_range": [
{
"value": {
"rate": 48000,
"latency": 25,
"channels": 2
},
"prob": 0.5
},
{
"value": {
"rate": 48000,
"latency": 20,
"channels": 2
},
"prob": 0.3
},
{
"value": {
"rate": 44100,
"latency": 30,
"channels": 2
},
"prob": 0.12
},
{
"value": {
"rate": 48000,
"latency": 20,
"channels": 6
},
"prob": 0.06
},
{
"value": {
"rate": 48000,
"latency": 20,
"channels": 8
},
"prob": 0.02
}
],
"high_end": [
{
"value": {
"rate": 48000,
"latency": 15,
"channels": 2
},
"prob": 0.6
},
{
"value": {
"rate": 96000,
"latency": 15,
"channels": 2
},
"prob": 0.18
},
{
"value": {
"rate": 48000,
"latency": 15,
"channels": 6
},
"prob": 0.1
},
{
"value": {
"rate": 48000,
"latency": 15,
"channels": 8
},
"prob": 0.05
},
{
"value": {
"rate": 96000,
"latency": 15,
"channels": 6
},
"prob": 0.05
},
{
"value": {
"rate": 96000,
"latency": 15,
"channels": 8
},
"prob": 0.02
}
],
"workstation": [
{
"value": {
"rate": 48000,
"latency": 10,
"channels": 2
},
"prob": 0.45
},
{
"value": {
"rate": 96000,
"latency": 10,
"channels": 2
},
"prob": 0.2
},
{
"value": {
"rate": 48000,
"latency": 10,
"channels": 8
},
"prob": 0.12
},
{
"value": {
"rate": 96000,
"latency": 10,
"channels": 8
},
"prob": 0.1
},
{
"value": {
"rate": 96000,
"latency": 10,
"channels": 6
},
"prob": 0.08
},
{
"value": {
"rate": 192000,
"latency": 10,
"channels": 8
},
"prob": 0.05
}
]
}
}

View file

@ -1,138 +0,0 @@
{
"_comment": [
"Per-class probability that a persona of a given gpu_class has visited each",
"site in the pool. Used by the browsing_history node to derive a coherent",
"visited-domain list per persona.",
"",
"Probabilities are tuned so each class samples ~15-30 sites on average",
"(sum across all 50 entries falls in that range), giving an established-user",
"look. Categories are biased by class:",
" - workstation/high_end: higher P(dev) + high P(news/media)",
" - mid_range: balanced",
" - low_end/integrated_*: lower P(dev), higher P(shop/news/reference)",
"",
"Missing class falls back to mid_range via Node CPT pool fallback."
],
"table": {
"workstation": {
"youtube.com": 0.80, "wikipedia.org": 0.85, "mozilla.org": 0.70,
"w3schools.com": 0.40, "mdn.io": 0.55, "duckduckgo.com": 0.45,
"github.com": 0.95, "stackoverflow.com": 0.90, "npmjs.com": 0.65,
"gitlab.com": 0.50, "pypi.org": 0.55, "docs.python.org": 0.60,
"rust-lang.org": 0.35, "go.dev": 0.30,
"amazon.com": 0.70, "ebay.com": 0.25, "etsy.com": 0.15,
"bestbuy.com": 0.45, "target.com": 0.30,
"nytimes.com": 0.55, "cnn.com": 0.40, "bbc.com": 0.55,
"theguardian.com": 0.45, "reuters.com": 0.40, "apnews.com": 0.30,
"washingtonpost.com": 0.40,
"techcrunch.com": 0.65, "theverge.com": 0.60, "arstechnica.com": 0.65,
"wired.com": 0.50, "engadget.com": 0.35, "9to5mac.com": 0.30,
"medium.com": 0.55, "dev.to": 0.40, "reddit.com": 0.70,
"news.ycombinator.com": 0.65, "quora.com": 0.20, "stackexchange.com": 0.60,
"imdb.com": 0.45, "rottentomatoes.com": 0.25, "metacritic.com": 0.20,
"allrecipes.com": 0.20, "epicurious.com": 0.15, "tripadvisor.com": 0.30,
"weather.com": 0.55, "timeanddate.com": 0.30, "thesaurus.com": 0.25,
"kayak.com": 0.30, "booking.com": 0.35, "airbnb.com": 0.30
},
"high_end": {
"youtube.com": 0.85, "wikipedia.org": 0.80, "mozilla.org": 0.60,
"w3schools.com": 0.45, "mdn.io": 0.45, "duckduckgo.com": 0.40,
"github.com": 0.85, "stackoverflow.com": 0.80, "npmjs.com": 0.50,
"gitlab.com": 0.40, "pypi.org": 0.45, "docs.python.org": 0.50,
"rust-lang.org": 0.30, "go.dev": 0.25,
"amazon.com": 0.75, "ebay.com": 0.30, "etsy.com": 0.20,
"bestbuy.com": 0.50, "target.com": 0.35,
"nytimes.com": 0.50, "cnn.com": 0.50, "bbc.com": 0.50,
"theguardian.com": 0.40, "reuters.com": 0.35, "apnews.com": 0.30,
"washingtonpost.com": 0.35,
"techcrunch.com": 0.60, "theverge.com": 0.65, "arstechnica.com": 0.60,
"wired.com": 0.50, "engadget.com": 0.40, "9to5mac.com": 0.35,
"medium.com": 0.50, "dev.to": 0.35, "reddit.com": 0.75,
"news.ycombinator.com": 0.55, "quora.com": 0.25, "stackexchange.com": 0.55,
"imdb.com": 0.55, "rottentomatoes.com": 0.35, "metacritic.com": 0.30,
"allrecipes.com": 0.25, "epicurious.com": 0.20, "tripadvisor.com": 0.30,
"weather.com": 0.55, "timeanddate.com": 0.30, "thesaurus.com": 0.25,
"kayak.com": 0.30, "booking.com": 0.40, "airbnb.com": 0.30
},
"mid_range": {
"youtube.com": 0.85, "wikipedia.org": 0.75, "mozilla.org": 0.45,
"w3schools.com": 0.40, "mdn.io": 0.30, "duckduckgo.com": 0.35,
"github.com": 0.55, "stackoverflow.com": 0.55, "npmjs.com": 0.30,
"gitlab.com": 0.25, "pypi.org": 0.25, "docs.python.org": 0.30,
"rust-lang.org": 0.15, "go.dev": 0.15,
"amazon.com": 0.80, "ebay.com": 0.40, "etsy.com": 0.30,
"bestbuy.com": 0.55, "target.com": 0.40,
"nytimes.com": 0.45, "cnn.com": 0.55, "bbc.com": 0.45,
"theguardian.com": 0.35, "reuters.com": 0.30, "apnews.com": 0.30,
"washingtonpost.com": 0.30,
"techcrunch.com": 0.45, "theverge.com": 0.50, "arstechnica.com": 0.40,
"wired.com": 0.45, "engadget.com": 0.35, "9to5mac.com": 0.30,
"medium.com": 0.45, "dev.to": 0.25, "reddit.com": 0.70,
"news.ycombinator.com": 0.30, "quora.com": 0.35, "stackexchange.com": 0.40,
"imdb.com": 0.60, "rottentomatoes.com": 0.40, "metacritic.com": 0.35,
"allrecipes.com": 0.35, "epicurious.com": 0.25, "tripadvisor.com": 0.40,
"weather.com": 0.60, "timeanddate.com": 0.25, "thesaurus.com": 0.30,
"kayak.com": 0.35, "booking.com": 0.45, "airbnb.com": 0.40
},
"low_end": {
"youtube.com": 0.85, "wikipedia.org": 0.70, "mozilla.org": 0.35,
"w3schools.com": 0.30, "mdn.io": 0.20, "duckduckgo.com": 0.30,
"github.com": 0.30, "stackoverflow.com": 0.30, "npmjs.com": 0.15,
"gitlab.com": 0.10, "pypi.org": 0.10, "docs.python.org": 0.15,
"rust-lang.org": 0.05, "go.dev": 0.05,
"amazon.com": 0.85, "ebay.com": 0.50, "etsy.com": 0.40,
"bestbuy.com": 0.55, "target.com": 0.45,
"nytimes.com": 0.40, "cnn.com": 0.60, "bbc.com": 0.40,
"theguardian.com": 0.30, "reuters.com": 0.25, "apnews.com": 0.30,
"washingtonpost.com": 0.25,
"techcrunch.com": 0.30, "theverge.com": 0.35, "arstechnica.com": 0.25,
"wired.com": 0.40, "engadget.com": 0.30, "9to5mac.com": 0.25,
"medium.com": 0.35, "dev.to": 0.15, "reddit.com": 0.65,
"news.ycombinator.com": 0.15, "quora.com": 0.45, "stackexchange.com": 0.25,
"imdb.com": 0.65, "rottentomatoes.com": 0.45, "metacritic.com": 0.35,
"allrecipes.com": 0.45, "epicurious.com": 0.30, "tripadvisor.com": 0.45,
"weather.com": 0.65, "timeanddate.com": 0.25, "thesaurus.com": 0.35,
"kayak.com": 0.35, "booking.com": 0.50, "airbnb.com": 0.40
},
"integrated_modern": {
"youtube.com": 0.85, "wikipedia.org": 0.70, "mozilla.org": 0.40,
"w3schools.com": 0.35, "mdn.io": 0.25, "duckduckgo.com": 0.35,
"github.com": 0.40, "stackoverflow.com": 0.40, "npmjs.com": 0.20,
"gitlab.com": 0.15, "pypi.org": 0.20, "docs.python.org": 0.20,
"rust-lang.org": 0.10, "go.dev": 0.10,
"amazon.com": 0.80, "ebay.com": 0.40, "etsy.com": 0.30,
"bestbuy.com": 0.50, "target.com": 0.40,
"nytimes.com": 0.40, "cnn.com": 0.55, "bbc.com": 0.45,
"theguardian.com": 0.35, "reuters.com": 0.30, "apnews.com": 0.30,
"washingtonpost.com": 0.30,
"techcrunch.com": 0.40, "theverge.com": 0.45, "arstechnica.com": 0.30,
"wired.com": 0.40, "engadget.com": 0.30, "9to5mac.com": 0.25,
"medium.com": 0.40, "dev.to": 0.20, "reddit.com": 0.65,
"news.ycombinator.com": 0.25, "quora.com": 0.40, "stackexchange.com": 0.35,
"imdb.com": 0.60, "rottentomatoes.com": 0.40, "metacritic.com": 0.30,
"allrecipes.com": 0.40, "epicurious.com": 0.25, "tripadvisor.com": 0.40,
"weather.com": 0.60, "timeanddate.com": 0.25, "thesaurus.com": 0.30,
"kayak.com": 0.35, "booking.com": 0.45, "airbnb.com": 0.40
},
"integrated_old": {
"youtube.com": 0.75, "wikipedia.org": 0.65, "mozilla.org": 0.30,
"w3schools.com": 0.20, "mdn.io": 0.10, "duckduckgo.com": 0.25,
"github.com": 0.15, "stackoverflow.com": 0.20, "npmjs.com": 0.05,
"gitlab.com": 0.05, "pypi.org": 0.05, "docs.python.org": 0.10,
"rust-lang.org": 0.02, "go.dev": 0.02,
"amazon.com": 0.85, "ebay.com": 0.55, "etsy.com": 0.45,
"bestbuy.com": 0.55, "target.com": 0.50,
"nytimes.com": 0.45, "cnn.com": 0.65, "bbc.com": 0.40,
"theguardian.com": 0.30, "reuters.com": 0.25, "apnews.com": 0.35,
"washingtonpost.com": 0.30,
"techcrunch.com": 0.20, "theverge.com": 0.25, "arstechnica.com": 0.15,
"wired.com": 0.30, "engadget.com": 0.20, "9to5mac.com": 0.20,
"medium.com": 0.30, "dev.to": 0.05, "reddit.com": 0.55,
"news.ycombinator.com": 0.05, "quora.com": 0.55, "stackexchange.com": 0.15,
"imdb.com": 0.70, "rottentomatoes.com": 0.50, "metacritic.com": 0.35,
"allrecipes.com": 0.55, "epicurious.com": 0.35, "tripadvisor.com": 0.50,
"weather.com": 0.70, "timeanddate.com": 0.30, "thesaurus.com": 0.40,
"kayak.com": 0.40, "booking.com": 0.55, "airbnb.com": 0.40
}
}
}

View file

@ -1,147 +0,0 @@
{
"_meta": "codec given gpu_class",
"table": {
"integrated_old": [
{
"value": {
"av1_enabled": false,
"webm_encoder_enabled": false,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 1.0
}
],
"integrated_modern": [
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.55
},
{
"value": {
"av1_enabled": false,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.35
},
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": false
},
"prob": 0.1
}
],
"low_end": [
{
"value": {
"av1_enabled": false,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.85
},
{
"value": {
"av1_enabled": false,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": false
},
"prob": 0.15
}
],
"mid_range": [
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.55
},
{
"value": {
"av1_enabled": false,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.35
},
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": false
},
"prob": 0.1
}
],
"high_end": [
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.85
},
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": false
},
"prob": 0.15
}
],
"workstation": [
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": true
},
"prob": 0.7
},
{
"value": {
"av1_enabled": true,
"webm_encoder_enabled": true,
"mediasource_webm": true,
"mediasource_mp4": true,
"webspeech_synth": false
},
"prob": 0.3
}
]
}
}

View file

@ -1,295 +0,0 @@
{
"_meta": {
"name": "optional_font presence | gpu_class",
"parents": [
"gpu_class"
],
"child": "optional_fonts_subset",
"description": "Per-optional-font presence probabilities given gpu_class. Each optional font in font_pool.json sampled INDEPENDENTLY with P(present | gpu_class) given here. Integrated_old: fewer language packs (older/cheaper machines). Workstation: more regional/language packs (international users, enterprise deployments).",
"rationale": "Near-invariant by design: most optional fonts have baseline P ~ 0.60-0.85 across classes. Per-session variance is small (~3-6 fonts toggling on/off out of 40 optional). Result: fingerprint always looks like 'typical Windows 11 + Office', with small realistic per-user variance in regional support."
},
"table": {
"integrated_old": {
"aparajita": 0.45,
"calibri": 0.9,
"dengxian": 0.3,
"dfkai-sb": 0.25,
"dokchampa": 0.2,
"estrangelo edessa": 0.55,
"euphemia": 0.6,
"fangsong": 0.35,
"gadugi": 0.88,
"gautami": 0.55,
"helv": 0.7,
"iskoola pota": 0.5,
"javanese text": 0.85,
"kaiti": 0.3,
"kalinga": 0.5,
"kartika": 0.5,
"khmer ui": 0.35,
"kokila": 0.55,
"lao ui": 0.35,
"latha": 0.65,
"leelawadee ui": 0.87,
"mangal": 0.65,
"meiryo": 0.6,
"microsoft uighur": 0.4,
"ms pmincho": 0.65,
"ms reference sans serif": 0.55,
"ms reference specialty": 0.5,
"ms ui gothic": 0.82,
"myanmar text": 0.84,
"nyala": 0.55,
"plantagenet cherokee": 0.6,
"raavi": 0.55,
"segoe fluent icons": 0.5,
"segoe ui light": 0.75,
"shonar bangla": 0.55,
"shruti": 0.55,
"simkai": 0.25,
"small fonts": 0.75,
"traditional arabic": 0.55,
"tunga": 0.55,
"urdu typesetting": 0.45,
"utsaah": 0.55,
"vani": 0.55,
"vijaya": 0.55,
"yu mincho": 0.55
},
"integrated_modern": {
"aparajita": 0.65,
"calibri": 0.78,
"dengxian": 0.5,
"dfkai-sb": 0.4,
"dokchampa": 0.4,
"estrangelo edessa": 0.7,
"euphemia": 0.78,
"fangsong": 0.55,
"gadugi": 0.96,
"gautami": 0.75,
"helv": 0.6,
"iskoola pota": 0.7,
"javanese text": 0.94,
"kaiti": 0.45,
"kalinga": 0.7,
"kartika": 0.7,
"khmer ui": 0.55,
"kokila": 0.75,
"lao ui": 0.55,
"latha": 0.82,
"leelawadee ui": 0.95,
"mangal": 0.82,
"meiryo": 0.8,
"microsoft uighur": 0.55,
"ms pmincho": 0.85,
"ms reference sans serif": 0.72,
"ms reference specialty": 0.68,
"ms ui gothic": 0.75,
"myanmar text": 0.94,
"nyala": 0.72,
"plantagenet cherokee": 0.78,
"raavi": 0.72,
"segoe fluent icons": 0.92,
"segoe ui light": 0.72,
"shonar bangla": 0.72,
"shruti": 0.72,
"simkai": 0.4,
"small fonts": 0.65,
"traditional arabic": 0.72,
"tunga": 0.72,
"urdu typesetting": 0.65,
"utsaah": 0.72,
"vani": 0.72,
"vijaya": 0.72,
"yu mincho": 0.75
},
"low_end": {
"aparajita": 0.55,
"calibri": 0.94,
"dengxian": 0.4,
"dfkai-sb": 0.3,
"dokchampa": 0.3,
"estrangelo edessa": 0.62,
"euphemia": 0.68,
"fangsong": 0.45,
"gadugi": 0.93,
"gautami": 0.65,
"helv": 0.78,
"iskoola pota": 0.6,
"javanese text": 0.91,
"kaiti": 0.35,
"kalinga": 0.6,
"kartika": 0.6,
"khmer ui": 0.45,
"kokila": 0.65,
"lao ui": 0.45,
"latha": 0.72,
"leelawadee ui": 0.92,
"mangal": 0.72,
"meiryo": 0.7,
"microsoft uighur": 0.48,
"ms pmincho": 0.75,
"ms reference sans serif": 0.62,
"ms reference specialty": 0.58,
"ms ui gothic": 0.9,
"myanmar text": 0.9,
"nyala": 0.62,
"plantagenet cherokee": 0.68,
"raavi": 0.62,
"segoe fluent icons": 0.8,
"segoe ui light": 0.88,
"shonar bangla": 0.62,
"shruti": 0.62,
"simkai": 0.3,
"small fonts": 0.83,
"traditional arabic": 0.62,
"tunga": 0.62,
"urdu typesetting": 0.55,
"utsaah": 0.62,
"vani": 0.62,
"vijaya": 0.62,
"yu mincho": 0.65
},
"mid_range": {
"aparajita": 0.72,
"calibri": 0.98,
"dengxian": 0.6,
"dfkai-sb": 0.5,
"dokchampa": 0.5,
"estrangelo edessa": 0.78,
"euphemia": 0.82,
"fangsong": 0.65,
"gadugi": 0.97,
"gautami": 0.8,
"helv": 0.85,
"iskoola pota": 0.78,
"javanese text": 0.96,
"kaiti": 0.55,
"kalinga": 0.78,
"kartika": 0.78,
"khmer ui": 0.65,
"kokila": 0.8,
"lao ui": 0.65,
"latha": 0.85,
"leelawadee ui": 0.97,
"mangal": 0.85,
"meiryo": 0.85,
"microsoft uighur": 0.65,
"ms pmincho": 0.88,
"ms reference sans serif": 0.78,
"ms reference specialty": 0.75,
"ms ui gothic": 0.96,
"myanmar text": 0.96,
"nyala": 0.78,
"plantagenet cherokee": 0.82,
"raavi": 0.78,
"segoe fluent icons": 0.94,
"segoe ui light": 0.94,
"shonar bangla": 0.78,
"shruti": 0.78,
"simkai": 0.5,
"small fonts": 0.9,
"traditional arabic": 0.78,
"tunga": 0.78,
"urdu typesetting": 0.72,
"utsaah": 0.78,
"vani": 0.78,
"vijaya": 0.78,
"yu mincho": 0.8
},
"high_end": {
"aparajita": 0.8,
"calibri": 0.99,
"dengxian": 0.7,
"dfkai-sb": 0.6,
"dokchampa": 0.6,
"estrangelo edessa": 0.85,
"euphemia": 0.88,
"fangsong": 0.72,
"gadugi": 0.98,
"gautami": 0.85,
"helv": 0.88,
"iskoola pota": 0.82,
"javanese text": 0.97,
"kaiti": 0.65,
"kalinga": 0.82,
"kartika": 0.82,
"khmer ui": 0.72,
"kokila": 0.85,
"lao ui": 0.72,
"latha": 0.9,
"leelawadee ui": 0.98,
"mangal": 0.9,
"meiryo": 0.9,
"microsoft uighur": 0.72,
"ms pmincho": 0.92,
"ms reference sans serif": 0.85,
"ms reference specialty": 0.82,
"ms ui gothic": 0.98,
"myanmar text": 0.97,
"nyala": 0.85,
"plantagenet cherokee": 0.88,
"raavi": 0.85,
"segoe fluent icons": 0.97,
"segoe ui light": 0.96,
"shonar bangla": 0.85,
"shruti": 0.85,
"simkai": 0.6,
"small fonts": 0.92,
"traditional arabic": 0.85,
"tunga": 0.85,
"urdu typesetting": 0.8,
"utsaah": 0.85,
"vani": 0.85,
"vijaya": 0.85,
"yu mincho": 0.88
},
"workstation": {
"aparajita": 0.88,
"calibri": 0.99,
"dengxian": 0.8,
"dfkai-sb": 0.75,
"dokchampa": 0.72,
"estrangelo edessa": 0.9,
"euphemia": 0.92,
"fangsong": 0.82,
"gadugi": 0.99,
"gautami": 0.92,
"helv": 0.9,
"iskoola pota": 0.9,
"javanese text": 0.98,
"kaiti": 0.78,
"kalinga": 0.9,
"kartika": 0.9,
"khmer ui": 0.8,
"kokila": 0.92,
"lao ui": 0.8,
"latha": 0.95,
"leelawadee ui": 0.99,
"mangal": 0.95,
"meiryo": 0.95,
"microsoft uighur": 0.82,
"ms pmincho": 0.95,
"ms reference sans serif": 0.92,
"ms reference specialty": 0.9,
"ms ui gothic": 0.98,
"myanmar text": 0.98,
"nyala": 0.92,
"plantagenet cherokee": 0.92,
"raavi": 0.92,
"segoe fluent icons": 0.98,
"segoe ui light": 0.97,
"shonar bangla": 0.92,
"shruti": 0.92,
"simkai": 0.72,
"small fonts": 0.94,
"traditional arabic": 0.92,
"tunga": 0.92,
"urdu typesetting": 0.88,
"utsaah": 0.92,
"vani": 0.92,
"vijaya": 0.92,
"yu mincho": 0.92
}
}
}

View file

@ -1,50 +0,0 @@
{
"_meta": {
"name": "hw_concurrency | gpu_class",
"parents": ["gpu_class"],
"child": "hw_concurrency",
"source": "Curated from realistic CPU/GPU pairings (Steam HW Survey + DIY build norms + laptop SKU patterns)"
},
"table": {
"integrated_old": [
{"value": 2, "prob": 0.20},
{"value": 4, "prob": 0.60},
{"value": 8, "prob": 0.20}
],
"integrated_modern": [
{"value": 4, "prob": 0.20},
{"value": 6, "prob": 0.15},
{"value": 8, "prob": 0.35},
{"value": 12, "prob": 0.20},
{"value": 16, "prob": 0.10}
],
"low_end": [
{"value": 4, "prob": 0.25},
{"value": 6, "prob": 0.25},
{"value": 8, "prob": 0.35},
{"value": 12, "prob": 0.15}
],
"mid_range": [
{"value": 6, "prob": 0.15},
{"value": 8, "prob": 0.30},
{"value": 12, "prob": 0.30},
{"value": 16, "prob": 0.20},
{"value": 24, "prob": 0.05}
],
"high_end": [
{"value": 8, "prob": 0.10},
{"value": 12, "prob": 0.25},
{"value": 16, "prob": 0.35},
{"value": 20, "prob": 0.05},
{"value": 24, "prob": 0.20},
{"value": 32, "prob": 0.05}
],
"workstation": [
{"value": 8, "prob": 0.15},
{"value": 12, "prob": 0.20},
{"value": 16, "prob": 0.30},
{"value": 24, "prob": 0.20},
{"value": 32, "prob": 0.15}
]
}
}

View file

@ -1,337 +0,0 @@
{
"_meta": "hardware_concurrency given (gpu_class, intra_tier)",
"table": {
"[\"integrated_old\", \"budget\"]": [
{
"value": 2,
"prob": 0.65
},
{
"value": 4,
"prob": 0.3
},
{
"value": 8,
"prob": 0.05
}
],
"[\"integrated_old\", \"standard\"]": [
{
"value": 2,
"prob": 0.3
},
{
"value": 4,
"prob": 0.55
},
{
"value": 8,
"prob": 0.15
}
],
"[\"integrated_old\", \"premium\"]": [
{
"value": 4,
"prob": 0.65
},
{
"value": 8,
"prob": 0.35
}
],
"[\"integrated_modern\", \"budget\"]": [
{
"value": 6,
"prob": 0.45
},
{
"value": 8,
"prob": 0.4
},
{
"value": 12,
"prob": 0.15
}
],
"[\"integrated_modern\", \"standard\"]": [
{
"value": 6,
"prob": 0.2
},
{
"value": 8,
"prob": 0.3
},
{
"value": 10,
"prob": 0.2
},
{
"value": 12,
"prob": 0.2
},
{
"value": 16,
"prob": 0.1
}
],
"[\"integrated_modern\", \"premium\"]": [
{
"value": 8,
"prob": 0.2
},
{
"value": 10,
"prob": 0.2
},
{
"value": 12,
"prob": 0.3
},
{
"value": 14,
"prob": 0.15
},
{
"value": 16,
"prob": 0.15
}
],
"[\"low_end\", \"budget\"]": [
{
"value": 4,
"prob": 0.5
},
{
"value": 6,
"prob": 0.25
},
{
"value": 8,
"prob": 0.2
},
{
"value": 12,
"prob": 0.05
}
],
"[\"low_end\", \"standard\"]": [
{
"value": 4,
"prob": 0.1
},
{
"value": 6,
"prob": 0.35
},
{
"value": 8,
"prob": 0.3
},
{
"value": 12,
"prob": 0.18
},
{
"value": 16,
"prob": 0.07
}
],
"[\"low_end\", \"premium\"]": [
{
"value": 6,
"prob": 0.1
},
{
"value": 8,
"prob": 0.3
},
{
"value": 12,
"prob": 0.3
},
{
"value": 16,
"prob": 0.22
},
{
"value": 24,
"prob": 0.08
}
],
"[\"mid_range\", \"budget\"]": [
{
"value": 6,
"prob": 0.55
},
{
"value": 8,
"prob": 0.3
},
{
"value": 12,
"prob": 0.15
}
],
"[\"mid_range\", \"standard\"]": [
{
"value": 6,
"prob": 0.4
},
{
"value": 8,
"prob": 0.3
},
{
"value": 12,
"prob": 0.18
},
{
"value": 16,
"prob": 0.1
},
{
"value": 24,
"prob": 0.02
}
],
"[\"mid_range\", \"premium\"]": [
{
"value": 6,
"prob": 0.15
},
{
"value": 8,
"prob": 0.45
},
{
"value": 12,
"prob": 0.2
},
{
"value": 16,
"prob": 0.15
},
{
"value": 24,
"prob": 0.05
}
],
"[\"high_end\", \"budget\"]": [
{
"value": 6,
"prob": 0.1
},
{
"value": 8,
"prob": 0.55
},
{
"value": 12,
"prob": 0.2
},
{
"value": 16,
"prob": 0.15
}
],
"[\"high_end\", \"standard\"]": [
{
"value": 8,
"prob": 0.3
},
{
"value": 12,
"prob": 0.18
},
{
"value": 14,
"prob": 0.15
},
{
"value": 16,
"prob": 0.27
},
{
"value": 24,
"prob": 0.1
}
],
"[\"high_end\", \"premium\"]": [
{
"value": 12,
"prob": 0.1
},
{
"value": 14,
"prob": 0.15
},
{
"value": 16,
"prob": 0.3
},
{
"value": 24,
"prob": 0.35
},
{
"value": 32,
"prob": 0.1
}
],
"[\"workstation\", \"budget\"]": [
{
"value": 8,
"prob": 0.4
},
{
"value": 12,
"prob": 0.3
},
{
"value": 16,
"prob": 0.2
},
{
"value": 24,
"prob": 0.1
}
],
"[\"workstation\", \"standard\"]": [
{
"value": 12,
"prob": 0.1
},
{
"value": 16,
"prob": 0.4
},
{
"value": 24,
"prob": 0.3
},
{
"value": 32,
"prob": 0.2
}
],
"[\"workstation\", \"premium\"]": [
{
"value": 24,
"prob": 0.3
},
{
"value": 32,
"prob": 0.45
},
{
"value": 48,
"prob": 0.15
},
{
"value": 64,
"prob": 0.1
}
]
}
}

View file

@ -1,17 +0,0 @@
{
"_meta": {
"name": "intra_tier | gpu_class",
"parents": ["gpu_class"],
"child": "intra_tier",
"value_type": "str (budget|standard|premium)",
"rationale": "Hidden variable catching 'intra-class premium-ness'. Within a gpu_class, users vary in how premium their OTHER components are (RAM, SSD, cores). A mid_range GPU paired with 32 cores + 2TB SSD is a 'premium' mid_range user. Without this var, hwc/screen/storage would be statistically independent given gpu_class — unrealistic (real users pick coherent bundles). Tier distribution biased: integrated_old mostly budget/standard, workstation mostly premium."
},
"table": {
"integrated_old": [{"value": "budget", "prob": 0.65}, {"value": "standard", "prob": 0.30}, {"value": "premium", "prob": 0.05}],
"integrated_modern": [{"value": "budget", "prob": 0.30}, {"value": "standard", "prob": 0.55}, {"value": "premium", "prob": 0.15}],
"low_end": [{"value": "budget", "prob": 0.50}, {"value": "standard", "prob": 0.40}, {"value": "premium", "prob": 0.10}],
"mid_range": [{"value": "budget", "prob": 0.25}, {"value": "standard", "prob": 0.50}, {"value": "premium", "prob": 0.25}],
"high_end": [{"value": "budget", "prob": 0.15}, {"value": "standard", "prob": 0.50}, {"value": "premium", "prob": 0.35}],
"workstation": [{"value": "budget", "prob": 0.10}, {"value": "standard", "prob": 0.40}, {"value": "premium", "prob": 0.50}]
}
}

View file

@ -1,16 +0,0 @@
{
"_meta": {
"name": "webgl.msaa-samples | gpu_class",
"parents": ["gpu_class"],
"child": "msaa_samples",
"source": "High-end GPUs more likely to have MSAA enabled in games; integrated usually off"
},
"table": {
"integrated_old": [{"value": 0, "prob": 0.85}, {"value": 2, "prob": 0.15}],
"integrated_modern": [{"value": 0, "prob": 0.70}, {"value": 2, "prob": 0.25}, {"value": 4, "prob": 0.05}],
"low_end": [{"value": 0, "prob": 0.55}, {"value": 2, "prob": 0.35}, {"value": 4, "prob": 0.10}],
"mid_range": [{"value": 0, "prob": 0.35}, {"value": 2, "prob": 0.35}, {"value": 4, "prob": 0.30}],
"high_end": [{"value": 0, "prob": 0.20}, {"value": 2, "prob": 0.25}, {"value": 4, "prob": 0.55}],
"workstation": [{"value": 0, "prob": 0.50}, {"value": 2, "prob": 0.25}, {"value": 4, "prob": 0.25}]
}
}

View file

@ -1,305 +0,0 @@
{
"_meta": "msaa_samples given (gpu_class, screen_tier)",
"table": {
"[\"integrated_old\", \"1080p\"]": [
{
"value": 2,
"prob": 0.75
},
{
"value": 4,
"prob": 0.2
},
{
"value": 8,
"prob": 0.05
}
],
"[\"integrated_old\", \"1440p\"]": [
{
"value": 2,
"prob": 0.85
},
{
"value": 4,
"prob": 0.15
}
],
"[\"integrated_old\", \"2160p\"]": [
{
"value": 2,
"prob": 1.0
}
],
"[\"integrated_old\", \"ultrawide\"]": [
{
"value": 2,
"prob": 1.0
}
],
"[\"integrated_modern\", \"1080p\"]": [
{
"value": 2,
"prob": 0.5
},
{
"value": 4,
"prob": 0.4
},
{
"value": 8,
"prob": 0.1
}
],
"[\"integrated_modern\", \"1440p\"]": [
{
"value": 2,
"prob": 0.6
},
{
"value": 4,
"prob": 0.35
},
{
"value": 8,
"prob": 0.05
}
],
"[\"integrated_modern\", \"2160p\"]": [
{
"value": 2,
"prob": 0.85
},
{
"value": 4,
"prob": 0.15
}
],
"[\"integrated_modern\", \"ultrawide\"]": [
{
"value": 2,
"prob": 0.8
},
{
"value": 4,
"prob": 0.2
}
],
"[\"low_end\", \"1080p\"]": [
{
"value": 2,
"prob": 0.4
},
{
"value": 4,
"prob": 0.45
},
{
"value": 8,
"prob": 0.15
}
],
"[\"low_end\", \"1440p\"]": [
{
"value": 2,
"prob": 0.55
},
{
"value": 4,
"prob": 0.4
},
{
"value": 8,
"prob": 0.05
}
],
"[\"low_end\", \"2160p\"]": [
{
"value": 2,
"prob": 0.85
},
{
"value": 4,
"prob": 0.15
}
],
"[\"low_end\", \"ultrawide\"]": [
{
"value": 2,
"prob": 0.7
},
{
"value": 4,
"prob": 0.3
}
],
"[\"mid_range\", \"1080p\"]": [
{
"value": 2,
"prob": 0.3
},
{
"value": 4,
"prob": 0.5
},
{
"value": 8,
"prob": 0.2
}
],
"[\"mid_range\", \"1440p\"]": [
{
"value": 2,
"prob": 0.4
},
{
"value": 4,
"prob": 0.45
},
{
"value": 8,
"prob": 0.15
}
],
"[\"mid_range\", \"2160p\"]": [
{
"value": 2,
"prob": 0.65
},
{
"value": 4,
"prob": 0.3
},
{
"value": 8,
"prob": 0.05
}
],
"[\"mid_range\", \"ultrawide\"]": [
{
"value": 2,
"prob": 0.55
},
{
"value": 4,
"prob": 0.4
},
{
"value": 8,
"prob": 0.05
}
],
"[\"high_end\", \"1080p\"]": [
{
"value": 2,
"prob": 0.2
},
{
"value": 4,
"prob": 0.45
},
{
"value": 8,
"prob": 0.35
}
],
"[\"high_end\", \"1440p\"]": [
{
"value": 2,
"prob": 0.25
},
{
"value": 4,
"prob": 0.5
},
{
"value": 8,
"prob": 0.25
}
],
"[\"high_end\", \"2160p\"]": [
{
"value": 2,
"prob": 0.4
},
{
"value": 4,
"prob": 0.45
},
{
"value": 8,
"prob": 0.15
}
],
"[\"high_end\", \"ultrawide\"]": [
{
"value": 2,
"prob": 0.3
},
{
"value": 4,
"prob": 0.5
},
{
"value": 8,
"prob": 0.2
}
],
"[\"workstation\", \"1080p\"]": [
{
"value": 2,
"prob": 0.15
},
{
"value": 4,
"prob": 0.5
},
{
"value": 8,
"prob": 0.35
}
],
"[\"workstation\", \"1440p\"]": [
{
"value": 2,
"prob": 0.15
},
{
"value": 4,
"prob": 0.55
},
{
"value": 8,
"prob": 0.3
}
],
"[\"workstation\", \"2160p\"]": [
{
"value": 2,
"prob": 0.25
},
{
"value": 4,
"prob": 0.55
},
{
"value": 8,
"prob": 0.2
}
],
"[\"workstation\", \"ultrawide\"]": [
{
"value": 2,
"prob": 0.2
},
{
"value": 4,
"prob": 0.55
},
{
"value": 8,
"prob": 0.25
}
]
}
}

View file

@ -1,54 +0,0 @@
{
"_meta": {
"name": "screen | gpu_class",
"parents": ["gpu_class"],
"child": "screen",
"value_shape": {"w": "int css px", "h": "int css px", "dpr": "float", "aw": "availWidth", "ah": "availHeight"},
"source": "Curated Windows desktop/laptop resolutions. Restricted to width >= 1920 to avoid FP Pro's server-side screen_resolution normalization (FP Pro sorts non-standard landscape resolutions like 1536x864 into [smaller, larger] order in raw_device_attributes.screen_resolution, appearing flipped vs our spoof). All retained resolutions are common and NOT normalized by FP Pro."
},
"table": {
"integrated_old": [
{"value": {"w": 1920, "h": 1080, "dpr": 1.0, "aw": 1920, "ah": 1040}, "prob": 0.90},
{"value": {"w": 1920, "h": 1200, "dpr": 1.0, "aw": 1920, "ah": 1160}, "prob": 0.10}
],
"integrated_modern": [
{"value": {"w": 1920, "h": 1080, "dpr": 1.0, "aw": 1920, "ah": 1040}, "prob": 0.60},
{"value": {"w": 1920, "h": 1080, "dpr": 1.25, "aw": 1920, "ah": 1040}, "prob": 0.18},
{"value": {"w": 2560, "h": 1440, "dpr": 1.0, "aw": 2560, "ah": 1400}, "prob": 0.10},
{"value": {"w": 1920, "h": 1200, "dpr": 1.0, "aw": 1920, "ah": 1160}, "prob": 0.08},
{"value": {"w": 2560, "h": 1600, "dpr": 1.25, "aw": 2560, "ah": 1560}, "prob": 0.04}
],
"low_end": [
{"value": {"w": 1920, "h": 1080, "dpr": 1.0, "aw": 1920, "ah": 1040}, "prob": 0.70},
{"value": {"w": 2560, "h": 1440, "dpr": 1.0, "aw": 2560, "ah": 1400}, "prob": 0.20},
{"value": {"w": 1920, "h": 1200, "dpr": 1.0, "aw": 1920, "ah": 1160}, "prob": 0.10}
],
"mid_range": [
{"value": {"w": 1920, "h": 1080, "dpr": 1.0, "aw": 1920, "ah": 1040}, "prob": 0.50},
{"value": {"w": 2560, "h": 1440, "dpr": 1.0, "aw": 2560, "ah": 1400}, "prob": 0.25},
{"value": {"w": 1920, "h": 1080, "dpr": 1.25, "aw": 1920, "ah": 1040}, "prob": 0.08},
{"value": {"w": 2560, "h": 1440, "dpr": 1.25, "aw": 2560, "ah": 1400}, "prob": 0.05},
{"value": {"w": 3840, "h": 2160, "dpr": 1.0, "aw": 3840, "ah": 2120}, "prob": 0.04},
{"value": {"w": 3840, "h": 2160, "dpr": 1.5, "aw": 3840, "ah": 2120}, "prob": 0.04},
{"value": {"w": 2560, "h": 1080, "dpr": 1.0, "aw": 2560, "ah": 1040}, "prob": 0.04}
],
"high_end": [
{"value": {"w": 1920, "h": 1080, "dpr": 1.0, "aw": 1920, "ah": 1040}, "prob": 0.25},
{"value": {"w": 2560, "h": 1440, "dpr": 1.0, "aw": 2560, "ah": 1400}, "prob": 0.30},
{"value": {"w": 3840, "h": 2160, "dpr": 1.0, "aw": 3840, "ah": 2120}, "prob": 0.15},
{"value": {"w": 3840, "h": 2160, "dpr": 1.5, "aw": 3840, "ah": 2120}, "prob": 0.10},
{"value": {"w": 3440, "h": 1440, "dpr": 1.0, "aw": 3440, "ah": 1400}, "prob": 0.08},
{"value": {"w": 2560, "h": 1440, "dpr": 1.25, "aw": 2560, "ah": 1400}, "prob": 0.05},
{"value": {"w": 5120, "h": 1440, "dpr": 1.0, "aw": 5120, "ah": 1400}, "prob": 0.04},
{"value": {"w": 3840, "h": 2160, "dpr": 2.0, "aw": 3840, "ah": 2120}, "prob": 0.03}
],
"workstation": [
{"value": {"w": 1920, "h": 1080, "dpr": 1.0, "aw": 1920, "ah": 1040}, "prob": 0.25},
{"value": {"w": 2560, "h": 1440, "dpr": 1.0, "aw": 2560, "ah": 1400}, "prob": 0.25},
{"value": {"w": 3840, "h": 2160, "dpr": 1.0, "aw": 3840, "ah": 2120}, "prob": 0.25},
{"value": {"w": 3840, "h": 2160, "dpr": 1.5, "aw": 3840, "ah": 2120}, "prob": 0.15},
{"value": {"w": 5120, "h": 2880, "dpr": 1.0, "aw": 5120, "ah": 2840}, "prob": 0.05},
{"value": {"w": 3840, "h": 2400, "dpr": 1.5, "aw": 3840, "ah": 2360}, "prob": 0.05}
]
}
}

View file

@ -1,761 +0,0 @@
{
"_meta": "screen given (gpu_class, intra_tier)",
"table": {
"[\"integrated_old\", \"budget\"]": [
{
"value": {
"w": 1366,
"h": 768,
"aw": 1366,
"ah": 728,
"dpr": 1.0
},
"prob": 0.78
},
{
"value": {
"w": 1280,
"h": 800,
"aw": 1280,
"ah": 760,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 1024,
"h": 768,
"aw": 1024,
"ah": 728,
"dpr": 1.0
},
"prob": 0.07
}
],
"[\"integrated_old\", \"standard\"]": [
{
"value": {
"w": 1366,
"h": 768,
"aw": 1366,
"ah": 728,
"dpr": 1.0
},
"prob": 0.55
},
{
"value": {
"w": 1600,
"h": 900,
"aw": 1600,
"ah": 860,
"dpr": 1.0
},
"prob": 0.25
},
{
"value": {
"w": 1280,
"h": 800,
"aw": 1280,
"ah": 760,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.1
}
],
"[\"integrated_old\", \"premium\"]": [
{
"value": {
"w": 1600,
"h": 900,
"aw": 1600,
"ah": 860,
"dpr": 1.0
},
"prob": 0.45
},
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.4
},
{
"value": {
"w": 1366,
"h": 768,
"aw": 1366,
"ah": 728,
"dpr": 1.0
},
"prob": 0.15
}
],
"[\"integrated_modern\", \"budget\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.8
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"integrated_modern\", \"standard\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.7
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.12
},
{
"value": {
"w": 2560,
"h": 1600,
"aw": 2560,
"ah": 1560,
"dpr": 1.0
},
"prob": 0.08
}
],
"[\"integrated_modern\", \"premium\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.3
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.25
},
{
"value": {
"w": 2560,
"h": 1600,
"aw": 2560,
"ah": 1560,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.2
}
],
"[\"low_end\", \"budget\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.85
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"low_end\", \"standard\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.6
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.25
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"low_end\", \"premium\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.25
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.45
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.2
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.05
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"mid_range\", \"budget\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.75
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.2
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"mid_range\", \"standard\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.45
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.4
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"mid_range\", \"premium\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.5
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.2
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 2560,
"h": 1080,
"aw": 2560,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"high_end\", \"budget\"]": [
{
"value": {
"w": 1920,
"h": 1080,
"aw": 1920,
"ah": 1040,
"dpr": 1.0
},
"prob": 0.2
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.55
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.2
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"high_end\", \"standard\"]": [
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.4
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.4
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 5120,
"h": 1440,
"aw": 5120,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"high_end\", \"premium\"]": [
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.55
},
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 5120,
"h": 1440,
"aw": 5120,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 7680,
"h": 2160,
"aw": 7680,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"workstation\", \"budget\"]": [
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.55
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.3
},
{
"value": {
"w": 1920,
"h": 1200,
"aw": 1920,
"ah": 1160,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 2560,
"h": 1600,
"aw": 2560,
"ah": 1560,
"dpr": 1.0
},
"prob": 0.05
}
],
"[\"workstation\", \"standard\"]": [
{
"value": {
"w": 2560,
"h": 1440,
"aw": 2560,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.3
},
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.45
},
{
"value": {
"w": 2560,
"h": 1600,
"aw": 2560,
"ah": 1560,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 3440,
"h": 1440,
"aw": 3440,
"ah": 1400,
"dpr": 1.0
},
"prob": 0.1
}
],
"[\"workstation\", \"premium\"]": [
{
"value": {
"w": 3840,
"h": 2160,
"aw": 3840,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.55
},
{
"value": {
"w": 2560,
"h": 1600,
"aw": 2560,
"ah": 1560,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 5120,
"h": 2160,
"aw": 5120,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.15
},
{
"value": {
"w": 3840,
"h": 2400,
"aw": 3840,
"ah": 2360,
"dpr": 1.0
},
"prob": 0.1
},
{
"value": {
"w": 7680,
"h": 2160,
"aw": 7680,
"ah": 2120,
"dpr": 1.0
},
"prob": 0.05
}
]
}
}

View file

@ -1,60 +0,0 @@
{
"_meta": {
"name": "storage_quota_mb | gpu_class",
"parents": ["gpu_class"],
"child": "storage_quota_mb",
"value_shape": "int megabytes (reported as navigator.storage.estimate().quota / (1024*1024))",
"description": "Storage quota override returned by navigator.storage.estimate(). Real Firefox computes quota from disk space, which is a fingerprinting signal (stable per host). We replace it with a realistic per-session value conditioned on gpu_class — modern/high-end users tend to have larger SSDs, integrated_old older/smaller disks.",
"source": "Typical Windows 11 disk sizes 2024-2026 per segment: budget laptops 256-512GB SSD, mainstream 512GB-1TB SSD, gaming/workstation 1-4TB SSD. Firefox default storage quota is ~50% of available disk (capped). We simulate that computation per-tier.",
"rationale": "Values are pre-quota-limit MB as Firefox would return to navigator.storage.estimate().quota. Probabilities cover realistic ranges centered on the segment median. A single session returns one value; fingerprinters that hash the exact quota get per-session variance, matching cross-user diversity."
},
"table": {
"integrated_old": [
{"value": 61440, "prob": 0.30},
{"value": 102400, "prob": 0.25},
{"value": 122880, "prob": 0.20},
{"value": 204800, "prob": 0.15},
{"value": 40960, "prob": 0.10}
],
"integrated_modern": [
{"value": 204800, "prob": 0.30},
{"value": 307200, "prob": 0.25},
{"value": 122880, "prob": 0.15},
{"value": 409600, "prob": 0.15},
{"value": 102400, "prob": 0.10},
{"value": 512000, "prob": 0.05}
],
"low_end": [
{"value": 122880, "prob": 0.30},
{"value": 204800, "prob": 0.25},
{"value": 102400, "prob": 0.20},
{"value": 61440, "prob": 0.15},
{"value": 307200, "prob": 0.10}
],
"mid_range": [
{"value": 307200, "prob": 0.30},
{"value": 409600, "prob": 0.25},
{"value": 204800, "prob": 0.20},
{"value": 512000, "prob": 0.15},
{"value": 716800, "prob": 0.05},
{"value": 122880, "prob": 0.05}
],
"high_end": [
{"value": 512000, "prob": 0.25},
{"value": 716800, "prob": 0.25},
{"value": 409600, "prob": 0.20},
{"value": 1024000, "prob": 0.15},
{"value": 307200, "prob": 0.10},
{"value": 1536000, "prob": 0.05}
],
"workstation": [
{"value": 1024000, "prob": 0.25},
{"value": 1536000, "prob": 0.20},
{"value": 716800, "prob": 0.20},
{"value": 2048000, "prob": 0.15},
{"value": 512000, "prob": 0.10},
{"value": 3072000, "prob": 0.05},
{"value": 409600, "prob": 0.05}
]
}
}

View file

@ -1,297 +0,0 @@
{
"_meta": "storage_quota_mb given (gpu_class, intra_tier)",
"table": {
"[\"integrated_old\", \"budget\"]": [
{
"value": 32000,
"prob": 0.3
},
{
"value": 64000,
"prob": 0.4
},
{
"value": 128000,
"prob": 0.25
},
{
"value": 256000,
"prob": 0.05
}
],
"[\"integrated_old\", \"standard\"]": [
{
"value": 64000,
"prob": 0.2
},
{
"value": 128000,
"prob": 0.45
},
{
"value": 256000,
"prob": 0.25
},
{
"value": 500000,
"prob": 0.1
}
],
"[\"integrated_old\", \"premium\"]": [
{
"value": 128000,
"prob": 0.2
},
{
"value": 256000,
"prob": 0.45
},
{
"value": 500000,
"prob": 0.35
}
],
"[\"integrated_modern\", \"budget\"]": [
{
"value": 256000,
"prob": 0.3
},
{
"value": 500000,
"prob": 0.45
},
{
"value": 1000000,
"prob": 0.25
}
],
"[\"integrated_modern\", \"standard\"]": [
{
"value": 256000,
"prob": 0.25
},
{
"value": 500000,
"prob": 0.45
},
{
"value": 1000000,
"prob": 0.25
},
{
"value": 2000000,
"prob": 0.05
}
],
"[\"integrated_modern\", \"premium\"]": [
{
"value": 500000,
"prob": 0.25
},
{
"value": 1000000,
"prob": 0.5
},
{
"value": 2000000,
"prob": 0.2
},
{
"value": 4000000,
"prob": 0.05
}
],
"[\"low_end\", \"budget\"]": [
{
"value": 128000,
"prob": 0.2
},
{
"value": 256000,
"prob": 0.5
},
{
"value": 500000,
"prob": 0.25
},
{
"value": 1000000,
"prob": 0.05
}
],
"[\"low_end\", \"standard\"]": [
{
"value": 256000,
"prob": 0.2
},
{
"value": 500000,
"prob": 0.5
},
{
"value": 1000000,
"prob": 0.25
},
{
"value": 2000000,
"prob": 0.05
}
],
"[\"low_end\", \"premium\"]": [
{
"value": 500000,
"prob": 0.2
},
{
"value": 1000000,
"prob": 0.5
},
{
"value": 2000000,
"prob": 0.25
},
{
"value": 4000000,
"prob": 0.05
}
],
"[\"mid_range\", \"budget\"]": [
{
"value": 256000,
"prob": 0.15
},
{
"value": 500000,
"prob": 0.5
},
{
"value": 1000000,
"prob": 0.3
},
{
"value": 2000000,
"prob": 0.05
}
],
"[\"mid_range\", \"standard\"]": [
{
"value": 500000,
"prob": 0.2
},
{
"value": 1000000,
"prob": 0.55
},
{
"value": 2000000,
"prob": 0.2
},
{
"value": 4000000,
"prob": 0.05
}
],
"[\"mid_range\", \"premium\"]": [
{
"value": 1000000,
"prob": 0.4
},
{
"value": 2000000,
"prob": 0.45
},
{
"value": 4000000,
"prob": 0.15
}
],
"[\"high_end\", \"budget\"]": [
{
"value": 500000,
"prob": 0.15
},
{
"value": 1000000,
"prob": 0.5
},
{
"value": 2000000,
"prob": 0.3
},
{
"value": 4000000,
"prob": 0.05
}
],
"[\"high_end\", \"standard\"]": [
{
"value": 1000000,
"prob": 0.3
},
{
"value": 2000000,
"prob": 0.5
},
{
"value": 4000000,
"prob": 0.2
}
],
"[\"high_end\", \"premium\"]": [
{
"value": 2000000,
"prob": 0.4
},
{
"value": 4000000,
"prob": 0.45
},
{
"value": 8000000,
"prob": 0.15
}
],
"[\"workstation\", \"budget\"]": [
{
"value": 1000000,
"prob": 0.3
},
{
"value": 2000000,
"prob": 0.5
},
{
"value": 4000000,
"prob": 0.2
}
],
"[\"workstation\", \"standard\"]": [
{
"value": 2000000,
"prob": 0.3
},
{
"value": 4000000,
"prob": 0.5
},
{
"value": 8000000,
"prob": 0.2
}
],
"[\"workstation\", \"premium\"]": [
{
"value": 4000000,
"prob": 0.35
},
{
"value": 8000000,
"prob": 0.45
},
{
"value": 16000000,
"prob": 0.2
}
]
}
}

View file

@ -1,650 +0,0 @@
{
"_source": "browserforge fingerprint-network.zip, FF/Windows slice, 3 userAgent leaves merged",
"_ff_win_uas": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:145.0) Gecko/20100101 Firefox/145.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0"
],
"_note": "Per-node weighted distributions. Marginal over the 3 Firefox/Windows UAs present in browserforge data.",
"distributions": {
"appCodeName": [
[
"Mozilla",
1.0
]
],
"appVersion": [
[
"5.0 (Windows)",
1.0
]
],
"doNotTrack": [
[
"1",
1.0
]
],
"extraProperties": [
[
{
"vendorFlavors": [],
"globalPrivacyControl": true,
"pdfViewerEnabled": true,
"installedApps": []
},
0.6111111111111112
],
[
{
"vendorFlavors": [],
"globalPrivacyControl": null,
"pdfViewerEnabled": null,
"installedApps": []
},
0.3888888888888889
]
],
"maxTouchPoints": [
[
0,
0.5777777777777778
],
[
1,
0.3666666666666667
],
[
10,
0.05555555555555555
]
],
"oscpu": [
[
"Windows NT 10.0; Win64; x64",
1.0
]
],
"webdriver": [
[
false,
1.0
]
],
"productSub": [
[
"20100101",
1.0
]
],
"hardwareConcurrency": [
[
12,
0.3333333333333333
],
[
16,
0.2777777777777778
],
[
8,
0.13333333333333333
],
[
24,
0.13333333333333333
],
[
32,
0.06666666666666667
],
[
6,
0.05555555555555555
]
],
"platform": [
[
"Win32",
1.0
]
],
"screen": [
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 772,
"hasHDR": false,
"width": 1536,
"height": 960,
"availWidth": 1536,
"availHeight": 960,
"clientWidth": 0,
"clientHeight": 21,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 751,
"outerHeight": 886,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1.25
},
0.3333333333333333
],
[
{
"availTop": 0,
"availLeft": 2176,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 2169,
"hasHDR": false,
"width": 2176,
"height": 1224,
"availWidth": 2176,
"availHeight": 1176,
"clientWidth": 0,
"clientHeight": 19,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 2190,
"outerHeight": 1190,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1.7647058823529411
},
0.13333333333333333
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 318,
"hasHDR": false,
"width": 2560,
"height": 1440,
"availWidth": 2560,
"availHeight": 1440,
"clientWidth": 0,
"clientHeight": 18,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 2121,
"outerHeight": 1191,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.1111111111111111
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 652,
"hasHDR": false,
"width": 2752,
"height": 1152,
"availWidth": 2752,
"availHeight": 1104,
"clientWidth": 0,
"clientHeight": 19,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1806,
"outerHeight": 1104,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1.25
},
0.06666666666666667
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 1912,
"hasHDR": false,
"width": 1920,
"height": 1080,
"availWidth": 1920,
"availHeight": 1032,
"clientWidth": 0,
"clientHeight": 18,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1936,
"outerHeight": 1048,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.06666666666666667
],
[
{
"availTop": 29,
"availLeft": 21,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 15,
"hasHDR": false,
"width": 1376,
"height": 774,
"availWidth": 1355,
"availHeight": 745,
"clientWidth": 0,
"clientHeight": 21,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1352,
"outerHeight": 749,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.05555555555555555
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 58,
"hasHDR": false,
"width": 1707,
"height": 960,
"availWidth": 1707,
"availHeight": 928,
"clientWidth": 0,
"clientHeight": 19,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1564,
"outerHeight": 793,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.05555555555555555
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 34,
"hasHDR": false,
"width": 5120,
"height": 1440,
"availWidth": 5120,
"availHeight": 1392,
"clientWidth": 0,
"clientHeight": 18,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 2462,
"outerHeight": 1399,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.05555555555555555
],
[
{
"availTop": 0,
"availLeft": 1920,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 1912,
"hasHDR": false,
"width": 1920,
"height": 1080,
"availWidth": 1920,
"availHeight": 1032,
"clientWidth": 0,
"clientHeight": 18,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1936,
"outerHeight": 1048,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.05555555555555555
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 95,
"hasHDR": false,
"width": 1366,
"height": 768,
"availWidth": 1366,
"availHeight": 768,
"clientWidth": 0,
"clientHeight": 18,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1294,
"outerHeight": 1280,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.03333333333333333
],
[
{
"availTop": 0,
"availLeft": 0,
"pageXOffset": 0,
"pageYOffset": 0,
"screenX": 1273,
"hasHDR": false,
"width": 2560,
"height": 1440,
"availWidth": 2560,
"availHeight": 1400,
"clientWidth": 0,
"clientHeight": 18,
"innerWidth": 0,
"innerHeight": 0,
"outerWidth": 1294,
"outerHeight": 1407,
"colorDepth": 24,
"pixelDepth": 24,
"devicePixelRatio": 1
},
0.03333333333333333
]
],
"pluginsData": [
[
{
"plugins": [
{
"name": "PDF Viewer",
"description": "Portable Document Format",
"filename": "internal-pdf-viewer",
"mimeTypes": [
{
"type": "application/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
},
{
"type": "text/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
}
]
},
{
"name": "Chrome PDF Viewer",
"description": "Portable Document Format",
"filename": "internal-pdf-viewer",
"mimeTypes": [
{
"type": "application/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
},
{
"type": "text/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
}
]
},
{
"name": "Chromium PDF Viewer",
"description": "Portable Document Format",
"filename": "internal-pdf-viewer",
"mimeTypes": [
{
"type": "application/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
},
{
"type": "text/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
}
]
},
{
"name": "Microsoft Edge PDF Viewer",
"description": "Portable Document Format",
"filename": "internal-pdf-viewer",
"mimeTypes": [
{
"type": "application/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
},
{
"type": "text/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
}
]
},
{
"name": "WebKit built-in PDF",
"description": "Portable Document Format",
"filename": "internal-pdf-viewer",
"mimeTypes": [
{
"type": "application/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
},
{
"type": "text/pdf",
"suffixes": "pdf",
"description": "Portable Document Format",
"enabledPlugin": "PDF Viewer"
}
]
}
],
"mimeTypes": [
"Portable Document Format~~application/pdf~~pdf",
"Portable Document Format~~text/pdf~~pdf"
]
},
0.6111111111111112
],
[
{
"plugins": [],
"mimeTypes": []
},
0.3888888888888889
]
],
"audioCodecs": [
[
{
"ogg": "probably",
"mp3": "maybe",
"wav": "probably",
"m4a": "maybe",
"aac": "maybe"
},
1.0
]
],
"videoCodecs": [
[
{
"ogg": "",
"h264": "probably",
"webm": "probably"
},
1.0
]
],
"videoCard": [
[
{
"renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar",
"vendor": "Google Inc. (NVIDIA)"
},
0.8444444444444444
],
[
{
"renderer": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0), or similar",
"vendor": "Google Inc. (AMD)"
},
0.08888888888888889
],
[
{
"renderer": "ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0), or similar",
"vendor": "Google Inc. (Intel)"
},
0.06666666666666667
]
],
"multimediaDevices": [
[
{
"speakers": [],
"micros": [],
"webcams": []
},
1.0
]
],
"fonts": [
[
[],
0.3888888888888889
],
[
[
"Calibri",
"HELV",
"MS UI Gothic",
"Marlett",
"Segoe UI Light",
"Small Fonts"
],
0.3222222222222222
],
[
[
"Agency FB",
"Calibri",
"Century",
"Century Gothic",
"Franklin Gothic",
"HELV",
"Haettenschweiler",
"Leelawadee",
"Lucida Bright",
"Lucida Sans",
"MS Outlook",
"MS Reference Specialty",
"MS UI Gothic",
"MT Extra",
"Marlett",
"Microsoft Uighur",
"Monotype Corsiva",
"Pristina",
"Segoe UI Light",
"Small Fonts"
],
0.13333333333333333
],
[
[
"AvantGarde Bk BT",
"Calibri",
"Clarendon",
"Franklin Gothic",
"Futura Bk BT",
"Futura Md BT",
"GOTHAM",
"HELV",
"Humanst521 BT",
"MS UI Gothic",
"MYRIAD PRO",
"Marlett",
"Minion Pro",
"Segoe UI Light",
"Small Fonts",
"TRAJAN PRO"
],
0.06666666666666667
],
[
[
"Calibri",
"MS UI Gothic",
"Marlett",
"Segoe UI Light"
],
0.05555555555555555
],
[
[
"Agency FB",
"Calibri",
"Century",
"Century Gothic",
"Franklin Gothic",
"Futura Bk BT",
"Futura Md BT",
"GOTHAM",
"HELV",
"Haettenschweiler",
"Humanst521 BT",
"Lucida Bright",
"Lucida Sans",
"MS Outlook",
"MS Reference Specialty",
"MS UI Gothic",
"MT Extra",
"MYRIAD PRO",
"Marlett",
"Minion Pro",
"Monotype Corsiva",
"Pristina",
"Segoe UI Light",
"Small Fonts"
],
0.03333333333333333
]
]
}
}

View file

@ -1,173 +0,0 @@
{
"core": [
{
"name": "arial"
},
{
"name": "calibri"
},
{
"name": "cambria"
},
{
"name": "cambria math"
},
{
"name": "candara"
},
{
"name": "comic sans ms"
},
{
"name": "consolas"
},
{
"name": "constantia"
},
{
"name": "corbel"
},
{
"name": "courier new"
},
{
"name": "ebrima"
},
{
"name": "franklin gothic medium"
},
{
"name": "gabriola"
},
{
"name": "gadugi"
},
{
"name": "georgia"
},
{
"name": "impact"
},
{
"name": "javanese text"
},
{
"name": "lucida console"
},
{
"name": "lucida sans unicode"
},
{
"name": "ms gothic"
},
{
"name": "ms pgothic"
},
{
"name": "mv boli"
},
{
"name": "malgun gothic"
},
{
"name": "marlett"
},
{
"name": "microsoft himalaya"
},
{
"name": "microsoft jhenghei"
},
{
"name": "microsoft jhenghei ui"
},
{
"name": "microsoft new tai lue"
},
{
"name": "microsoft phagspa"
},
{
"name": "microsoft sans serif"
},
{
"name": "microsoft yahei"
},
{
"name": "microsoft yi baiti"
},
{
"name": "mingliu-extb"
},
{
"name": "mongolian baiti"
},
{
"name": "myanmar text"
},
{
"name": "pmingliu-extb"
},
{
"name": "palatino linotype"
},
{
"name": "segoe print"
},
{
"name": "segoe script"
},
{
"name": "segoe ui"
},
{
"name": "segoe ui symbol"
},
{
"name": "simsun"
},
{
"name": "simsun-extb"
},
{
"name": "sitka small"
},
{
"name": "sylfaen"
},
{
"name": "trebuchet ms"
},
{
"name": "verdana"
},
{
"name": "webdings"
},
{
"name": "yu gothic"
},
{
"name": "courier"
},
{
"name": "helvetica"
},
{
"name": "segoe ui light"
}
],
"optional": [],
"profiles": [
{
"name": "win_clean",
"weight": 35,
"optional": []
},
{
"name": "win_office",
"weight": 65,
"optional": []
}
]
}

View file

@ -1,17 +0,0 @@
{
"_meta": {
"name": "audio (joint: sample_rate, output_latency_ms, max_channel_count)",
"parents": [],
"child": "audio",
"source": "Curated from common Windows WASAPI/DirectSound configs on FF desktop"
},
"table": [
{"value": {"rate": 44100, "latency": 40, "channels": 2}, "prob": 0.20},
{"value": {"rate": 48000, "latency": 30, "channels": 2}, "prob": 0.25},
{"value": {"rate": 48000, "latency": 20, "channels": 2}, "prob": 0.15},
{"value": {"rate": 48000, "latency": 40, "channels": 6}, "prob": 0.08},
{"value": {"rate": 48000, "latency": 60, "channels": 2}, "prob": 0.12},
{"value": {"rate": 44100, "latency": 50, "channels": 2}, "prob": 0.10},
{"value": {"rate": 48000, "latency": 25, "channels": 2}, "prob": 0.10}
]
}

View file

@ -1,14 +0,0 @@
{
"_meta": {
"name": "Independent marginal priors (no parents)",
"source": "StatCounter GlobalStats + Firefox defaults + community data 2025-2026",
"note": "Codec prefs (av1_enabled, webm_encoder_enabled, hw_video_decoding, wmf_enabled, ffvpx_enabled) moved to cpt_codec_given_class.json — they correlate with GPU class via Firefox version / user-tier distribution."
},
"dark_theme": {
"_note": "0=light, 1=dark. StatCounter ~40% users report dark theme preference on desktop.",
"table": [
{"value": 0, "prob": 0.60},
{"value": 1, "prob": 0.40}
]
}
}

View file

@ -1,476 +0,0 @@
{
"win": [
{
"vendor": "Google Inc. (NVIDIA)",
"renderer_out": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.470921,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (NVIDIA)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212988,35379|200704,35380|256,35657|4096,35658|16380,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4095,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.195745,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.160284,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|16,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (AMD)",
"renderer_out": "ANGLE (AMD, Radeon HD 3200 Graphics Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.086524,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (AMD, Radeon HD 3200 Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (AMD)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (AMD)",
"renderer_out": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.053901,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (AMD)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (Microsoft)",
"renderer_out": "ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.012766,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Microsoft, Microsoft Basic Render Driver Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Microsoft)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|0,32937|0,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|16,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0)",
"prob": 0.007092,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_4_1 ps_4_1), or similar",
"prob": 0.004255,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_4_1 ps_4_1, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|8192,34045|2,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|512,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|4,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16383:16383,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (AMD)",
"renderer_out": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0)",
"prob": 0.001419,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (AMD)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_4_0 ps_4_0)",
"prob": 0.001419,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_4_0 ps_4_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3317|4,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32936|0,32937|0,32938|1,32968|0,32969|1,32970|0,32971|1,33170|4352,34016|33984,34024|8192,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34877|32774,34921|16,34930|16,35660|16,35661|32,35738|5121,35739|6408,36003|0,36004|2147483647,36005|2147483647,36347|4096,36348|14,36349|1024,37443|37444",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16383:16383,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": false
}
},
{
"vendor": "Google Inc. (NVIDIA)",
"renderer_out": "ANGLE (NVIDIA, NVIDIA GeForce 8800 GTX Direct3D11 vs_4_0 ps_4_0)",
"prob": 0.001419,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (NVIDIA, NVIDIA GeForce 8800 GTX Direct3D11 vs_4_0 ps_4_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (NVIDIA)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3317|4,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33170|4352,34016|33984,34024|8192,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34877|32774,34921|16,34930|16,35660|16,35661|32,35738|5121,35739|6408,36003|0,36004|2147483647,36005|2147483647,36347|4095,36348|14,36349|1024,37443|37444",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16383:16383,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": false
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel 945GM Direct3D11 vs_4_0 ps_4_0)",
"prob": 0.001419,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel 945GM Direct3D11 vs_4_0 ps_4_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3317|4,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32936|0,32937|0,32938|1,32968|0,32969|1,32970|0,32971|1,33170|4352,34016|33984,34024|8192,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34877|32774,34921|16,34930|16,35660|16,35661|32,35738|5121,35739|6408,36003|0,36004|2147483647,36005|2147483647,36347|4096,36348|14,36349|1024,37443|37444",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16383:16383,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": false
}
},
{
"vendor": "Google Inc. (Intel)",
"renderer_out": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_4_1 ps_4_1)",
"prob": 0.001419,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_4_1 ps_4_1, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (Intel)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|8192,34045|2,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|512,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212992,35379|200704,35380|256,35657|4096,35658|16384,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|4,36203|4294967294,36347|4096,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16383:16383,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (NVIDIA)",
"renderer_out": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0)",
"prob": 0.001419,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (NVIDIA)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212988,35379|200704,35380|256,35657|4096,35658|16380,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4095,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
}
],
"mac": [
{
"vendor": "Apple",
"renderer_out": "Apple M1, or similar",
"prob": 0.84322,
"prefs": {
"zoom.stealth.webgl.renderer": "Apple M1, or similar",
"zoom.stealth.webgl.vendor": "Apple",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|4294967295,2964|7680,2965|7680,2966|7680,2967|0,2968|4294967295,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|1048575,33001|150000,33170|4352,34016|33984,34024|8192,34045|16,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|16,35373|16,35374|80,35375|80,35376|65536,35377|266240,35379|266240,35380|256,35657|4096,35658|4096,35659|124,35660|16,35661|80,35723|4352,35738|5121,35739|6408,35968|4,35978|64,35979|4,36003|0,36004|4294967295,36005|4294967295,36063|8,36183|4,36203|4294967295,36347|1024,36348|32,36349|1024,37137|18446744073709552000,37154|64,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:64,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Intel Inc.",
"renderer_out": "Intel(R) HD Graphics 400, or similar",
"prob": 0.076271,
"prefs": {
"zoom.stealth.webgl.renderer": "Intel(R) HD Graphics 400, or similar",
"zoom.stealth.webgl.vendor": "Intel Inc.",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|4294967295,2964|7680,2965|7680,2966|7680,2967|0,2968|4294967295,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|1048575,33001|150000,33170|4352,34016|33984,34024|8192,34045|16,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|15,35373|15,35374|75,35375|75,35376|65536,35377|249856,35379|249856,35380|256,35657|4096,35658|4096,35659|60,35660|16,35661|80,35723|4352,35738|5121,35739|6408,35968|4,35978|64,35979|4,36003|0,36004|4294967295,36005|4294967295,36063|8,36183|16,36203|4294967295,36347|1024,36348|32,36349|1024,37137|18446744073709552000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33902|1:1",
"zoom.stealth.webgl.float_params": "33901|1:255.875",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "ATI Technologies Inc.",
"renderer_out": "Radeon R9 200 Series, or similar",
"prob": 0.038136,
"prefs": {
"zoom.stealth.webgl.renderer": "Radeon R9 200 Series, or similar",
"zoom.stealth.webgl.vendor": "ATI Technologies Inc.",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|4294967295,2964|7680,2965|7680,2966|7680,2967|0,2968|4294967295,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|16384,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|1048575,33001|150000,33170|4352,34016|33984,34024|8192,34045|16,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|16,35373|16,35374|80,35375|80,35376|65536,35377|266240,35379|266240,35380|256,35657|4096,35658|4096,35659|128,35660|16,35661|80,35723|4352,35738|5121,35739|6408,35968|4,35978|64,35979|4,36003|0,36004|4294967295,36005|4294967295,36063|8,36183|8,36203|4294967295,36347|1024,36348|32,36349|1024,37137|18446744073709552000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:8191,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Intel Inc.",
"renderer_out": "Intel(R) HD Graphics, or similar",
"prob": 0.033898,
"prefs": {
"zoom.stealth.webgl.renderer": "Intel(R) HD Graphics, or similar",
"zoom.stealth.webgl.vendor": "Intel Inc.",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|4294967295,2964|7680,2965|7680,2966|7680,2967|0,2968|4294967295,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|1048575,33001|150000,33170|4352,34016|33984,34024|8192,34045|16,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|15,35373|15,35374|75,35375|75,35376|65536,35377|249856,35379|249856,35380|256,35657|4096,35658|4096,35659|60,35660|16,35661|80,35723|4352,35738|5121,35739|6408,35968|4,35978|64,35979|4,36003|0,36004|4294967295,36005|4294967295,36063|8,36183|16,36203|4294967295,36347|1024,36348|32,36349|1024,37137|18446744073709552000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33902|1:1",
"zoom.stealth.webgl.float_params": "33901|1:255.875",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Google Inc. (NVIDIA)",
"renderer_out": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0), or similar",
"prob": 0.008475,
"prefs": {
"zoom.stealth.webgl.renderer": "ANGLE (NVIDIA, NVIDIA GeForce GTX 980 Direct3D11 vs_5_0 ps_5_0, D3D11)",
"zoom.stealth.webgl.vendor": "Google Inc. (NVIDIA)",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context,WEBGL_provoking_vertex",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|2147483647,2964|7680,2965|7680,2966|7680,2967|0,2968|2147483647,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|2147483647,33001|2147483647,33170|4352,34016|33984,34024|16384,34045|2,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|16,35071|2048,35076|-8,35077|7,35371|12,35373|12,35374|24,35375|24,35376|65536,35377|212988,35379|200704,35380|256,35657|4096,35658|16380,35659|120,35660|16,35661|32,35723|4352,35738|5121,35739|6408,35968|4,35978|120,35979|4,36003|0,36004|2147483647,36005|2147483647,36063|8,36183|8,36203|4294967294,36347|4095,36348|30,36349|1024,37137|0,37154|120,37157|120,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32767:32767,33901|1:1024,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|31:30:0,35633*36340|31:30:0,35633*36341|31:30:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|31:30:0,35632*36340|31:30:0,35632*36341|31:30:0",
"zoom.stealth.webgl2.enabled": true
}
}
],
"lin": [
{
"vendor": "Intel",
"renderer_out": "Intel(R) HD Graphics, or similar",
"prob": 0.282051,
"prefs": {
"zoom.stealth.webgl.renderer": "Intel(R) HD Graphics, or similar",
"zoom.stealth.webgl.vendor": "Intel",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|8192,34045|15,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-8,35077|7,35371|15,35373|15,35374|90,35375|90,35376|65536,35377|262144,35379|262144,35380|32,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|16,35978|64,35979|4,36003|0,36004|255,36005|255,36063|8,36183|16,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:255,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "NVIDIA Corporation",
"renderer_out": "NVIDIA GeForce GTX 980, or similar",
"prob": 0.25641,
"prefs": {
"zoom.stealth.webgl.renderer": "NVIDIA GeForce GTX 980, or similar",
"zoom.stealth.webgl.vendor": "NVIDIA Corporation",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|4294967295,2964|7680,2965|7680,2966|7680,2967|0,2968|4294967295,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|32768,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|16384,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|1048576,33001|1048576,33170|4352,34016|33984,34024|32768,34045|15,34076|32768,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-8,35077|7,35371|14,35373|14,35374|84,35375|84,35376|65536,35377|233472,35379|233472,35380|256,35657|4096,35658|4096,35659|124,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|4,35978|128,35979|4,36003|0,36004|4294967295,36005|4294967295,36063|8,36183|32,36203|4294967295,36347|1024,36348|32,36349|1024,37137|18446744073709552000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32768:32768,33901|1:2047,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Intel",
"renderer_out": "Intel(R) HD Graphics 400, or similar",
"prob": 0.153846,
"prefs": {
"zoom.stealth.webgl.renderer": "Intel(R) HD Graphics 400, or similar",
"zoom.stealth.webgl.vendor": "Intel",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|8192,34045|15,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-8,35077|7,35371|15,35373|15,35374|90,35375|90,35376|65536,35377|262144,35379|262144,35380|32,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|16,35978|64,35979|4,36003|0,36004|255,36005|255,36063|8,36183|8,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:255,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "AMD",
"renderer_out": "Radeon R9 200 Series, or similar",
"prob": 0.102564,
"prefs": {
"zoom.stealth.webgl.renderer": "Radeon R9 200 Series, or similar",
"zoom.stealth.webgl.vendor": "AMD",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|16384,34045|16,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-32,35077|31,35371|15,35373|15,35374|90,35375|90,35376|4294967292,35377|16106143729,35379|16106143729,35380|4,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|128,35978|128,35979|4,36003|0,36004|255,36005|255,36063|8,36183|8,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33902|1:1",
"zoom.stealth.webgl.float_params": "33901|0.125:2048",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "AMD",
"renderer_out": "Radeon HD 3200 Graphics, or similar",
"prob": 0.083333,
"prefs": {
"zoom.stealth.webgl.renderer": "Radeon HD 3200 Graphics, or similar",
"zoom.stealth.webgl.vendor": "AMD",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|16384,34045|16,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-32,35077|31,35371|15,35373|15,35374|90,35375|90,35376|4051211264,35377|15192058624,35379|15192058624,35380|4,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|128,35978|128,35979|4,36003|0,36004|255,36005|255,36063|8,36183|8,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33902|1:1",
"zoom.stealth.webgl.float_params": "33901|0.125:2048",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Mesa",
"renderer_out": "llvmpipe, or similar",
"prob": 0.070513,
"prefs": {
"zoom.stealth.webgl.renderer": "llvmpipe, or similar",
"zoom.stealth.webgl.vendor": "Mesa",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|16384,34045|16,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-32,35077|31,35371|15,35373|15,35374|90,35375|90,35376|65536,35377|262144,35379|262144,35380|16,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|64,35978|64,35979|4,36003|0,36004|255,36005|255,36063|8,36183|4,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:255,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Intel",
"renderer_out": "Intel(R) HD Graphics 400",
"prob": 0.019231,
"prefs": {
"zoom.stealth.webgl.renderer": "Intel(R) HD Graphics 400",
"zoom.stealth.webgl.vendor": "Intel",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|8192,3408|4,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|8192,34045|15,34076|8192,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-8,35077|7,35371|15,35373|15,35374|90,35375|90,35376|65536,35377|262144,35379|262144,35380|32,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|16,35978|64,35979|4,36003|0,36004|255,36005|255,36063|8,36183|8,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:255,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Mesa",
"renderer_out": "llvmpipe",
"prob": 0.019231,
"prefs": {
"zoom.stealth.webgl.renderer": "llvmpipe",
"zoom.stealth.webgl.vendor": "Mesa",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|16384,34045|16,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-32,35077|31,35371|15,35373|15,35374|90,35375|90,35376|65536,35377|262144,35379|262144,35380|16,35657|16384,35658|16384,35659|128,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|64,35978|64,35979|4,36003|0,36004|255,36005|255,36063|8,36183|4,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33901|1:255,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "NVIDIA Corporation",
"renderer_out": "NVIDIA GeForce GTX 980/PCIe/SSE2",
"prob": 0.00641,
"prefs": {
"zoom.stealth.webgl.renderer": "NVIDIA GeForce GTX 980/PCIe/SSE2",
"zoom.stealth.webgl.vendor": "NVIDIA Corporation",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|4294967295,2964|7680,2965|7680,2966|7680,2967|0,2968|4294967295,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|32768,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|16384,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|1048576,33001|1048576,33170|4352,34016|33984,34024|32768,34045|15,34076|32768,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-8,35077|7,35371|14,35373|14,35374|84,35375|84,35376|65536,35377|233472,35379|233472,35380|256,35657|4096,35658|4096,35659|124,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|4,35978|128,35979|4,36003|0,36004|4294967295,36005|4294967295,36063|8,36183|32,36203|4294967295,36347|1024,36348|32,36349|1024,37137|18446744073709552000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|32768:32768,33901|1:2047,33902|1:1",
"zoom.stealth.webgl.float_params": "",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
},
{
"vendor": "Mesa",
"renderer_out": "GeForce GTX 980, or similar",
"prob": 0.00641,
"prefs": {
"zoom.stealth.webgl.renderer": "GeForce GTX 980, or similar",
"zoom.stealth.webgl.vendor": "Mesa",
"zoom.stealth.webgl.extensions": "ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_depth_clamp,EXT_float_blend,EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,WEBGL_color_buffer_float,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,WEBGL_lose_context",
"zoom.stealth.webgl2.extensions": "EXT_color_buffer_float,EXT_depth_clamp,EXT_float_blend,EXT_texture_compression_bptc,EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,OES_texture_float_linear,WEBGL_compressed_texture_astc,WEBGL_compressed_texture_etc,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_lose_context",
"zoom.stealth.webgl.int_params": "2849|1,2885|1029,2886|2305,2931|1,2932|513,2961|0,2962|519,2963|255,2964|7680,2965|7680,2966|7680,2967|0,2968|255,3074|1029,3314|0,3315|0,3316|0,3317|4,3330|0,3331|0,3332|0,3333|4,3379|16384,3408|8,3410|8,3411|8,3412|8,3413|8,3414|24,3415|0,10752|0,32777|32774,32824|0,32877|0,32878|0,32883|2048,32936|1,32937|4,32938|1,32968|0,32969|1,32970|0,32971|1,33000|3000,33001|3000,33170|4352,34016|33984,34024|16384,34045|15,34076|16384,34816|519,34817|7680,34818|7680,34819|7680,34852|8,34853|1029,34854|0,34855|0,34856|0,34857|0,34858|0,34859|0,34860|0,34877|32774,34921|16,34930|32,35071|2048,35076|-8,35077|7,35371|14,35373|14,35374|84,35375|84,35376|65536,35377|245760,35379|245760,35380|256,35657|16384,35658|16384,35659|124,35660|32,35661|192,35723|4352,35738|5121,35739|6408,35968|128,35978|128,35979|4,36003|0,36004|255,36005|255,36063|8,36183|8,36203|4294967295,36347|4096,36348|32,36349|4096,37137|9223372034707292000,37154|128,37157|128,37443|37444,37447|1000000000",
"zoom.stealth.webgl.int2_params": "2928|0:1,3386|16384:16384,33902|1:1",
"zoom.stealth.webgl.float_params": "33901|1:63.375",
"zoom.stealth.webgl.shader_precisions": "35633*36336|127:127:23,35633*36337|127:127:23,35633*36338|127:127:23,35633*36339|24:24:0,35633*36340|24:24:0,35633*36341|24:24:0,35632*36336|127:127:23,35632*36337|127:127:23,35632*36338|127:127:23,35632*36339|24:24:0,35632*36340|24:24:0,35632*36341|24:24:0",
"zoom.stealth.webgl2.enabled": true
}
}
],
"_meta": {
"source": "camoufox webgl_data.db (offline extract; real Firefox WebGL telemetry)",
"note": "prefs are zoom.stealth.webgl.* override values; prob normalized per-OS"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,469 +0,0 @@
{
"_meta": {
"source": "browserforge Windows UAs (offline; OS-level screen/cores marginal)"
},
"screens": [
{
"w": 1536,
"h": 864,
"availW": 1536,
"availH": 816,
"dpr": 1.25,
"prob": 0.264935
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.161965
},
{
"w": 1280,
"h": 720,
"availW": 1280,
"availH": 672,
"dpr": 1.5,
"prob": 0.07915
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1040,
"dpr": 1,
"prob": 0.06376
},
{
"w": 1366,
"h": 768,
"availW": 1366,
"availH": 728,
"dpr": 1,
"prob": 0.054416
},
{
"w": 1366,
"h": 768,
"availW": 1366,
"availH": 720,
"dpr": 1,
"prob": 0.046171
},
{
"w": 1536,
"h": 864,
"availW": 1536,
"availH": 824,
"dpr": 1.25,
"prob": 0.036644
},
{
"w": 1536,
"h": 960,
"availW": 1536,
"availH": 912,
"dpr": 1.25,
"prob": 0.029681
},
{
"w": 2560,
"h": 1440,
"availW": 2560,
"availH": 1392,
"dpr": 1,
"prob": 0.029133
},
{
"w": 1280,
"h": 800,
"availW": 1280,
"availH": 752,
"dpr": 1.5,
"prob": 0.024917
},
{
"w": 1536,
"h": 864,
"availW": 1536,
"availH": 864,
"dpr": 1.25,
"prob": 0.024733
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1080,
"dpr": 1,
"prob": 0.013742
},
{
"w": 1280,
"h": 720,
"availW": 1280,
"availH": 680,
"dpr": 1.5,
"prob": 0.012643
},
{
"w": 1280,
"h": 720,
"availW": 1280,
"availH": 720,
"dpr": 1.5,
"prob": 0.01081
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.009527
},
{
"w": 1707,
"h": 1067,
"availW": 1707,
"availH": 1019,
"dpr": 1.5,
"prob": 0.009344
},
{
"w": 1440,
"h": 900,
"availW": 1440,
"availH": 852,
"dpr": 2,
"prob": 0.008795
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.008795
},
{
"w": 1920,
"h": 1200,
"availW": 1920,
"availH": 1152,
"dpr": 1,
"prob": 0.007513
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.007146
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.006596
},
{
"w": 1600,
"h": 900,
"availW": 1600,
"availH": 860,
"dpr": 1,
"prob": 0.006229
},
{
"w": 2048,
"h": 1152,
"availW": 2048,
"availH": 1104,
"dpr": 1.25,
"prob": 0.006045
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.005864
},
{
"w": 1536,
"h": 864,
"availW": 1536,
"availH": 816,
"dpr": 1.25,
"prob": 0.005864
},
{
"w": 1366,
"h": 768,
"availW": 1366,
"availH": 768,
"dpr": 1,
"prob": 0.00568
},
{
"w": 2560,
"h": 1440,
"availW": 2560,
"availH": 1392,
"dpr": 1.5,
"prob": 0.00513
},
{
"w": 1707,
"h": 960,
"availW": 1707,
"availH": 912,
"dpr": 1.5,
"prob": 0.004946
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1040,
"dpr": 1,
"prob": 0.004946
},
{
"w": 3440,
"h": 1440,
"availW": 3440,
"availH": 1392,
"dpr": 1,
"prob": 0.00458
},
{
"w": 1504,
"h": 1003,
"availW": 1504,
"availH": 955,
"dpr": 1.5,
"prob": 0.004396
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.004396
},
{
"w": 1280,
"h": 800,
"availW": 1280,
"availH": 752,
"dpr": 2,
"prob": 0.004396
},
{
"w": 1536,
"h": 864,
"availW": 1536,
"availH": 816,
"dpr": 1.25,
"prob": 0.004396
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1040,
"dpr": 1,
"prob": 0.004031
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1050,
"dpr": 1,
"prob": 0.003847
},
{
"w": 2560,
"h": 1440,
"availW": 2560,
"availH": 1400,
"dpr": 1,
"prob": 0.003847
},
{
"w": 1366,
"h": 768,
"availW": 1366,
"availH": 728,
"dpr": 1,
"prob": 0.003664
},
{
"w": 1920,
"h": 1080,
"availW": 1920,
"availH": 1032,
"dpr": 1,
"prob": 0.003664
},
{
"w": 1600,
"h": 900,
"availW": 1600,
"availH": 852,
"dpr": 1,
"prob": 0.003664
}
],
"cores": [
{
"value": 8,
"prob": 0.271311
},
{
"value": 12,
"prob": 0.21567
},
{
"value": 4,
"prob": 0.157888
},
{
"value": 16,
"prob": 0.136607
},
{
"value": 20,
"prob": 0.03971
},
{
"value": 2,
"prob": 0.025086
},
{
"value": 24,
"prob": 0.023184
},
{
"value": 6,
"prob": 0.022233
},
{
"value": 32,
"prob": 0.021876
},
{
"value": 14,
"prob": 0.015575
},
{
"value": 3,
"prob": 0.011651
},
{
"value": 28,
"prob": 0.010462
},
{
"value": 22,
"prob": 0.010225
},
{
"value": 5,
"prob": 0.008441
},
{
"value": 10,
"prob": 0.005588
},
{
"value": 7,
"prob": 0.005469
},
{
"value": 9,
"prob": 0.004042
},
{
"value": 11,
"prob": 0.003923
},
{
"value": 18,
"prob": 0.003567
},
{
"value": 15,
"prob": 0.001189
},
{
"value": 13,
"prob": 0.000951
},
{
"value": 36,
"prob": 0.000713
},
{
"value": 64,
"prob": 0.000594
},
{
"value": 44,
"prob": 0.000594
},
{
"value": 19,
"prob": 0.000476
},
{
"value": 17,
"prob": 0.000357
},
{
"value": 23,
"prob": 0.000357
},
{
"value": 40,
"prob": 0.000357
},
{
"value": 21,
"prob": 0.000357
},
{
"value": 48,
"prob": 0.000357
},
{
"value": 640,
"prob": 0.000357
},
{
"value": 56,
"prob": 0.000357
},
{
"value": 26,
"prob": 0.000238
},
{
"value": 27,
"prob": 0.000119
},
{
"value": 25,
"prob": 0.000119
}
]
}

View file

@ -1,273 +0,0 @@
"""Public dataclass surface for fpforge."""
from __future__ import annotations
from dataclasses import dataclass, field, replace as _dc_replace
from typing import Any, Dict, List, Optional
from ._sampler import sample as _sample_raw
@dataclass(frozen=True)
class GPUProfile:
vendor: str
renderer: str
class_tier: str # "low_end" | "mid_range" | "high_end" | "integrated_old" | "integrated_modern"
@dataclass(frozen=True)
class ScreenProfile:
width: int
height: int
avail_width: int
avail_height: int
dpr: float
tier: str
@dataclass(frozen=True)
class HardwareProfile:
concurrency: int
storage_quota_mb: int
@dataclass(frozen=True)
class AudioProfile:
sample_rate: int
output_latency_ms: int
max_channel_count: int
@dataclass(frozen=True)
class CodecProfile:
av1_enabled: bool
webm_encoder_enabled: bool
mediasource_webm: bool
mediasource_mp4: bool
webspeech_synth: bool
@dataclass(frozen=True)
class WebGLProfile:
msaa_samples: int
# ──────────────────────────────────────────────────────────────────────
# Pin map: flat dotted-path -> value. Set via `pin=` on generate_profile.
#
# Supported keys:
# "gpu.vendor", "gpu.renderer", "gpu.class_tier"
# "screen.width", "screen.height", "screen.avail_width",
# "screen.avail_height", "screen.dpr", "screen.tier"
# "hardware.concurrency", "hardware.storage_quota_mb"
# "audio.sample_rate", "audio.output_latency_ms",
# "audio.max_channel_count"
# "codec.av1_enabled", "codec.webm_encoder_enabled",
# "codec.mediasource_webm", "codec.mediasource_mp4",
# "codec.webspeech_synth"
# "webgl.msaa_samples"
# "fonts" (replaces the whole list)
# "dark_theme"
# ──────────────────────────────────────────────────────────────────────
_PIN_GROUPS = {
"gpu": {"vendor", "renderer", "class_tier"},
"screen": {"width", "height", "avail_width", "avail_height", "dpr", "tier"},
"hardware": {"concurrency", "storage_quota_mb"},
"audio": {"sample_rate", "output_latency_ms", "max_channel_count"},
"codec": {
"av1_enabled", "webm_encoder_enabled",
"mediasource_webm", "mediasource_mp4", "webspeech_synth",
},
"webgl": {"msaa_samples"},
}
_PIN_TOP = {"fonts", "dark_theme"}
def _validate_pin_key(key: str) -> None:
if key in _PIN_TOP:
return
if "." not in key:
raise ValueError(
f"pin key {key!r} is not valid. "
f"Use 'group.field' (e.g. 'screen.width') or one of {sorted(_PIN_TOP)}."
)
group, field_name = key.split(".", 1)
if group not in _PIN_GROUPS:
raise ValueError(
f"pin key {key!r}: unknown group {group!r}. "
f"Known groups: {sorted(_PIN_GROUPS)}."
)
if field_name not in _PIN_GROUPS[group]:
raise ValueError(
f"pin key {key!r}: unknown field {field_name!r} in group {group!r}. "
f"Known fields: {sorted(_PIN_GROUPS[group])}."
)
@dataclass(frozen=True)
class Profile:
"""Coherent browser fingerprint profile sampled from a single integer seed.
Use `generate_profile(seed)` to build one. Pin specific values at build
time with `generate_profile(seed, pin={"screen.width": 2560, ...})`.
"""
seed: int
gpu: GPUProfile
screen: ScreenProfile
hardware: HardwareProfile
audio: AudioProfile
codec: CodecProfile
webgl: WebGLProfile
fonts: List[str]
dark_theme: bool
# Bayesian browsing-history: list of {name, category, cookie_profile}
# dicts sampled from data/browsing_pool.json with per-class CPT. Used
# by _recaptcha_seed.py to build a coherent cookie pre-seed when the
# caller opts in via Stealthfox(prep_recaptcha=True).
browsing_history: List[Dict[str, str]] = field(default_factory=list)
_raw: Dict[str, Any] = field(default_factory=dict, repr=False, compare=False)
def to_prefs_dict(self) -> Dict[str, Any]:
"""Return the flat dict of raw sampler fields, as produced by the
underlying Bayesian sampler. Stable across releases for a given seed."""
return dict(self._raw)
# Mapping from flat pin key -> raw sampler dict key, so `to_prefs_dict()`
# and `invisible_playwright.prefs.translate_profile_to_prefs` observe the pinned value.
_PIN_TO_RAW = {
"gpu.vendor": "webgl_vendor",
"gpu.renderer": "webgl_renderer",
"gpu.class_tier": "gpu_class",
"screen.width": "screen_w",
"screen.height": "screen_h",
"screen.avail_width": "screen_avail_w",
"screen.avail_height": "screen_avail_h",
"screen.dpr": "dpr",
"screen.tier": "screen_tier",
"hardware.concurrency": "hw_concurrency",
"hardware.storage_quota_mb": "storage_quota_mb",
"audio.sample_rate": "audio_sample_rate",
"audio.output_latency_ms": "audio_output_latency_ms",
"audio.max_channel_count": "audio_max_channel_count",
"codec.av1_enabled": "av1_enabled",
"codec.webm_encoder_enabled": "webm_encoder_enabled",
"codec.mediasource_webm": "mediasource_webm",
"codec.mediasource_mp4": "mediasource_mp4",
"codec.webspeech_synth": "webspeech_synth",
"webgl.msaa_samples": "msaa_samples",
"dark_theme": "dark_theme",
# "fonts" is a list — handled specially (joined into font_whitelist).
}
def _apply_pins_to_raw(raw: Dict[str, Any], pin: Dict[str, Any]) -> Dict[str, Any]:
"""Return a copy of `raw` with the pinned sampler-level fields updated."""
out = dict(raw)
for key, value in pin.items():
if key == "fonts":
if not isinstance(value, (list, tuple)):
raise TypeError("pin 'fonts' must be a list/tuple of strings")
out["font_whitelist"] = ",".join(value)
continue
raw_key = _PIN_TO_RAW.get(key)
if raw_key is None:
# Shouldn't happen after validation, but guard anyway.
continue
out[raw_key] = value
return out
def generate_profile(
seed: int,
pin: Optional[Dict[str, Any]] = None,
fixed_gpu_class: Optional[str] = None,
) -> Profile:
"""Return a deterministic Profile for the given integer seed.
pin: optional dict of dotted-path keys (e.g. "screen.width", "gpu.renderer")
to values that are FORCED in the resulting profile. All other fields
are still sampled from the Bayesian network based on `seed`, so the
same seed + same pin map always yields the same profile.
Example force a specific GPU and screen while letting everything
else vary with the seed (via the public invisible_playwright API):
from invisible_playwright import InvisiblePlaywright
with InvisiblePlaywright(
seed=42,
pin={
"gpu.renderer": "ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 Direct3D11)",
"gpu.vendor": "Google Inc. (NVIDIA)",
"gpu.class_tier": "high_end",
"screen.width": 2560,
"screen.height": 1440,
},
) as browser:
...
Warning: pinning breaks Bayesian coherence across the pinned fields
(if you pin a high-end GPU but leave screen unpinned, you may get a
1080p screen that would be unusual for that GPU class). Pin related
fields together when coherence matters.
Supported keys: see the module-level _PIN_GROUPS / _PIN_TOP tables
or run `help(generate_profile)` after import.
"""
if pin:
for key in pin:
_validate_pin_key(key)
# fixed_gpu_class re-conditions the whole bundle on a chosen class (used so the
# bundle stays coherent with the validated WebGL persona we expose on Windows/mac).
# An explicit gpu.class_tier pin still wins.
eff_class = (pin or {}).get("gpu.class_tier") or fixed_gpu_class
raw = _sample_raw(int(seed), fixed_gpu_class=eff_class)
if pin:
raw = _apply_pins_to_raw(raw, pin)
# Font whitelist is stored as a comma-separated string in raw; split it.
font_wl = raw.get("font_whitelist", "")
if isinstance(font_wl, str):
fonts = [f.strip() for f in font_wl.split(",") if f.strip()]
else:
fonts = list(font_wl) if font_wl else []
return Profile(
seed=int(raw["stealth_seed"]),
gpu=GPUProfile(
vendor=raw["webgl_vendor"],
renderer=raw["webgl_renderer"],
class_tier=raw["gpu_class"],
),
screen=ScreenProfile(
width=int(raw["screen_w"]),
height=int(raw["screen_h"]),
avail_width=int(raw["screen_avail_w"]),
avail_height=int(raw["screen_avail_h"]),
dpr=float(raw["dpr"]),
tier=str(raw.get("screen_tier", "")),
),
hardware=HardwareProfile(
concurrency=int(raw["hw_concurrency"]),
storage_quota_mb=int(raw["storage_quota_mb"]),
),
audio=AudioProfile(
sample_rate=int(raw["audio_sample_rate"]),
output_latency_ms=int(raw["audio_output_latency_ms"]),
max_channel_count=int(raw["audio_max_channel_count"]),
),
codec=CodecProfile(
av1_enabled=bool(raw["av1_enabled"]),
webm_encoder_enabled=bool(raw["webm_encoder_enabled"]),
mediasource_webm=bool(raw["mediasource_webm"]),
mediasource_mp4=bool(raw["mediasource_mp4"]),
webspeech_synth=bool(raw["webspeech_synth"]),
),
webgl=WebGLProfile(msaa_samples=int(raw["msaa_samples"])),
fonts=fonts,
dark_theme=bool(raw["dark_theme"]),
browsing_history=list(raw.get("browsing_history") or []),
_raw=raw,
)

View file

@ -1,268 +1,4 @@
"""Resolve the session timezone from the egress IP (``timezone="auto"``).
Approach B: discover the egress IP with one HTTP request routed *through the
proxy* when one is set, otherwise a direct request that sees the host's own
public IP then map IP IANA timezone with an offline mmdb
(``daijro/geoip-all-in-one``, downloaded + cached by ``download.py``).
Precedence (see ``resolve_session_timezone``):
explicit IANA unchanged explicit always wins
"" / "auto" egress ALWAYS resolve. With a proxy, from the proxy
egress IP; without a proxy, from the host's
own public IP. This is the default.
On failure:
with a proxy raise a foreign proxy paired with the host TZ is
the precise ``timezone_mismatch`` signal, so
we fail loudly rather than fall back silently.
without a proxy "" (host) the host TZ is a safe default, so a transient
lookup failure must not break the launch.
"""
from __future__ import annotations
import ipaddress
from typing import Any, Dict, NamedTuple, Optional
from urllib.parse import quote
import requests
class GeoTimezoneError(RuntimeError):
"""Raised when ``timezone="auto"`` cannot resolve a valid IANA zone."""
# Plain-text IP echo endpoints (each returns just the caller's public IP).
_IP_ECHO_ENDPOINTS = (
"https://api.ipify.org",
"https://icanhazip.com",
"https://checkip.amazonaws.com",
)
_SOCKS_SCHEMES = ("socks5://", "socks4://", "socks://")
def _proxy_is_set(proxy: Optional[Dict[str, str]]) -> bool:
if not proxy:
return False
server = (proxy.get("server") or "").strip()
return bool(server) and server.lower() != "direct://"
def _proxies_for_requests(proxy: Dict[str, str]) -> Dict[str, str]:
"""Translate our proxy dict into a ``requests`` proxies mapping.
SOCKS5 uses the ``socks5h`` scheme so DNS is resolved proxy-side (matches
``network.proxy.socks_remote_dns=True`` in the Firefox path). HTTP/HTTPS
pass through unchanged. Credentials are URL-encoded.
"""
server = (proxy.get("server") or "").strip()
low = server.lower()
if low.startswith("socks5://") or low.startswith("socks://"):
scheme = "socks5h"
elif low.startswith("socks4://"):
scheme = "socks4"
elif low.startswith("https://"):
scheme = "https"
else:
scheme = "http"
host_port = server.split("://", 1)[1] if "://" in server else server
user = proxy.get("username") or ""
pwd = proxy.get("password") or ""
if user:
auth = f"{quote(user, safe='')}:{quote(pwd, safe='')}@"
else:
auth = ""
url = f"{scheme}://{auth}{host_port}"
return {"http": url, "https": url}
def discover_egress_ip(
proxy: Optional[Dict[str, str]] = None, *, timeout: float = 10.0
) -> str:
"""Return the public egress IP.
Routes the request through ``proxy`` when given (SOCKS support requires
``requests[socks]`` / PySocks); with ``proxy=None`` it makes a direct
request that sees the host's own public IP. Tries each echo endpoint in
turn; raises :class:`GeoTimezoneError` if none return a valid IP.
"""
proxies = _proxies_for_requests(proxy) if proxy else None
last_err: Optional[Exception] = None
for url in _IP_ECHO_ENDPOINTS:
try:
resp = requests.get(url, proxies=proxies, timeout=timeout)
resp.raise_for_status()
ip = resp.text.strip()
ipaddress.ip_address(ip) # validate (raises ValueError if not an IP)
return ip
except Exception as exc: # noqa: BLE001 - try the next endpoint
last_err = exc
continue
raise GeoTimezoneError(
f"could not discover the proxy egress IP via {len(_IP_ECHO_ENDPOINTS)} "
f"endpoints (last error: {last_err!r}). For SOCKS proxies make sure "
f"requests[socks] / PySocks is installed."
)
def ip_to_timezone(ip: str, mmdb_path: Any) -> str:
"""Map ``ip`` to its IANA timezone using the offline mmdb.
Reads the standard MaxMind ``location.time_zone`` field and validates it
against the system tz database. Raises :class:`GeoTimezoneError` if the IP
is absent from the DB or the zone is missing / not a valid IANA name.
"""
import maxminddb
with maxminddb.open_database(str(mmdb_path)) as reader:
record = reader.get(ip)
if not record:
raise GeoTimezoneError(f"egress IP {ip} not present in the geoip database")
tz = ((record.get("location") or {}) if isinstance(record, dict) else {}).get(
"time_zone"
)
if not tz:
raise GeoTimezoneError(f"no timezone for egress IP {ip} in the geoip database")
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
try:
ZoneInfo(tz)
except (ZoneInfoNotFoundError, ValueError) as exc:
raise GeoTimezoneError(
f"geoip returned an invalid IANA zone {tz!r} for {ip}: {exc}"
) from exc
return tz
# ISO 3166 country code -> the primary BCP-47 locale a real Windows machine in that
# country most commonly runs. Multi-language countries use the majority language; the
# user can always force a specific locale instead of "auto". Unknown -> en-US.
_COUNTRY_LOCALE = {
"US": "en-US", "GB": "en-GB", "CA": "en-CA", "AU": "en-AU", "NZ": "en-NZ", "IE": "en-IE",
"ZA": "en-ZA", "IN": "en-IN", "SG": "en-SG", "PH": "en-PH",
"FR": "fr-FR", "BE": "fr-BE", "LU": "fr-LU",
"DE": "de-DE", "AT": "de-AT", "CH": "de-CH",
"IT": "it-IT", "ES": "es-ES", "PT": "pt-PT", "NL": "nl-NL",
"SE": "sv-SE", "NO": "nb-NO", "DK": "da-DK", "FI": "fi-FI", "IS": "is-IS",
"PL": "pl-PL", "CZ": "cs-CZ", "SK": "sk-SK", "HU": "hu-HU", "RO": "ro-RO",
"GR": "el-GR", "BG": "bg-BG", "HR": "hr-HR", "RS": "sr-RS", "SI": "sl-SI",
"RU": "ru-RU", "UA": "uk-UA", "TR": "tr-TR", "IL": "he-IL",
"BR": "pt-BR", "MX": "es-MX", "AR": "es-AR", "CL": "es-CL", "CO": "es-CO", "PE": "es-PE",
"JP": "ja-JP", "KR": "ko-KR", "CN": "zh-CN", "TW": "zh-TW", "HK": "zh-HK",
"ID": "id-ID", "TH": "th-TH", "VN": "vi-VN", "MY": "ms-MY",
"SA": "ar-SA", "AE": "ar-AE", "EG": "ar-EG",
}
def ip_to_locale(ip: str, mmdb_path: Any) -> str:
"""Map ``ip`` -> a BCP-47 locale via the MaxMind ``country.iso_code`` field, so the
browser language stays consistent with the proxy egress country. Falls back to
``en-US`` for IPs absent from the DB or countries we don't map."""
import maxminddb
with maxminddb.open_database(str(mmdb_path)) as reader:
record = reader.get(ip)
cc = ""
if isinstance(record, dict):
cc = ((record.get("country") or {}).get("iso_code") or "")
return _COUNTRY_LOCALE.get(cc.upper(), "en-US")
def resolve_session_locale(egress_ip: Optional[str], proxy: Optional[Dict[str, str]]) -> str:
"""Resolve ``locale="auto"`` to a BCP-47 locale from the egress country. Behind a proxy
it reuses the already-discovered ``egress_ip`` (no extra round-trip); without a proxy it
discovers the host's public IP. On any failure it returns ``en-US`` (never breaks launch
locale is cosmetic, unlike timezone which traps a foreign-proxy mismatch)."""
from .download import ensure_geoip_mmdb
try:
ip = egress_ip if _proxy_is_set(proxy) else discover_egress_ip(None)
if ip is None:
return "en-US"
return ip_to_locale(ip, ensure_geoip_mmdb())
except Exception: # noqa: BLE001
return "en-US"
class SessionGeo(NamedTuple):
"""Geo facts resolved once per session from a single egress round-trip.
``timezone`` follows the precedence in the module docstring.
``egress_ip`` is the proxy egress IP (the IP the *outside world* sees) when
a proxy is set, else ``None`` it feeds the WebRTC srflx override, which is
only meaningful behind a proxy (a direct connection's real STUN already
reports the truthful public IP, so we leave it alone).
"""
timezone: str
egress_ip: Optional[str]
def prepare_session_geo(
timezone: str, proxy: Optional[Dict[str, str]]
) -> SessionGeo:
"""Resolve the session timezone AND the proxy egress IP in ONE round-trip.
The egress IP is discovered once and reused for both the timezone mapping
(when ``timezone`` is ``""``/``"auto"``) and the WebRTC public-IP override.
Timezone precedence is identical to :func:`resolve_session_timezone`; the
egress IP is best-effort for the WebRTC side (a discovery failure that the
timezone path doesn't need won't break the launch but if the timezone
path *does* need it behind a proxy, that path still fails loudly).
"""
from .download import ensure_geoip_mmdb
tz = (timezone or "").strip()
proxy_set = _proxy_is_set(proxy)
# One discovery, reused below. Behind a proxy we always want the egress IP
# (for WebRTC) regardless of the timezone setting.
egress_ip: Optional[str] = None
egress_err: Optional[Exception] = None
if proxy_set:
try:
egress_ip = discover_egress_ip(proxy)
except Exception as exc: # noqa: BLE001
egress_err = exc
# Timezone resolution — same precedence as resolve_session_timezone.
if tz and tz.lower() != "auto":
return SessionGeo(tz, egress_ip) # explicit IANA wins
try:
ip = egress_ip if proxy_set else discover_egress_ip(None)
if ip is None: # proxy set but discovery failed above
raise egress_err or GeoTimezoneError("egress IP discovery failed")
return SessionGeo(ip_to_timezone(ip, ensure_geoip_mmdb()), egress_ip)
except Exception:
if proxy_set:
raise # fail-early behind a proxy (timezone_mismatch trap)
return SessionGeo("", None) # no proxy: host TZ is a safe fallback
def resolve_session_timezone(
timezone: str, proxy: Optional[Dict[str, str]]
) -> str:
"""Map the user's ``timezone`` setting to a concrete IANA zone (or ``""``).
Timezone-only path (no WebRTC side effects): an explicit IANA zone wins and
triggers NO network call; ``""``/``"auto"`` resolve from the egress IP. The
launch path uses :func:`prepare_session_geo` instead (which additionally
returns the egress IP for WebRTC); this standalone resolver is kept for
third-party integrations that only want the zone. See the module docstring
for the precedence table.
"""
tz = (timezone or "").strip()
if tz and tz.lower() != "auto":
return tz # explicit IANA wins — no egress lookup
from .download import ensure_geoip_mmdb
proxy_set = _proxy_is_set(proxy)
try:
ip = discover_egress_ip(proxy if proxy_set else None)
return ip_to_timezone(ip, ensure_geoip_mmdb())
except Exception:
if proxy_set:
raise # fail-early behind a proxy (timezone_mismatch trap)
return "" # no proxy: host TZ is a safe fallback
"""Backward-compat shim — spostato in invisible_core._geo (alias completo)."""
import sys as _sys
from invisible_core import _geo as _mod
_sys.modules[__name__] = _mod

View file

@ -1,178 +1,4 @@
"""Invisible-but-headed browser windows.
Playwright's ``headless=True`` flips Firefox onto a different code path —
no widget tree, software-only rendering, distinct timing and anti-bot
systems can spot the divergence. Running the browser *headed* but hidden
gives us the real rendering pipeline while keeping the windows off screen.
Two mechanisms, by platform:
- **Windows & macOS**: the patched binary cloaks its OWN chrome windows
when ``zoom.stealth.cloak_windows`` is set ``DWMWA_CLOAK`` (Windows)
/ ``NSWindow`` alpha-0 + pinned occlusion-ignore (macOS). The window
renders on the real GPU but never appears on screen, in the taskbar or
the Dock. The launcher injects the pref; nothing host-side is spawned.
- **Linux**: spawns its own ``Xvfb`` instance and points ``DISPLAY`` at
it (X11/Wayland have no per-window cloak that keeps the GPU rendering).
"""
from __future__ import annotations
import os
import subprocess
import sys
import time
from typing import Optional
# Inherited from WSLg / GNOME / etc. these env vars make Firefox prefer a
# Wayland compositor over the X11 DISPLAY we set, so the window leaks onto
# the real desktop. Strip them all before starting.
_WAYLAND_LEAK_VARS = (
"WAYLAND_DISPLAY",
"XDG_RUNTIME_DIR",
"XDG_SESSION_TYPE",
"PULSE_SERVER",
"WSL2_GUI_APPS_ENABLED",
)
class _LinuxVirtualDisplay:
"""Standalone Xvfb instance owned by this InvisiblePlaywright session."""
def __init__(self, width: int = 1920, height: int = 1080) -> None:
self._geometry = f"{width}x{height}x24"
self._proc: Optional[subprocess.Popen] = None
self._display: Optional[str] = None
self._saved_env: dict[str, Optional[str]] = {}
def start(self) -> None:
if not _binary_on_path("Xvfb"):
raise RuntimeError(
"invisible_playwright headless=True requires Xvfb. "
"Install it: sudo apt install xvfb"
)
# Retry: when many workers start in parallel they can pick the same
# display number before any has created its lockfile. Xvfb on the
# losing side exits immediately — try again with a fresh number.
last_err: Optional[Exception] = None
for _ in range(10):
display = self._pick_display()
try:
self._spawn(display)
self._wait_until_ready(display)
self._display = display
self._apply_env(display)
return
except RuntimeError as e:
last_err = e
if self._proc is not None and self._proc.poll() is None:
self._proc.kill()
self._proc = None
raise RuntimeError(f"Xvfb failed to start after 10 attempts: {last_err}")
def _spawn(self, display: str) -> None:
self._proc = subprocess.Popen(
[
"Xvfb", display,
"-screen", "0", self._geometry,
"+extension", "GLX",
"+extension", "RENDER",
"-nolisten", "unix",
"-listen", "tcp",
"-ac",
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
)
def _pick_display(self) -> str:
for n in range(99, 400):
if not os.path.exists(f"/tmp/.X{n}-lock"):
return f":{n}"
raise RuntimeError("no free X display number in :99:399")
def _wait_until_ready(self, display: str) -> None:
# We start Xvfb with -nolisten unix → no /tmp/.X11-unix socket appears.
# Xvfb creates /tmp/.X{n}-lock immediately though — wait for that.
lockfile = f"/tmp/.X{display[1:]}-lock"
deadline = time.monotonic() + 3.0
assert self._proc is not None
while time.monotonic() < deadline:
if self._proc.poll() is not None:
raise RuntimeError(f"Xvfb {display} exited immediately")
if os.path.exists(lockfile):
return
time.sleep(0.02)
raise RuntimeError(f"Xvfb {display} did not become ready in 3s")
def _apply_env(self, display: str) -> None:
keys = ("DISPLAY", "MOZ_ENABLE_WAYLAND", "GDK_BACKEND") + _WAYLAND_LEAK_VARS
for k in keys:
self._saved_env[k] = os.environ.get(k)
for k in _WAYLAND_LEAK_VARS:
os.environ.pop(k, None)
os.environ["DISPLAY"] = display
os.environ["MOZ_ENABLE_WAYLAND"] = "0"
os.environ["GDK_BACKEND"] = "x11"
def stop(self) -> None:
for k, v in self._saved_env.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
self._saved_env.clear()
if self._proc is not None and self._proc.poll() is None:
self._proc.terminate()
try:
self._proc.wait(timeout=3)
except subprocess.TimeoutExpired:
self._proc.kill()
self._proc.wait(timeout=2)
self._proc = None
self._display = None
# Windows & macOS: the patched Firefox cloaks its own chrome windows when this
# pref is set (DWMWA_CLOAK / NSWindow alpha-0 + pinned occlusion-ignore), so the
# window renders on the real GPU but never shows on screen / in the taskbar or
# Dock. window_occlusion_tracking is disabled so a hidden window keeps painting.
CLOAK_PREFS = {
"zoom.stealth.cloak_windows": True,
"widget.windows.window_occlusion_tracking.enabled": False,
}
def cloak_prefs() -> dict:
"""Prefs that make the patched binary self-cloak its chrome windows.
Used on Windows & macOS, where hiding is done inside the binary rather than
with a host-side virtual display.
"""
return dict(CLOAK_PREFS)
def make_virtual_display():
"""Return a start()/stop()-able virtual display, or ``None`` when the
platform hides windows via the in-binary cloak pref instead.
- Linux: a fresh ``Xvfb`` (the launcher start()s/stop()s it).
- Windows / macOS: ``None`` the binary self-cloaks via ``cloak_prefs()``,
injected by the launcher; nothing host-side needs spawning.
"""
if sys.platform.startswith("linux"):
return _LinuxVirtualDisplay()
if sys.platform in ("win32", "darwin"):
return None
raise RuntimeError(
f"invisible_playwright supports Windows, macOS and Linux "
f"(got {sys.platform!r})"
)
def _binary_on_path(name: str) -> bool:
import shutil
return shutil.which(name) is not None
"""Backward-compat shim — spostato in invisible_core._headless (alias completo)."""
import sys as _sys
from invisible_core import _headless as _mod
_sys.modules[__name__] = _mod

View file

@ -1,56 +1,4 @@
"""Proxy translation shared by sync and async launchers.
SOCKS proxies are driven entirely by the patched Firefox prefs (the
``nsProtocolProxyService`` patch reads ``network.proxy.socks_username``
and ``socks_password``). HTTP/HTTPS proxies go through Playwright's own
``proxy=`` kwarg so it can negotiate Basic auth.
"""
from __future__ import annotations
from typing import Any, Dict, Optional
_SOCKS_SCHEMES = ("socks5://", "socks4://", "socks://")
def configure_proxy(
proxy: Optional[Dict[str, str]],
prefs: Dict[str, Any],
) -> Optional[Dict[str, str]]:
"""Mutate ``prefs`` for SOCKS auth; return what to pass to Playwright.
* ``None`` proxy returns ``None``.
* SOCKS proxy writes the auth prefs and returns ``None`` (Playwright
gets nothing; Firefox does the rest).
* HTTP / HTTPS proxy returns the dict unchanged for Playwright.
"""
if not proxy:
return None
server = (proxy.get("server") or "").strip()
if not server or server.lower() == "direct://":
return None
if not _is_socks_scheme(server):
return proxy
host_port = _strip_scheme(server)
if ":" not in host_port:
return None # malformed, drop silently
host, port_str = host_port.rsplit(":", 1)
prefs["network.proxy.type"] = 1
prefs["network.proxy.socks"] = host
prefs["network.proxy.socks_port"] = int(port_str)
prefs["network.proxy.socks_version"] = 4 if server.lower().startswith("socks4://") else 5
prefs["network.proxy.socks_username"] = proxy.get("username") or ""
prefs["network.proxy.socks_password"] = proxy.get("password") or ""
prefs["network.proxy.socks_remote_dns"] = True
return None
def _is_socks_scheme(server: str) -> bool:
return server.lower().startswith(_SOCKS_SCHEMES)
def _strip_scheme(server: str) -> str:
return server.split("://", 1)[1] if "://" in server else server
"""Backward-compat shim — spostato in invisible_core._proxy (alias completo)."""
import sys as _sys
from invisible_core import _proxy as _mod
_sys.modules[__name__] = _mod

View file

@ -1,202 +1,4 @@
"""Empirically-calibrated WebGL GPU personas for Windows ANGLE D3D11.
We expose a FALSE GPU (this is a multi-user tool never leak each host's real GPU),
chosen deterministically per seed from a small set of renderer-string "buckets" that
Firefox's SanitizeRenderer emits and that FP Pro's tampering_ml scores as CLEAN.
## What actually gates a persona (calibrated 2026-06-14, supersedes the old theory)
The blocker is NOT anti_detect and NOT a "render-vs-renderer" check. It is FP Pro's
**tampering_ml** (gate <=0.5), a holistic ML coherence score. We reverse-engineered its
GPU sensitivity with single-variable A/Bs on demo.fingerprint.com (deterministic per
(seed, renderer, IP); tools in tests/_gpu_isolate.py / _gpu_landscape.py / _gpu_sweep.py /
_gpu_sweep2.py / _gpu_persona_pure.py). Findings:
1. tampering_ml = f(renderer STRING, seed baseline = canvas/audio). The renderer string
carries a STABLE per-bucket penalty; the seed sets the floor it adds to.
2. gpu_class is IRRELEVANT to tampering_ml (nv_980 scored identically on mid_range /
high_end / premium / workstation). So pairing a fake GPU with a "matching" hardware
tier does NOT help the score (we still set a coherent class see gpu_class below
for OTHER detectors that cross-check cores/screen, just not for this).
3. It is NOT render-consistency: a cross-vendor AMD string is CLEAN on our Intel-Arc
host. So the real silicon's pixels are not the dominant signal; falsifying to a
different vendor works IF the string is one FP Pro scores low.
Sweep over all 10 Windows SanitizeRenderer buckets x 10 seeds (clean = tml<=0.5 AND not
anti_detect), on our Intel Arc A750 host:
- amd_r9 (Radeon R9 200 Series) ...... 10/10 clean, max tml 0.346 <- SHIP
- intel_arc (Arc A750) ............... 10/10 clean, max tml 0.377 <- SHIP
- amd_hd5850 ......................... 9/10 (fails the hardest seed)
- amd_hd3200 / intel_hd .............. 6/10 (seed-dependent, risky)
- intel_hd400 ........................ 3/10
- ALL NVIDIA (8800/480/980) .......... 0/10 (penalized everywhere, ~0.7-0.99)
- intel_945 (ancient Intel) .......... 0/10
So only TWO buckets are robustly clean across profiles. We ship exactly those, weighted
to real-world prevalence ("Radeon R9 200 Series" is the bucket for ALL modern AMD = a big
real slice; "Arc A750" covers Intel discrete = rarer). Cross-vendor, so the fleet is not a
single-GPU cluster. More names require lowering the seed floor first (see CAVEAT 2).
## ⚠️ CAVEATS
1. HOST-INDEPENDENCE NOT PROVEN. Everything above was measured on ONE host (Intel Arc
A750). The host's real render is embedded in the seed baseline, so the clean-bucket set
*might* be host-dependent (on a real NVIDIA host, maybe nv_980 is clean and amd_r9 is
not). This MUST be validated on a non-Arc machine before trusting it fleet-wide; if it
turns out host-dependent, add a pre-launch host-GPU-class probe and pick a bucket per
detected class. Until then: safe for Arc hosts (incl. the dev's), unvalidated elsewhere.
2. DIVERSITY CEILING = 2 names because "hard" seeds (high canvas/audio floor, e.g. seed 4
~0.35) only stay clean on the 2 best buckets. Lowering that floor (an fpforge CPT fix
candidate: 8-channel audio + 1TB storage emitted on a mid_range profile) would unlock
amd_hd5850 / intel_hd for more seeds => up to ~5 names. Follow-up, not done yet.
## Load-bearing format requirements (unchanged, still true)
- renderer MUST end ", D3D11)" (full ANGLE wire format) or SanitizeRenderer returns
"Generic Renderer" (a tell). The C++ passes our string through SanitizeRenderer, which
buckets "AMD Radeon R9 200 Series" -> "Radeon R9 200 Series" and "Arc A750" -> itself.
- the forced extension list MUST be the EXACT NATIVE ORDER getSupportedExtensions returns.
The set+order is fixed by Firefox+ANGLE on D3D11 FL11_0 (VENDOR-INDEPENDENT verified
via 20-agent source study), so ONE list is correct for both personas. A reorder is caught
(tampering_ml 0.34 -> 0.84). The lists below are the verbatim native-order Arc capture.
Calibration data + sweep tooling live in the local workbench (not shipped).
"""
from __future__ import annotations
import sys
from typing import Dict, List, Optional
# Vendor-independent ext lists (native order, Arc host capture). Identical for every persona
# because the set+order is fixed by Firefox+ANGLE on D3D11 FL11_0, not by the GPU vendor.
_EXT1 = (
"ANGLE_instanced_arrays,EXT_blend_minmax,EXT_color_buffer_half_float,EXT_float_blend,"
"EXT_frag_depth,EXT_shader_texture_lod,EXT_sRGB,EXT_texture_compression_bptc,"
"EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_element_index_uint,"
"OES_fbo_render_mipmap,OES_standard_derivatives,OES_texture_float,OES_texture_float_linear,"
"OES_texture_half_float,OES_texture_half_float_linear,OES_vertex_array_object,"
"WEBGL_color_buffer_float,WEBGL_compressed_texture_s3tc,WEBGL_compressed_texture_s3tc_srgb,"
"WEBGL_debug_renderer_info,WEBGL_debug_shaders,WEBGL_depth_texture,WEBGL_draw_buffers,"
"WEBGL_lose_context,WEBGL_provoking_vertex"
)
_EXT2 = (
"EXT_color_buffer_float,EXT_float_blend,EXT_texture_compression_bptc,"
"EXT_texture_compression_rgtc,EXT_texture_filter_anisotropic,OES_draw_buffers_indexed,"
"OES_texture_float_linear,OVR_multiview2,WEBGL_compressed_texture_s3tc,"
"WEBGL_compressed_texture_s3tc_srgb,WEBGL_debug_renderer_info,WEBGL_debug_shaders,"
"WEBGL_lose_context,WEBGL_provoking_vertex"
)
# ── Real-Firefox GPU pool (2026-06-18, supersedes the 2-bucket sweep above) ───────────────
# The personas are now sourced from `_fpforge/data/webgl_gpu_pool.json` — an OFFLINE extract
# of camoufox's real-Firefox WebGL telemetry DB (17 Windows GPUs with their REAL per-OS
# prevalence AND the full coherent WebGL fingerprint: renderer + vendor + extensions +
# ~100 getParameter values + shader-precision formats). prefs.py applies ALL of these, not
# just the renderer string. The linchpin A/B (2026-06-18) proved that the OLD "NVIDIA 0/10"
# verdict was an artifact of spoofing the renderer string over the host's REAL (Arc) params:
# FP Pro cross-checks renderer<->params, so a GTX 980 string over Arc params mismatched
# (~0.7-0.85). Injecting camoufox's REAL GTX 980 params makes it coherent (tml median 0.333,
# flags clean). So the params are NOT vendor-independent (the old assumption) and per-GPU
# real data is what unlocks the full real GPU mix — including NVIDIA (~47% of real FF-Win),
# which we no longer gate.
_ENABLED = True
_POOL_PATH = __import__("pathlib").Path(__file__).parent / "_fpforge" / "data" / "webgl_gpu_pool.json"
_GPU_POOL_CACHE: Optional[List[Dict]] = None
def _gpu_pool() -> List[Dict]:
"""Lazy-load the Windows GPU pool (we always claim Windows). Each entry:
{key, renderer (input form), vendor, gpu_class (via classify_gpu), prefs (full
zoom.stealth.webgl.* override dict), weight (real per-OS prevalence)}."""
global _GPU_POOL_CACHE
if _GPU_POOL_CACHE is not None:
return _GPU_POOL_CACHE
import json
from ._fpforge._sampler import classify_gpu # lazy import → no module cycle
raw = json.loads(_POOL_PATH.read_text(encoding="utf-8"))
pool: List[Dict] = []
for e in raw.get("win", []):
prefs = e["prefs"]
rend_in = prefs["zoom.stealth.webgl.renderer"]
cls = classify_gpu({"renderer": rend_in,
"vendor": prefs.get("zoom.stealth.webgl.vendor", "")})
pool.append({
"key": e.get("renderer_out", rend_in)[:48],
"renderer": rend_in,
"vendor": prefs["zoom.stealth.webgl.vendor"],
"gpu_class": cls,
"prefs": prefs,
"weight": float(e["prob"]),
})
_GPU_POOL_CACHE = pool
return pool
def select_persona(seed: int) -> Optional[Dict]:
"""Deterministic, prevalence-weighted GPU persona for this seed — on EVERY host.
Same seed -> same persona (fppro_consistency: identity stable per seed). Different seeds
spread across the REAL Windows GPU mix by prevalence. Returns the Windows-ANGLE persona on
Linux/Mac too: we must always look Windows, and the C++ WebGL override (SanitizeRenderer +
pref-driven params/extensions) is platform-independent, so the same Windows GPU is presented
on any host without consulting the real GL backend (no more Linux "Generic Renderer")."""
if not _ENABLED:
return None
pool = _gpu_pool()
if not pool:
return None
total = sum(p["weight"] for p in pool) or 1.0
h = ((int(seed) * 2654435761) % 1_000_003) / 1_000_003.0 * total
cum = 0.0
for p in pool:
cum += p["weight"]
if h < cum:
return p
return pool[-1]
def forced_gpu_class(seed: int) -> Optional[str]:
"""The gpu_class the forge conditions the bundle on (== the selected GPU's class via
classify_gpu), so cores/screen/fonts stay coherent with the GPU we expose. Does NOT
affect FP Pro tampering_ml (proven) but matters for detectors that cross-check hardware
tier. None on Linux."""
p = select_persona(seed)
return p["gpu_class"] if p else None
# ── Render-noise seed pool (canvas/WebGL gamma) ──────────────────────────────
# zoom.stealth.fpp.hw_seed drives the per-seed canvas2D + WebGL readPixels gamma
# LUT in C++. The render-image HASH it produces is the DOMINANT FP Pro tampering_ml
# driver (proven 2026-06-14: holding a fixed profile and varying ONLY hw_seed moved
# tml 0.25->0.75). The monotonic gamma preserves the GPU's render structure, so some
# hw_seeds yield a "suspicious" render hash. We therefore DECOUPLE the render-noise
# seed from the identity seed and pick from a calibrated pool of hw_seeds that score
# CLEAN even on the hardest attribute profile (sweep 1..30 vs the worst seed: these
# 14 all gave tml<=0.285). Diversity is preserved (14 distinct render hashes spread
# across the population — real GPUs cluster to few canvas hashes anyway); identity
# stays per-seed (the rest of the fingerprint differs). Same seed -> same render seed
# (fppro_consistency holds).
# CAVEAT: the render hash = f(host GPU render, gamma), so this pool is calibrated on
# the Intel-Arc host. On other GPUs the clean set may differ (host-independence open,
# same as the personas) — Option B (substitution = GPU-independent render hash) would
# remove that dependency. Validate per-host or move to B before trusting fleet-wide.
# RECALIBRATED 2026-06-18 for the real-Firefox GPU mix (incl NVIDIA, which is more
# consistency-sensitive than the old amd/arc personas). Swept hw_seed 0..30 on the hottest
# persona (NVIDIA GTX 980) through a residential exit: these 9 stayed well within the clean
# band with a wide margin to the rest. The old pool's picks scored dirty on NVIDIA (clean
# only on the retired amd/arc mix) → dropped. NVIDIA is the worst case, so these are clean on
# amd/intel too. hw_seed = the canvas/WebGL gamma render hash (the dominant consistency-score
# driver); host-calibrated.
# 2026-06-21: with WebGL Option B (zoom.stealth.webgl.substitute_pixels, ON in prefs.py) the WebGL
# render hash is hash(seed,idx) = HOST-INDEPENDENT, so this list NO LONGER needs per-host calibration
# — it only supplies per-session diversity. A 2026-06-21 attempt to re-calibrate it per-host FAILED
# cross-OS: hw_seed clean on Windows went dirty on the Linux GL backend (b008 0.034->0.839; Win-dirty
# {7,11,20,27} = Linux-clean and vice-versa; + identity×hw_seed interaction on Linux). That proved
# calibration can't work cross-host → substitution replaces it. Kept the original diverse 9-set.
CLEAN_RENDER_SEEDS = [0, 5, 6, 9, 11, 16, 19, 20, 28]
def render_noise_seed(seed: int) -> int:
"""Deterministic clean render-noise seed for hw_seed (decoupled from identity).
Maps the identity seed into CLEAN_RENDER_SEEDS so every session gets a calibrated
clean canvas/WebGL render hash while keeping per-user diversity. Stable per seed."""
return CLEAN_RENDER_SEEDS[(int(seed) * 2654435761) % len(CLEAN_RENDER_SEEDS)]
"""Backward-compat shim — spostato in invisible_core._webgl_personas (alias completo)."""
import sys as _sys
from invisible_core import _webgl_personas as _mod
_sys.modules[__name__] = _mod

View file

@ -1,112 +1,4 @@
"""Public helpers for building Firefox launch config without using ``InvisiblePlaywright``.
Use these when you need to call ``playwright.firefox.launch()`` (or
``firefox.launch_persistent_context()``) directly with our patched binary
and stealth prefs, instead of using the ``InvisiblePlaywright`` context
manager.
Typical caller is an external integration that owns its own browser
lifecycle (a Crawlee/Skyvern/changedetection-style fetcher, a Playwright
Server wrapper, a multi-language harness) and just wants the building
blocks::
from playwright.async_api import async_playwright
from invisible_playwright import ensure_binary, get_default_stealth_prefs
async with async_playwright() as p:
browser = await p.firefox.launch(
executable_path=str(ensure_binary()),
firefox_user_prefs=get_default_stealth_prefs(seed=42),
)
For everyday Python usage the ``InvisiblePlaywright`` context manager is
still the recommended entry point; these helpers expose the same internals
without the lifecycle ownership.
.. note::
When calling ``firefox.launch()`` yourself, pass ``headless=False`` and
manage the display hiding (Xvfb on Linux, hidden desktop on Windows)
externally. Passing ``headless=True`` directly to Playwright puts
Firefox in true headless mode, which skips the real rendering pipeline
and breaks canvas / audio / WebGL fingerprint coherence. The
``InvisiblePlaywright`` context manager does this translation
automatically; the public helpers leave it to the caller.
"""
from __future__ import annotations
import secrets
from typing import Any, Dict, List, Optional, Union
from ._fpforge import generate_profile
from ._webgl_personas import forced_gpu_class
from .prefs import translate_profile_to_prefs
def get_default_stealth_prefs(
seed: Optional[int] = None,
*,
pin: Optional[Dict[str, Any]] = None,
locale: str = "en-US",
timezone: str = "",
extra_prefs: Optional[Dict[str, Any]] = None,
humanize: Union[bool, float] = True,
virtual_display: bool = False,
) -> Dict[str, Any]:
"""Build a complete ``firefox_user_prefs`` dict for ``firefox.launch()``.
Same prefs that ``InvisiblePlaywright(seed=..., locale=..., timezone=...,
extra_prefs=..., humanize=...)`` would inject. Use this when you need to
drive ``playwright.firefox.launch()`` yourself.
Args:
seed: Integer seed for the Bayesian fingerprint sampler. Same seed
produces the same fingerprint. ``None`` generates a fresh
random int31 (matches ``InvisiblePlaywright`` default).
pin: Optional dict forcing specific fingerprint fields while the
rest stays seed-derived. See ``docs/pinning.md``.
locale: BCP-47 tag (e.g. ``"en-US"``). Drives ``Accept-Language``
and ``navigator.language``.
timezone: IANA timezone (e.g. ``"America/New_York"``). Empty means
use the host TZ. This pure pref builder does NOT resolve
``"auto"`` (that needs the proxy + a network lookup at launch
time) pass a concrete zone here, or use ``InvisiblePlaywright``
/ ``resolve_session_timezone(timezone, proxy)`` for ``"auto"``.
extra_prefs: Optional dict overlaid LAST onto the generated prefs.
humanize: When True (default), every mouse move is expanded into
a Bezier trajectory by the patched Juggler. A float caps the
motion in seconds. False disables the behavior.
virtual_display: When True on Windows, apply GPU-disabling prefs
to prevent GPU process crashes on virtual desktops without
D3D11 backend.
Returns:
Dict ready to pass as ``firefox_user_prefs=`` to
``playwright.firefox.launch()`` or ``launch_persistent_context()``.
"""
resolved_seed = int(seed) if seed is not None else secrets.randbits(31)
profile = generate_profile(resolved_seed, pin=pin, fixed_gpu_class=forced_gpu_class(resolved_seed))
prefs = translate_profile_to_prefs(
profile,
locale=locale,
timezone=timezone,
extra_prefs=extra_prefs,
virtual_display=virtual_display,
)
# stealthfox.* is the namespace the binary's Juggler reads (see launcher.py note).
prefs["stealthfox.humanize"] = bool(humanize)
if humanize:
max_seconds = float(humanize) if not isinstance(humanize, bool) else 1.5
prefs["stealthfox.humanize.maxTime"] = str(max_seconds)
return prefs
def get_default_args() -> List[str]:
"""Return the default Firefox CLI args to pass via ``args=``.
Currently empty list, since all our stealth configuration is delivered
via ``firefox_user_prefs`` rather than CLI flags. Exposed for parity
with the ``cloakbrowser.config.get_default_stealth_args`` pattern and
to future-proof integrations that already wire ``args=[*existing,
*get_default_args()]``.
"""
return []
"""Backward-compat shim — spostato in invisible_core.config (alias completo)."""
import sys as _sys
from invisible_core import config as _mod
_sys.modules[__name__] = _mod

View file

@ -1,80 +1,4 @@
"""Compile-time constants that pin the wrapper to a specific Firefox build.
BINARY_VERSION is bumped every time new Firefox patches are released. It is
deliberately decoupled from the Python package version so that pure-Python
bugfixes don't force a multi-hour Firefox rebuild.
"""
from __future__ import annotations
# Bump this when a new patched Firefox build is released on GitHub.
BINARY_VERSION: str = "firefox-13"
# Releases known to be broken — ensure_binary() refuses them with a clear error
# instead of handing the user an unusable binary. firefox-8 was packaged without
# the juggler automation layer, so Playwright cannot drive it (TargetClosedError);
# fixed in firefox-9 (package-manifest.in now ships chrome/juggler). A cached
# firefox-8 from before the bump would otherwise keep being used silently.
BROKEN_VERSIONS: frozenset[str] = frozenset({"firefox-8"})
# Underlying Firefox version (for display only; does not drive downloads).
FIREFOX_UPSTREAM_VERSION: str = "150.0.1"
# The base filename prefix used inside archives.
BINARY_BASENAME: str = f"firefox-{FIREFOX_UPSTREAM_VERSION}-stealth"
def ARCHIVE_NAME(platform_key: str, machine: str) -> str:
"""Return the platform-specific archive filename.
platform_key: sys.platform ("win32", "linux", "darwin")
machine: platform.machine() ("AMD64", "x86_64", "arm64", "aarch64", ...)
"""
pk = platform_key.lower()
m = machine.lower()
if m in {"amd64", "x86_64"}:
arch = "x86_64"
elif m in {"arm64", "aarch64"}:
arch = "arm64"
else:
raise NotImplementedError(f"unsupported arch: {machine}")
if pk == "win32":
return f"{BINARY_BASENAME}-win-{arch}.zip"
if pk == "linux":
return f"{BINARY_BASENAME}-linux-{arch}.tar.gz"
if pk == "darwin":
return f"{BINARY_BASENAME}-macos-{arch}.tar.gz"
raise NotImplementedError(f"unsupported platform: {platform_key}")
# Binary entry point relative path inside the extracted archive root.
# macOS ships the .app bundle (renamed to a stable "Firefox.app" by release.yml);
# the wrapper execs the inner binary directly, which sidesteps Gatekeeper.
BINARY_ENTRY_REL = {
"win32": "firefox.exe",
"linux": "firefox",
"darwin": "Firefox.app/Contents/MacOS/firefox",
}
# GitHub release URL template. The "TODO" owner is resolved at publication time.
RELEASE_URL_TEMPLATE = (
"https://github.com/feder-cr/invisible_playwright/releases/download/{tag}/{asset}"
)
# ─────────────────────────────────────────────────────────────────────────
# GeoIP database (timezone="auto" → resolve IANA zone from proxy egress IP)
# ─────────────────────────────────────────────────────────────────────────
# daijro/geoip-all-in-one merges IP2Location LITE + GeoLite2 + DB-IP into a
# single mmdb (country ISO + coordinates + IANA timezone via tzfpy), rebuilt
# weekly. GPL-3.0, so we DOWNLOAD it at runtime into the user cache (like the
# Firefox binary) rather than bundling it into this MIT package. The `-all`
# variant covers IPv4+IPv6. download.py NEVER pins a tag (daijro prunes old
# releases, so a pinned tag eventually 404s): on every launch it resolves the
# CURRENT latest tag from the `releases/latest/download` permalink (no GitHub
# API, no rate limit) and pulls it if newer than the cache.
GEOIP_REPO: str = "daijro/geoip-all-in-one"
GEOIP_ASSET: str = "geoip-aio-all.mmdb.zip"
GEOIP_MMDB_NAME: str = "geoip-aio-all.mmdb"
GEOIP_RELEASE_URL_TEMPLATE: str = (
"https://github.com/daijro/geoip-all-in-one/releases/download/{tag}/{asset}"
)
"""Backward-compat shim — spostato in invisible_core.constants (alias completo)."""
import sys as _sys
from invisible_core import constants as _mod
_sys.modules[__name__] = _mod

File diff suppressed because it is too large Load diff

View file

@ -1,343 +1,4 @@
"""Download and cache the patched Firefox binary from GitHub Releases."""
from __future__ import annotations
import hashlib
import os
import platform
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
import time
import zipfile
from pathlib import Path
import platformdirs
import requests
from .constants import (
ARCHIVE_NAME,
BINARY_ENTRY_REL,
BINARY_VERSION,
BROKEN_VERSIONS,
GEOIP_ASSET,
GEOIP_MMDB_NAME,
GEOIP_REPO,
GEOIP_RELEASE_URL_TEMPLATE,
RELEASE_URL_TEMPLATE,
)
def _github_token() -> str | None:
return os.environ.get("STEALTHFOX_GITHUB_TOKEN") or os.environ.get("GITHUB_TOKEN")
def _parse_owner_repo(template: str) -> tuple[str, str]:
"""Extract (owner, repo) from RELEASE_URL_TEMPLATE."""
m = re.match(r"https://github\.com/([^/]+)/([^/]+)/releases/", template)
if not m:
raise RuntimeError(f"cannot parse owner/repo from {template!r}")
return m.group(1), m.group(2)
def cache_root() -> Path:
"""Directory where all cached binaries live."""
return Path(platformdirs.user_cache_dir("invisible-playwright"))
def cache_dir_for_version(version: str = BINARY_VERSION) -> Path:
return cache_root() / version
def _resolve_asset_url(tag: str, asset_name: str) -> str:
"""Return a downloadable URL for the asset.
For private repos the direct `releases/download/<tag>/<asset>` URL returns
404 even with a token, so we resolve via the API: list assets for the
release tag, find the one matching `asset_name`, and use its API URL with
`Accept: application/octet-stream` (which 302-redirects to a signed URL).
For public repos the direct URL still works without a token.
"""
token = _github_token()
if not token:
return RELEASE_URL_TEMPLATE.format(tag=tag, asset=asset_name)
owner, repo = _parse_owner_repo(RELEASE_URL_TEMPLATE)
api = f"https://api.github.com/repos/{owner}/{repo}/releases/tags/{tag}"
r = requests.get(api, headers={"Authorization": f"token {token}"}, timeout=30)
r.raise_for_status()
for a in r.json().get("assets", []):
if a.get("name") == asset_name:
return a["url"]
raise RuntimeError(f"asset {asset_name!r} not found in release {tag!r}")
def _download_file(url: str, dst: Path, chunk_size: int = 1 << 16) -> None:
dst.parent.mkdir(parents=True, exist_ok=True)
headers: dict[str, str] = {}
token = _github_token()
if token and url.startswith("https://api.github.com/"):
headers["Authorization"] = f"token {token}"
headers["Accept"] = "application/octet-stream"
with requests.get(url, stream=True, timeout=60, headers=headers) as r:
r.raise_for_status()
with open(dst, "wb") as f:
for chunk in r.iter_content(chunk_size):
if chunk:
f.write(chunk)
def _sha256_file(path: Path) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(1 << 16), b""):
h.update(chunk)
return h.hexdigest()
def _parse_checksums(text: str) -> dict[str, str]:
out: dict[str, str] = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split()
if len(parts) >= 2:
# sha256sum uses ' *' or ' ' prefix for binary vs text mode
key = parts[-1].lstrip("*")
out[key] = parts[0]
return out
def _extract(archive: Path, dst: Path) -> None:
dst.mkdir(parents=True, exist_ok=True)
if archive.suffix == ".zip":
with zipfile.ZipFile(archive) as zf:
zf.extractall(dst)
elif archive.name.endswith(".tar.gz") or archive.suffix in {".tgz", ".gz"}:
with tarfile.open(archive, "r:gz") as tf:
tf.extractall(dst)
else:
raise RuntimeError(f"unknown archive format: {archive}")
def _post_extract_darwin(app_root: Path, entry: Path) -> None:
"""Make an ad-hoc-signed .app launchable on macOS.
The .app is downloaded via requests (no Finder quarantine attached), but we
strip com.apple.quarantine defensively and ensure the inner binary is
executable. We exec the inner binary directly (not via LaunchServices), so
Gatekeeper's first-launch prompt does not apply; the ad-hoc signature
(applied in release.yml) is what lets the arm64 Mach-O run at all.
"""
app = app_root
# walk up to the .app bundle dir if entry points inside it
for parent in entry.parents:
if parent.name.endswith(".app"):
app = parent
break
try:
subprocess.run(["xattr", "-dr", "com.apple.quarantine", str(app)], check=False)
except FileNotFoundError:
pass
try:
entry.chmod(0o755)
except OSError:
pass
def ensure_binary(version: str = BINARY_VERSION) -> Path:
"""Return a path to a runnable Firefox executable. Download if needed."""
if version in BROKEN_VERSIONS:
raise RuntimeError(
f"{version} is a known-broken release (the juggler automation layer is "
f"missing, so Playwright cannot drive it). Upgrade invisible_playwright "
f"(current BINARY_VERSION={BINARY_VERSION}) or pass a newer version."
)
plat = sys.platform
mach = platform.machine()
asset = ARCHIVE_NAME(plat, mach)
entry_rel = BINARY_ENTRY_REL.get(plat)
if entry_rel is None:
raise NotImplementedError(f"no binary entry for platform {plat}")
version_dir = cache_dir_for_version(version)
entry = version_dir / entry_rel
if entry.exists():
return entry
url_archive = _resolve_asset_url(version, asset)
url_sums = _resolve_asset_url(version, "checksums.txt")
with tempfile.TemporaryDirectory() as td:
tmp = Path(td)
archive_path = tmp / asset
_download_file(url_archive, archive_path)
sums_path = tmp / "checksums.txt"
_download_file(url_sums, sums_path)
sums = _parse_checksums(sums_path.read_text())
expected = sums.get(asset)
if expected is None:
raise RuntimeError(f"no SHA256 for {asset} in checksums.txt")
actual = _sha256_file(archive_path)
if actual.lower() != expected.lower():
raise RuntimeError(
f"SHA256 mismatch for {asset}: got {actual}, expected {expected}"
)
_extract(archive_path, version_dir)
if plat == "darwin":
_post_extract_darwin(version_dir, entry)
if not entry.exists():
raise RuntimeError(f"binary not found after extraction: {entry}")
return entry
# ─────────────────────────────────────────────────────────────────────────
# GeoIP mmdb (timezone="auto" → map egress IP → IANA zone)
#
# daijro/geoip-all-in-one is rebuilt weekly and KEEPS ONLY the latest ~2
# releases — older tags are pruned and 404. So we NEVER pin a tag: on every
# launch we resolve the CURRENT latest tag from the `releases/latest/download`
# permalink (its 302 Location carries the tag — a plain CDN request, NOT the
# rate-limited GitHub API) and download it if it differs from the cached one.
# Offline → reuse the cached mmdb; cold cache + offline → raise (the caller can
# then fall back off timezone="auto"). No stale pinned tag to rot.
# ─────────────────────────────────────────────────────────────────────────
def _geoip_root() -> Path:
return cache_root() / "geoip"
def _cached_geoip_mmdb() -> Path | None:
"""Newest cached mmdb across tag dirs, or None. Tag dirs are date strings
(e.g. ``2026.06.17``) so a lexical sort is chronological."""
root = _geoip_root()
if not root.exists():
return None
cands = sorted(root.glob("*/*.mmdb"))
return cands[-1] if cands else None
def _geoip_latest_url() -> str:
return f"https://github.com/{GEOIP_REPO}/releases/latest/download/{GEOIP_ASSET}"
def _latest_geoip_tag_api() -> str:
"""Latest ``daijro/geoip-all-in-one`` release tag via the GitHub API
(fallback for :func:`_resolve_latest_geoip_tag` when the permalink HEAD
can't be parsed)."""
headers = {"Accept": "application/vnd.github+json"}
token = _github_token()
if token:
headers["Authorization"] = f"token {token}"
r = requests.get(
f"https://api.github.com/repos/{GEOIP_REPO}/releases/latest",
headers=headers, timeout=15,
)
r.raise_for_status()
tag = r.json().get("tag_name")
if not tag:
raise RuntimeError("no tag_name in geoip-all-in-one latest release")
return tag
def _resolve_latest_geoip_tag() -> str | None:
"""Current latest release tag WITHOUT the rate-limited API: HEAD the
``releases/latest/download`` permalink GitHub answers 302 with
``Location: /releases/download/<tag>/<asset>``. Falls back to the API,
then to ``None`` (offline / unparseable)."""
try:
r = requests.head(_geoip_latest_url(), allow_redirects=False, timeout=10)
loc = r.headers.get("Location") or r.headers.get("location") or ""
m = re.search(r"/releases/download/([^/]+)/", loc)
if m:
return m.group(1)
except Exception:
pass
try:
return _latest_geoip_tag_api()
except Exception:
return None
def _download_geoip_tag(tag: str) -> Path:
"""Download + extract a specific tag's mmdb if not already cached."""
dst_dir = _geoip_root() / tag
target = dst_dir / GEOIP_MMDB_NAME
if not target.exists():
url = GEOIP_RELEASE_URL_TEMPLATE.format(tag=tag, asset=GEOIP_ASSET)
dst_dir.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory() as td:
archive = Path(td) / GEOIP_ASSET
_download_file(url, archive)
_extract(archive, dst_dir)
if target.exists():
return target
# asset name inside the zip may differ from GEOIP_MMDB_NAME
found = sorted(dst_dir.glob("*.mmdb"))
if found:
return found[0]
raise RuntimeError(f"geoip mmdb not found after extraction in {dst_dir}")
def _prune_old_geoip_tags(keep: str) -> None:
"""Drop every cached tag dir except ``keep`` to bound disk usage."""
root = _geoip_root()
if not root.exists():
return
for d in root.iterdir():
if d.is_dir() and d.name != keep:
shutil.rmtree(d, ignore_errors=True)
def geoip_mmdb_path() -> Path | None:
"""Path to the currently-cached mmdb (newest tag), or None if none cached."""
return _cached_geoip_mmdb()
def ensure_geoip_mmdb() -> Path:
"""Return the geoip mmdb, always the latest daijro build. Checked on EVERY
call a single cheap permalink HEAD (no GitHub API, so no rate limit).
Resolution order:
1. ``STEALTHFOX_GEOIP_MMDB`` env use that file (user-supplied / test).
2. Resolve the CURRENT latest tag. If it differs from the newest cached
tag (or nothing is cached) download it, prune older tags, return it.
3. Latest tag == newest cached tag use the cache (no download).
4. Couldn't resolve the tag (offline / unparseable): cached mmdb → use it;
cold cache raise (caller can then drop timezone="auto").
"""
override = os.environ.get("STEALTHFOX_GEOIP_MMDB")
if override:
p = Path(override)
if not p.exists():
raise RuntimeError(f"STEALTHFOX_GEOIP_MMDB points to a missing file: {p}")
return p
cached = _cached_geoip_mmdb()
cached_tag = cached.parent.name if cached else None
latest = _resolve_latest_geoip_tag()
if latest and latest != cached_tag:
# newer build available (or nothing cached) → fetch it
try:
mmdb = _download_geoip_tag(latest)
_prune_old_geoip_tags(mmdb.parent.name)
return mmdb
except Exception:
if cached:
return cached # transient download failure → keep using the cache
raise
if cached:
return cached # cache is already the latest, or we're offline
raise RuntimeError(
"geoip mmdb unavailable: no cached copy and GitHub is unreachable. "
"Connect once to download it, or set STEALTHFOX_GEOIP_MMDB to a local "
"geoip-aio-all.mmdb file."
)
"""Backward-compat shim — spostato in invisible_core.download (alias completo)."""
import sys as _sys
from invisible_core import download as _mod
_sys.modules[__name__] = _mod

View file

@ -1,673 +1,4 @@
"""Translate an internal Profile into the Firefox prefs dict that the
patched Firefox binary expects.
The output dict keys map 1:1 to ``user.js`` preferences. Playwright passes
them via ``firefox_user_prefs=``. The patched binary propagates them to all
content processes over IPC; C++ patches read the ``zoom.stealth.*``
namespace.
The translation is split into:
* ``_BASELINE`` global stealth policy (RFP off, WebRTC leaks blocked,
safebrowsing disabled, debugger detach, ) plus Windows-canonical
constants that don't depend on the Profile (system colors palette,
WebGL extensions whitelist, speech voices, navigator identity).
* ``translate_profile_to_prefs`` overlays the Profile fields plus the
user-supplied ``locale`` and ``timezone``.
"""
from __future__ import annotations
import sys
from typing import Any, Dict, Optional
from ._fpforge import Profile
from ._webgl_personas import render_noise_seed, select_persona
# ──────────────────────────────────────────────────────────────────────
# Navigator identity — locked to Firefox 150 Windows so the binary
# reports the same UA / platform / oscpu regardless of the host OS.
# ──────────────────────────────────────────────────────────────────────
_NAVIGATOR_OVERRIDES: Dict[str, str] = {
"general.useragent.override":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) "
"Gecko/20100101 Firefox/150.0.1",
"general.platform.override": "Win32",
"general.oscpu.override": "Windows NT 10.0; Win64; x64",
# general.buildID.override removed 2026-04-28: the previous value
# "20181001000000" was a 2018 buildID stuck on a 2026-built Firefox 150
# binary (real BuildID=20260426192818 from application.ini). The 7.5-yr
# discrepancy is the kind of internal-consistency check Google reCAPTCHA
# can use to flag bot/spoofed browsers. Deleting the override lets
# Firefox emit its compiled-in buildID, which auto-tracks the binary.
# A/B knockout 2026-04-28 (n=30): F2 delete +0.083 RC vs BASE; n=100
# confirm: +0.021; overnight isolated: +0.155 single-variant. Variable
# signal, but the underlying data error is unambiguous.
"general.appversion.override": "5.0 (Windows)",
}
# ──────────────────────────────────────────────────────────────────────
# System colors — FP Pro probes getComputedStyle(div) with CSS system
# keywords (ButtonFace, Menu, Highlight, …) and hashes the result into
# signal s142. On Linux, Firefox resolves these via GTK theme → GTK
# RGB values diverge from Windows Win32 palette → server-side anomaly
# even with Windows UA. Pinning the palette to Win10 default closes
# the gap (see project_css_system_colors.md memory).
# ──────────────────────────────────────────────────────────────────────
_WIN_LIGHT_COLORS: Dict[str, str] = {
"ui.activeborder": "#B4B4B4",
"ui.activecaption": "#99B4D1",
"ui.appworkspace": "#ABABAB",
"ui.background": "#000000",
"ui.buttonface": "#F0F0F0",
"ui.buttonhighlight": "#FFFFFF",
"ui.buttonshadow": "#A0A0A0",
"ui.buttontext": "#000000",
"ui.buttonborder": "#000000",
"ui.captiontext": "#000000",
"ui.graytext": "#6D6D6D",
"ui.highlight": "#0078D7",
"ui.highlighttext": "#FFFFFF",
"ui.inactiveborder": "#F4F7FC",
"ui.inactivecaption": "#BFCDDB",
"ui.inactivecaptiontext": "#434E54",
"ui.infobackground": "#FFFFE1",
"ui.infotext": "#000000",
"ui.menu": "#F9F9FB",
"ui.menutext": "#000000",
"ui.scrollbar": "#C8C8C8",
"ui.threeddarkshadow": "#696969",
"ui.threedface": "#F0F0F0",
"ui.threedhighlight": "#FFFFFF",
"ui.threedlightshadow": "#E3E3E3",
"ui.threedshadow": "#A0A0A0",
"ui.window": "#FFFFFF",
"ui.windowframe": "#646464",
"ui.windowtext": "#000000",
"ui.mark": "#FFFF00",
"ui.marktext": "#000000",
"ui.accentcolor": "#0078D4",
"ui.accentcolortext": "#FFFFFF",
"ui.selecteditem": "#0078D7",
"ui.selecteditemtext": "#FFFFFF",
"ui.-moz-hyperlinktext": "#0066CC",
"ui.-moz-activehyperlinktext": "#EE0000",
"ui.-moz-visitedhyperlinktext": "#551A8B",
}
# ──────────────────────────────────────────────────────────────────────
# WebGL extensions — Windows ANGLE canonical lists. Empty string =
# fall back to native Mesa/ANGLE; non-empty = `getSupportedExtensions`
# returns this list verbatim and `IsSupported()` rejects anything else.
# ──────────────────────────────────────────────────────────────────────
_WEBGL1_EXTENSIONS = ",".join([
"ANGLE_instanced_arrays",
"EXT_blend_minmax",
"EXT_color_buffer_half_float",
"EXT_float_blend",
"EXT_frag_depth",
"EXT_sRGB",
"EXT_shader_texture_lod",
"EXT_texture_compression_bptc",
"EXT_texture_compression_rgtc",
"EXT_texture_filter_anisotropic",
"OES_element_index_uint",
"OES_fbo_render_mipmap",
"OES_standard_derivatives",
"OES_texture_float",
"OES_texture_float_linear",
"OES_texture_half_float",
"OES_texture_half_float_linear",
"OES_vertex_array_object",
"WEBGL_color_buffer_float",
"WEBGL_compressed_texture_s3tc",
"WEBGL_compressed_texture_s3tc_srgb",
"WEBGL_debug_renderer_info",
"WEBGL_debug_shaders",
"WEBGL_depth_texture",
"WEBGL_draw_buffers",
"WEBGL_lose_context",
"WEBGL_provoking_vertex",
])
_WEBGL2_EXTENSIONS = ",".join([
"EXT_color_buffer_float",
"EXT_color_buffer_half_float",
"EXT_float_blend",
"EXT_texture_compression_bptc",
"EXT_texture_compression_rgtc",
"EXT_texture_filter_anisotropic",
"OES_draw_buffers_indexed",
"OES_texture_float_linear",
"OES_texture_half_float_linear",
"OVR_multiview2",
"WEBGL_compressed_texture_s3tc",
"WEBGL_compressed_texture_s3tc_srgb",
"WEBGL_debug_renderer_info",
"WEBGL_debug_shaders",
"WEBGL_lose_context",
"WEBGL_provoking_vertex",
])
# ──────────────────────────────────────────────────────────────────────
# Speech voices — Windows canonical "Microsoft *" set. Format:
# "NAME|LANG|DEFAULT|LOCAL,...". Non-empty value drives the
# speechSynthesis.getVoices() patch; empty disables it.
# ──────────────────────────────────────────────────────────────────────
_WIN_VOICES = ",".join([
"Microsoft David - English (United States)|en-US|1|1",
"Microsoft Zira - English (United States)|en-US|0|1",
"Microsoft Mark - English (United States)|en-US|0|1",
"Microsoft David Desktop - English (United States)|en-US|0|1",
"Microsoft Zira Desktop - English (United States)|en-US|0|1",
])
# ──────────────────────────────────────────────────────────────────────
# Baseline — applied to every session regardless of Profile.
# ──────────────────────────────────────────────────────────────────────
_BASELINE: Dict[str, Any] = {
# Turn off Firefox's own resistFingerprinting; we do our own via patches.
"privacy.resistFingerprinting": False,
"privacy.resistFingerprinting.letterboxing": False,
# FF150 fingerprintingProtection — enabled by default (or remotely via
# Mozilla webcompat overrides). FP Pro detects the side-effects and
# flips `privacy_settings: true`. On FF146 these were all off → False.
# Force off so FP Pro reports privacy_settings:false (matches FF146).
"privacy.fingerprintingProtection": False,
"privacy.fingerprintingProtection.pbmode": False,
"privacy.fingerprintingProtection.remoteOverrides.enabled": False,
# WebRTC: enabled, looks like a real Firefox behind NAT, no real-IP leak.
# obfuscate_host_addresses=true → host candidate is `<uuid>.local` mDNS,
# exactly like vanilla Firefox (BrowserLeaks "No Leak", Local IP "-").
# The mDNS-IPC hang feared on older builds does NOT reproduce on FF150.
# The proxy-egress srflx is injected by our C++ (srflx swap §17 + fallback
# §17.B), fed the egress IP via STEALTHFOX_WEBRTC_PUBLIC_IP from
# launcher._build_env (auto-discovered from the proxy).
# IPv6: media.peerconnection.ice.disableIPv6 is DEAD on FF150 (read by no
# ICE-gathering code). The real switch is our zoom.stealth.webrtc.disable_ipv6
# (nICEr addrs.cpp filter) + the STEALTHFOX_WEBRTC_DISABLE_IPV6 env.
"media.peerconnection.enabled": True,
"media.peerconnection.ice.no_host": False,
"media.peerconnection.ice.default_address_only": False,
"media.peerconnection.ice.obfuscate_host_addresses": True,
"zoom.stealth.webrtc.disable_ipv6": True,
"media.peerconnection.ice.proxy_only": False,
"media.peerconnection.ice.relay_only": False,
"media.peerconnection.use_document_iceservers": True,
# Proxy — route DNS through SOCKS proxies to avoid local DNS leaks.
"network.proxy.socks_remote_dns": True,
"network.proxy.failover_direct": False,
# TLS ClientHello fingerprint — match stock Firefox byte-for-byte.
# The Playwright/Juggler Firefox build this binary derives from re-enables
# cipher 0xC009 (TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), which retail Firefox
# 150 does NOT offer. That extra (17th) cipher shifts our JA3/JA4 away from
# any real Firefox (ja4 t13d1717h2 vs stock t13d1617h2). A ClientHello that
# matches no real browser is itself a consistency tell. Disabling it makes
# JA3/JA4/peetprint byte-identical to retail FF150 (verified on tls.peet.ws).
# Stock Firefox ships without 0xC009 and works on the whole web, so this only
# improves fingerprint consistency — it cannot break connectivity.
"security.ssl3.ecdhe_ecdsa_aes_128_sha": False,
# Safebrowsing — chatty and fingerprintable.
"browser.safebrowsing.malware.enabled": False,
"browser.safebrowsing.phishing.enabled": False,
"browser.safebrowsing.downloads.enabled": False,
"browser.safebrowsing.downloads.remote.enabled": False,
# First-run / welcome UI noise.
"browser.startup.page": 0,
"browser.shell.checkDefaultBrowser": False,
"browser.aboutwelcome.enabled": False,
"browser.startup.upgradeDialog.enabled": False,
"termsofuse.acceptedVersion": 999,
# Disable about:newtab auto-load — TopSitesFeed.sys.mjs auto-fetches when
# a tab opens, triggering a cross-process BC swap that hijacks the first
# page.goto() (NS_BINDING_ABORTED on creepjs/peet/sannysoft/fppro).
"browser.newtabpage.enabled": False,
"browser.newtab.preload": False,
"browser.newtabpage.activity-stream.feeds.topsites": False,
"browser.newtabpage.activity-stream.feeds.section.topstories": False,
"browser.newtabpage.activity-stream.enabled": False,
# Disable Firefox internal services that hit the network on startup.
# Through a residential SOCKS5 proxy these compete with the test
# navigation and trigger NS_BINDING_FAILED (server-side rate-limit /
# connection drops). Domains observed in MOZ_LOG: push.services,
# firefox.settings.services, detectportal, ohttp-gateway, location.
"browser.aboutConfig.showWarning": False,
"network.captive-portal-service.enabled": False,
"network.connectivity-service.enabled": False,
"dom.push.enabled": False,
"dom.push.connection.enabled": False,
"geo.enabled": False,
"geo.provider.network.url": "",
"browser.region.network.url": "",
"browser.region.update.enabled": False,
"services.settings.server": "",
"browser.search.geoSpecificDefaults": False,
"browser.contentblocking.report.lockwise.enabled": False,
"browser.contentblocking.report.monitor.enabled": False,
"extensions.systemAddon.update.enabled": False,
"extensions.update.enabled": False,
"extensions.getAddons.cache.enabled": False,
"browser.discovery.enabled": False,
"browser.ping-centre.telemetry": False,
"app.normandy.enabled": False,
"dom.private-attribution.submission.enabled": False,
"browser.translations.enable": False,
"browser.search.update": False,
# HTTP/3 + speculative + Alt-Svc disabled. SOCKS5 proxy doesn't
# support UDP ASSOCIATE so HTTP/3 fails. Speculative connections
# under load cause early channel cancel (NS_BINDING_FAILED).
"network.http.http3.enable": False,
"network.http.http3.enabled": False,
"network.http.altsvc.enabled": False,
"network.http.altsvc.oe": False,
"network.http.speculative-parallel-limit": 0,
"network.predictor.enabled": False,
"network.dns.disablePrefetch": True,
"network.dns.disablePrefetchFromHTTPS": True,
"network.dns.echconfig.enabled": False,
"network.dns.use_https_rr_as_altsvc": False,
# === Fission / site-isolation disabled (FF146 Playwright parity) ===
# Force a single content-process model. Three knobs are required in FF150:
# upstream Playwright Firefox (FF146-based) only needed fission.autostart=False
# because FF146's default isolation strategy was looser. FF150 ships with
# fission.webContentIsolationStrategy=1 (IsolateEverything) which still
# site-isolates cross-origin iframes into separate `webIsolated` content
# processes EVEN WHEN fission.autostart is False. From the parent process's
# point of view, those iframes get a Juggler Frame placeholder with no
# docShell, no URL, and an execution context that wraps the wrong global,
# so frame.evaluate() fails with cross-origin SOP errors and
# element_handle.content_frame() returns None.
#
# Pinning the strategy to 0 keeps every cross-origin web iframe in the
# parent's content process, where the Juggler code paths from the FF146
# era expect them. processCount.webIsolated=1 is kept as belt-and-suspenders
# in case some path still classifies an origin as webIsolated despite the
# strategy change. It costs nothing to leave.
#
# See issue #20 + tests/test_cross_origin_iframe.py for the regression
# sentinel that catches a future A/B flipping these back.
"fission.autostart": False,
"fission.autostart.session": False,
"fission.webContentIsolationStrategy": 0, # IsolateNothing
"dom.ipc.processCount.webIsolated": 1,
# Telemetry & data reporting.
"datareporting.healthreport.uploadEnabled": False,
"datareporting.policy.dataSubmissionEnabled": False,
"toolkit.telemetry.enabled": False,
"toolkit.telemetry.unified": False,
"app.shield.optoutstudies.enabled": False,
# Update channels.
"app.update.enabled": False,
"app.update.auto": False,
# Speech synth: enabled (the C++ patch fabricates voices from the
# comma list above) regardless of the host OS.
"media.webspeech.synth.enabled": True,
"zoom.stealth.voices.list": _WIN_VOICES,
# WebGL extensions whitelist — non-empty pre-empts native enumeration.
"zoom.stealth.webgl.extensions": _WEBGL1_EXTENSIONS,
"zoom.stealth.webgl2.extensions": _WEBGL2_EXTENSIONS,
# WebGL numeric param overrides — kept empty (A/B test 2026-04-22 showed
# mismatches between the values we shipped and ANGLE's real envelope
# raised FP Pro's ML tampering score). Slot kept for future experiments.
"zoom.stealth.webgl.int_params": "",
"zoom.stealth.webgl.int2_params": "",
"zoom.stealth.webgl.shader_precisions": "",
"zoom.stealth.webgl.float_params": "",
# DevTools anti-detection.
"zoom.stealth.debugger.force_detach": True,
# Canvas substitution (Option B for canvas) — replace pixels with hash(seed,idx),
# uniform-skip (red-box exact, masking-safe) + full overwrite. Makes the canvas
# render a pure function of (seed) = HOST-INDEPENDENT (kills the DWrite-vs-FreeType
# text-raster leak: Canvas Hash + Font hash were the residual Win!=Linux signals).
# ON by default (paired with webgl.substitute_pixels).
"zoom.stealth.canvas.substitute_pixels": True,
# WebGL substitution (Option B) — replace readback/snapshot RGB with
# hash(seed,idx), endpoint-preserving. Makes the WebGL render hash a pure
# function of (seed, dims) = HOST-INDEPENDENT, so no per-host hw_seed
# calibration is needed (the gamma path was per-host: NVIDIA/Arc-on-Win clean
# seeds went dirty on the Linux GL backend). ON by default.
"zoom.stealth.webgl.substitute_pixels": True,
# WebGPU presence consistency. Firefox enables dom.webgpu.enabled by default on
# Windows/Mac-ARM but NOT on Linux/Mac-x64. We ALWAYS claim Windows, so force it ON
# on every host: a Windows FF MUST expose navigator.gpu (object); a Linux host leaving
# it undefined while the UA says Windows is an inconsistency tell (RE 2026-06-22:
# has_gpu was object on Win, undefined on WSL). adapter.info is empty (FF privacy
# default) so no GPU-name leak; requestAdapter may be null on a GPU-less host, which
# is itself plausible for a real Windows machine.
"dom.webgpu.enabled": True,
# Audio fingerprint noise OFF. RE 2026-06-22: the per-session OfflineAudioContext
# noise (gated by hw_seed) was THE dominant driver of FP Pro tampering_ml on Windows
# — b005 Win dropped 0.4349 -> 0.0564 with audio noise alone disabled (canvas_text/
# emoji unchanged, so they were a red-herring). The audio value is already host-indep
# AND identical to a real FF's canonical OfflineAudioContext sum, so a fixed (un-noised)
# audio is NOT a linking signal (every real FF has the same value) — removing the noise
# matches real Firefox and clears the tampering flag.
"zoom.stealth.audio.fp_noise": False,
# Navigator identity (locked to Windows Firefox 150).
**_NAVIGATOR_OVERRIDES,
}
# ──────────────────────────────────────────────────────────────────────
# Linux-only Xvfb workarounds — the Linux Firefox build under Xvfb
# cannot run WebRender (`ConnectToCompositor` retries forever). We
# disable WebRender + force WebGL through the GL software path so
# webgl_basics / webgl_extensions still report.
# ──────────────────────────────────────────────────────────────────────
_LINUX_XVFB_WORKAROUNDS: Dict[str, Any] = {
"gfx.webrender.all": False,
"gfx.webrender.force-disabled": True,
"webgl.force-enabled": True,
# webgl.software-rendering-enabled / webgl.force-layers-readback removed in FF150.
}
# ──────────────────────────────────────────────────────────────────────
# Windows virtual-desktop workarounds — when headless=True on Windows,
# Firefox runs on a CreateDesktop virtual desktop. The hardware GPU is
# inaccessible from the virtual desktop, so the GPU process crashes when
# it tries to initialize the D3D11 compositor with hardware acceleration.
#
# Approach: force D3D11 WARP (CPU software renderer) for the GPU process.
# layers.d3d11.force-warp=True → compositor uses WARP → GPU process stable.
# webgl.angle.force-warp=True → ANGLE uses WARP → WebGL context creates.
#
# CRITICAL: do NOT set webgl.out-of-process=False. That moves WebGL from the
# GPU process to the sandboxed content process. The content process sandbox
# blocks D3D11 access entirely → ANGLE crashes the content process →
# canvas.getContext('webgl') throws instead of returning null.
#
# gfx.canvas.accelerated=False: default is true, disabling avoids any
# hardware GPU dependency for 2D canvas in the content process.
# ──────────────────────────────────────────────────────────────────────
_WIN_VIRT_DESKTOP_WORKAROUNDS: Dict[str, Any] = {
# FF150 regression vs FF146 on CreateDesktop alt-desktop:
# The GPU process sandbox (level=1, default since FF110) tries to parent
# its compositor window to the parent process's window. Our worker spawns
# Firefox on a CreateDesktop-created alt desktop — parent and GPU process
# do not share the same desktop/HWND namespace, so window parenting fails
# silently. WebRender falls back to "Software D3D11" and OOP-WebGL never
# publishes a hardware ANGLE renderer → getContext('webgl') returns a
# context but extensions/parameters/$hash all come back null/empty (FF146
# had a more permissive sandbox, so the same setup worked there).
# Bugzilla refs: 1798091, 1524591, 1229829. Lowering the GPU sandbox to 0
# restores hardware compositor + functional WebGL on alt desktops.
"security.sandbox.gpu.level": 0,
# Same root cause as above, content process side. Wrapper repo issue #18
# (tab crash on cross-process navigation under headless=True). Sandbox
# content level > 4 puts content processes on the sandbox's own
# kAlternateWinstation (see security/sandbox/win/src/sandboxbroker/
# sandboxBroker.cpp line 1113-1114:
# `if (aSandboxLevel > 4) config->SetDesktop(kAlternateWinstation)`).
# Combined with our CreateDesktop alt-desktop, that puts browser process
# and content processes on DIFFERENT desktops. Cross-process navigation
# then fails window parenting between parent and child, the content
# process exits cleanly (exitCode=0, signal=null) and Playwright fires
# page.on('crash') ~10s after page load. Lowering content sandbox to 4
# keeps content processes on the same desktop as the browser process,
# which is what we want here (still tight enough — level 4 blocks
# file/registry write, network calls, hardware access).
"security.sandbox.content.level": 4,
}
# ──────────────────────────────────────────────────────────────────────
# Public helpers
# ──────────────────────────────────────────────────────────────────────
def _accept_language(locale: str) -> str:
# "<locale>, <base>" — the desktop-default shape (e.g. "en-US, en"). Firefox expands it
# to navigator.languages=["en-US","en"] AND (via the patched binary) the q-valued header
# "en-US,en;q=0.5". The patched nsHttpHandler (STEALTHFOX, RE 2026-06-23) builds the
# Accept-Language header from THIS pref even when juggler sets a per-context locale
# override, so header and navigator.languages stay consistent 2/2 — the most authentic
# (real desktop) form. Supersedes the 2026-06-22 single-tag workaround.
lang = locale.replace("_", "-")
base = lang.split("-")[0]
return f"{lang}, {base}" if base != lang else lang
def translate_profile_to_prefs(
profile: Profile,
*,
locale: str = "en-US",
timezone: str = "",
extra_prefs: Optional[Dict[str, Any]] = None,
virtual_display: bool = False,
) -> Dict[str, Any]:
"""Return a complete prefs dict ready for Playwright's firefox_user_prefs=.
Args:
profile: Bayesian-sampled fingerprint (from ``generate_profile``).
locale: BCP-47 tag, e.g. ``"en-US"``.
timezone: IANA timezone name, e.g. ``"America/New_York"``.
extra_prefs: Optional overlay applied LAST.
virtual_display: When True on Windows, apply GPU-disabling workarounds
to prevent the GPU process from crashing on virtual
desktops that have no D3D11 backend.
"""
prefs: Dict[str, Any] = dict(_BASELINE)
# GPU / WebGL renderer/vendor.
# On Linux we spoof to a Windows ANGLE renderer string (profile.gpu.renderer)
# so cross-platform sessions report a consistent Windows GPU identity.
# On Windows/mac, spoofing a renderer string ALONE is unsafe — the ~81
# getParameter values stay real, so a name↔params hash mismatch FP Pro flags
# (setting GTX 980 over real Arc A750 params scored ~0.70). Instead we apply a
# VALIDATED PERSONA (see _webgl_personas): a {renderer, vendor} whose params are
# the shared ANGLE D3D11 caps (vendor-independent — identical on any host, per the
# ANGLE source) and whose extension list is FORCED below. That is a coherent fake
# GPU that passes FP Pro host-independently (the host's real GPU never leaks). If no
# validated persona exists for the sampled gpu_class yet, fall back to the host-real
# renderer (empty → native ANGLE; SanitizeRenderer at ClientWebGLContext.cpp:2592).
# Apply the camoufox-derived real-Firefox GPU persona on EVERY host (Win/Linux/Mac).
# We must ALWAYS look Windows (rule), and the WebGL override is platform-independent:
# SanitizeRenderer (ClientWebGLContext.cpp) is pure string regex, and the param/extension
# overrides are pref-driven, so the C++ presents the SAME Windows ANGLE GPU regardless of
# the host's real GL backend (it never consults it when the pref is set). This is why a
# Windows GPU shows correctly even on a Linux/Mesa host (no more "Generic Renderer").
_persona = select_persona(profile.seed)
if _persona:
# Apply the FULL coherent WebGL override (renderer + vendor + webgl1/webgl2 extensions
# + ~100 getParameter values + shader-precision formats). Setting ALL of them — not just
# the renderer string — keeps renderer<->params coherent (FP Pro cross-checks them); a
# string-only spoof over the host's real params is the old ~0.85 mismatch.
for _k, _v in _persona["prefs"].items():
if _k == "zoom.stealth.webgl2.enabled":
prefs["webgl.enable-webgl2"] = bool(_v)
else:
prefs[_k] = _v
else:
prefs["zoom.stealth.webgl.renderer"] = ""
prefs["zoom.stealth.webgl.vendor"] = ""
# Canvas-noise mask is calibrated to the REAL host GPU's rendering variance — the canvas is
# drawn by real hardware, NOT the persona's claimed GPU, so it must NOT follow the persona
# (a non-Intel persona on an Intel host would over-noise). Dev/deployment host is Intel.
_renderer_lo = "intel"
# MSAA: on Windows, pin to 4 (Firefox default for ANGLE) so gl.SAMPLES is
# constant across all sessions. Different MSAA values cause different CN-set
# parameters hashes even with the same renderer → detectable variation.
# Vanilla Intel Arc A750 parameters hash (66544db8) verified at msaa=4.
_msaa = profile.webgl.msaa_samples if sys.platform.startswith("linux") else 4
prefs["zoom.stealth.webgl.msaa"] = _msaa
prefs["webgl.msaa-samples"] = _msaa
prefs["webgl.msaa-force"] = _msaa > 0
# Canvas pixel-noise density per vendor. Intel has lower natural
# rendering variance than NVIDIA/AMD, so the default 1/8 noise rate
# over-amplifies the FP Pro tampering ML signal. Drop to 1/16 for Intel
# to keep tampering_ml below the detection threshold while still
# breaking the canvas geometry hash.
if "intel" in _renderer_lo:
prefs["zoom.stealth.canvas.noise_skip_mask"] = 15 # 1/16, ~6.25%
else:
prefs["zoom.stealth.canvas.noise_skip_mask"] = 7 # 1/8, ~12.5%
# Screen
prefs["zoom.stealth.screen.width"] = profile.screen.width
prefs["zoom.stealth.screen.height"] = profile.screen.height
prefs["zoom.stealth.screen.avail_width"] = profile.screen.avail_width
prefs["zoom.stealth.screen.avail_height"] = profile.screen.avail_height
prefs["zoom.stealth.screen.dpr"] = profile.screen.dpr
prefs["layout.css.devPixelsPerPx"] = str(profile.screen.dpr)
# Hardware — coherent with the sampled gpu_class by construction (the forge
# draws hw_concurrency conditioned on the GPU class).
prefs["zoom.stealth.hw_concurrency"] = profile.hardware.concurrency
prefs["zoom.stealth.storage.quota_mb"] = profile.hardware.storage_quota_mb
# Audio
prefs["zoom.stealth.audio.sample_rate"] = profile.audio.sample_rate
prefs["zoom.stealth.audio.output_latency_ms"] = profile.audio.output_latency_ms
prefs["zoom.stealth.audio.max_channel_count"] = profile.audio.max_channel_count
# Codec
prefs["media.av1.enabled"] = profile.codec.av1_enabled
prefs["media.encoder.webm.enabled"] = profile.codec.webm_encoder_enabled
prefs["media.mediasource.webm.enabled"] = profile.codec.mediasource_webm
prefs["media.mediasource.mp4.enabled"] = profile.codec.mediasource_mp4
# Fonts — real bundled fonts (no collapse). The binary ships the real
# Windows font files in <GRE>/fonts and loads them via MOZ_BUNDLED_FONTS, so
# glyphs are genuine on every host. We expose this profile's family set via
# the per-profile fontlist, which the binary applies to the native system
# font allow-list AT CONSTRUCTION (no runtime rebuild → no scan stall), and
# force the system-ui generic to Segoe UI. profile.fonts is the _fpforge
# sample (core always + a conditioned optional subset). Per-profile metric
# uniqueness comes from the shared fpp.hw_seed jitter in the HarfBuzz shaper
# (set with the other fpp prefs), not from fabricated widths.
prefs["zoom.stealth.font.fontlist"] = ",".join(profile.fonts)
prefs["zoom.stealth.font.system_ui"] = "Segoe UI"
# Activate the bundled real-Windows fonts (MOZ_BUNDLED_FONTS / <GRE>/fonts).
prefs["gfx.bundled-fonts.activate"] = 1
# Point the CSS generics at Windows defaults. On a Windows HOST Firefox's
# built-in name-lists already resolve to these (and they're in the fontlist
# so they survive the allow-list); but on a non-Windows host (Linux/Mac) the
# built-in defaults name host fonts (DejaVu/Liberation/…) which the allow-list
# hides — so the generics would collapse. Setting them explicitly keeps the
# generics resolving to the bundled Windows families on EVERY host (system-ui
# is forced to Segoe UI by the C++ hook above, so it is not listed here).
prefs["font.name-list.serif.x-western"] = "Times New Roman"
prefs["font.name-list.sans-serif.x-western"] = "Arial"
prefs["font.name-list.monospace.x-western"] = "Consolas"
prefs["font.name-list.sans-serif.ja"] = "Yu Gothic UI"
prefs["font.name-list.serif.ja"] = "Yu Gothic UI"
prefs["font.name-list.sans-serif.ko"] = "Malgun Gothic"
prefs["font.name-list.serif.ko"] = "Malgun Gothic"
prefs["font.name-list.sans-serif.zh-CN"] = "Microsoft YaHei UI"
prefs["font.name-list.sans-serif.zh-TW"] = "Microsoft JhengHei UI"
prefs["font.name-list.sans-serif.zh-HK"] = "Microsoft JhengHei UI"
# UI / dark mode + Windows colors palette (only when light theme).
prefs["ui.systemUsesDarkTheme"] = int(profile.dark_theme)
if not profile.dark_theme:
prefs.update(_WIN_LIGHT_COLORS)
# Locale prefs.
locale = locale or "en-US"
lang = locale.replace("_", "-")
prefs["intl.accept_languages"] = _accept_language(locale)
prefs["general.useragent.locale"] = lang
prefs["intl.locale.requested"] = lang
prefs["privacy.spoof_english"] = 0
# juggler.locale.override seeds the BrowsingContext LanguageOverride FIELD in
# the parent process (BrowsingContext::Attach), whose DidSet drives BOTH
# navigator.languages (the full list) AND the realm Intl default locale (the
# primary tag it extracts) — so Intl.DateTimeFormat / NumberFormat /
# toLocaleString follow the locale, not just the Accept-Language header. Seed
# it with the full Accept-Language list so navigator.languages stays the
# desktop-default 2 elements (["fr-FR","fr"]); the C++ DidSet takes "fr-FR"
# for Intl. Mirrors juggler.timezone.override; the SOLE source of truth.
prefs["juggler.locale.override"] = _accept_language(locale)
if timezone:
# juggler.timezone.override is the SOLE source of truth read by the C++
# timezone chain (BrowsingContext::Attach/DidSet, ContentChild). The old
# zoom.stealth.timezone pref was declared in the yaml but read by NO
# code — dropped here on 2026-06-10 (see 20-our-patches.md §8).
prefs["juggler.timezone.override"] = timezone
# Cross-process seed (canvas noise + DWrite gamma share this). Only
# zoom.stealth.fpp.hw_seed is read by the C++; the old zoom.stealth.seed
# alias was never declared in the yaml and read by nothing — dropped
# 2026-06-10. The render-noise seed is DECOUPLED from the identity seed and
# drawn from a calibrated CLEAN pool: the canvas/WebGL render HASH it drives
# is the dominant FP Pro tampering_ml signal, and some hw_seeds yield a
# "suspicious" render hash. render_noise_seed() maps to the clean pool while
# keeping per-seed determinism + diversity. See _webgl_personas.
prefs["zoom.stealth.fpp.hw_seed"] = render_noise_seed(profile.seed)
# Synthetic host ICE candidate — injected by C++ when addr_ct==0 (SOCKS5
# proxy suppresses all local addresses so Firefox can't gather host cands).
# LAN IP is seed-derived so it's consistent per session and looks like a
# real home router assignment (192.168.x.x range).
_s = profile.seed
_lan_ip = f"192.168.{(_s >> 8) % 254 + 1}.{_s % 254 + 1}"
prefs["zoom.stealth.webrtc.host_ip"] = _lan_ip
# Windows/mac extension list:
# - persona active → the coherent webgl1/webgl2 extension lists (in the GPU's real
# native order) were ALREADY applied above from the GPU pool's `prefs`, alongside the
# matching renderer + params + shader-precisions. Nothing to do here.
# - no persona → clear so the host-real renderer reports its native extension set
# (matches real vanilla captures for that host's GPU).
if not sys.platform.startswith("linux") and not _persona:
prefs["zoom.stealth.webgl.extensions"] = ""
prefs["zoom.stealth.webgl2.extensions"] = ""
# Linux Xvfb workarounds (no-op on Windows).
if sys.platform.startswith("linux"):
for k, v in _LINUX_XVFB_WORKAROUNDS.items():
prefs.setdefault(k, v)
# Windows virtual-desktop workarounds (headless=True on Windows).
if virtual_display and sys.platform == "win32":
for k, v in _WIN_VIRT_DESKTOP_WORKAROUNDS.items():
prefs.setdefault(k, v)
# Caller overlay LAST so users can override anything we set. A value of
# None is treated as a sentinel meaning "delete this pref entirely from
# the final dict" — useful for A/B harnesses that need to test what
# happens when an override is unset (vs set to empty string, which for
# some prefs like general.useragent.override means literally empty UA).
if extra_prefs:
for k, v in extra_prefs.items():
if v is None:
prefs.pop(k, None)
else:
prefs[k] = v
return prefs
"""Backward-compat shim — spostato in invisible_core.prefs (alias completo)."""
import sys as _sys
from invisible_core import prefs as _mod
_sys.modules[__name__] = _mod

84
tests/test_backcompat.py Normal file
View file

@ -0,0 +1,84 @@
"""Backward-compatibility guard for the invisible_core / invisible_playwright split.
After extracting the pure config into ``invisible_core`` and turning the moved
modules into aliasing shims, EVERY import path that external users (and our own
outreach PRs) rely on must keep working same objects, same behavior. This test
is the contract: if it breaks, we broke someone's install.
"""
import importlib
import pytest
@pytest.mark.unit
def test_top_level_public_api_imports():
import invisible_playwright as ip
for name in [
"InvisiblePlaywright", "ensure_binary", "ensure_geoip_mmdb",
"get_default_stealth_prefs", "get_default_args",
"resolve_session_timezone", "GeoTimezoneError",
"BINARY_VERSION", "FIREFOX_UPSTREAM_VERSION", "__version__",
]:
assert hasattr(ip, name), f"invisible_playwright.{name} missing"
@pytest.mark.unit
def test_submodule_imports_external_users_rely_on():
# These are the exact import shapes used in the wild + in our Skyvern/onyx/etc PRs.
from invisible_playwright._fpforge import generate_profile, Profile # noqa: F401
from invisible_playwright._webgl_personas import forced_gpu_class # noqa: F401
from invisible_playwright.prefs import translate_profile_to_prefs # noqa: F401
from invisible_playwright.download import ensure_binary # noqa: F401
from invisible_playwright.constants import BINARY_VERSION # noqa: F401
from invisible_playwright.config import get_default_stealth_prefs # noqa: F401
@pytest.mark.unit
def test_deep_submodule_and_private_names_still_import():
from invisible_playwright._fpforge.profile import Profile # noqa: F401
from invisible_playwright._fpforge import _sampler # noqa: F401
from invisible_playwright.prefs import _BASELINE # noqa: F401
assert isinstance(_BASELINE, dict)
@pytest.mark.unit
def test_class_identity_is_shared_with_core():
# isinstance must hold: the object returned by generate_profile must be an
# instance of BOTH the invisible_playwright-path Profile and the core Profile
# (they must be the SAME class object, not duplicates).
from invisible_playwright._fpforge import generate_profile
from invisible_playwright._fpforge import Profile as PW_Profile
from invisible_playwright._fpforge.profile import Profile as PW_Profile2
import invisible_core
from invisible_core import Profile as Core_Profile
p = generate_profile(seed=42)
assert isinstance(p, PW_Profile)
assert isinstance(p, PW_Profile2)
assert isinstance(p, Core_Profile)
assert PW_Profile is Core_Profile
assert PW_Profile2 is Core_Profile
@pytest.mark.unit
def test_shim_output_matches_core_output():
from invisible_playwright._fpforge import generate_profile as gp_pw
from invisible_playwright._webgl_personas import forced_gpu_class as fgc_pw
from invisible_playwright.prefs import translate_profile_to_prefs as tp_pw
import invisible_core as ic
a = tp_pw(gp_pw(42, fixed_gpu_class=fgc_pw(42)))
b = ic.translate_profile_to_prefs(ic.generate_profile(42, fixed_gpu_class=ic.forced_gpu_class(42)))
assert a == b
@pytest.mark.unit
def test_core_imports_without_playwright(monkeypatch):
# invisible_core must be importable even if playwright is absent. We simulate
# by asserting the core modules never pulled playwright into sys.modules as a
# side effect of their own import (the wrapper may have, but core must not need it).
import sys
import invisible_core # noqa: F401
# invisible_core itself must not hard-depend on playwright at import time.
core_file = importlib.import_module("invisible_core").__file__
assert "invisible_core" in core_file