pub const FINGERPRINT_PAYLOAD_SOURCE: &str = "/**\n * Shared fingerprint init payload -- the one copy the three implementations run.\n *\n * This file is not a module. It is source text: every implementation reads it\n * verbatim and sends it to `Page.addScriptToEvaluateOnNewDocument`, which runs\n * a classic script, so it must contain no `import` and no `export`.\n *\n * The same bytes live in three places, because npm, PyPI and crates.io each\n * package one directory and none of them can reference a file outside it:\n *\n * - js/src/fingerprint/init-payload.js\n * - python/src/browser_commander/fingerprint/init_payload.js\n * - rust/src/fingerprint/init_payload.js\n *\n * `scripts/check-shared-fingerprint-assets.sh` fails the build when they differ.\n * Editing one and forgetting the others is the failure mode that rotted\n * selenium-stealth and playwright_stealth -- see\n * docs/case-studies/issue-79/prior-art.md -- so the check exists to make a\n * silent divergence impossible rather than unlikely.\n */\n\n/**\n * Runs inside the page, before any page script, in every frame.\n *\n * @param {object} config Plain JSON produced by `buildInitScriptConfig`.\n */\nfunction fingerprintPayload(config) {\n const scope = globalThis;\n // Playwright and Puppeteer both re-inject init scripts on same-document\n // navigations in some versions; patching twice would stack getters and make\n // the descriptor chain observable.\n const marker = \'__browserCommanderFingerprint\';\n if (scope[marker]) {\n return;\n }\n Object.defineProperty(scope, marker, {\n value: true,\n configurable: true,\n enumerable: false,\n writable: false,\n });\n\n const nativeSources = new WeakMap();\n const functionToString = Function.prototype.toString;\n const objectDefineProperty = Object.defineProperty;\n\n /** Make a replacement report `[native code]`, like the accessor it replaces. */\n function asNative(fn, name) {\n try {\n objectDefineProperty(fn, \'name\', {\n value: name,\n configurable: true,\n enumerable: false,\n writable: false,\n });\n } catch {\n // A frozen function name is not worth failing the whole patch over.\n }\n nativeSources.set(fn, `function ${name}() { [native code] }`);\n return fn;\n }\n\n const patchedToString = function toString() {\n const source = nativeSources.get(this);\n return source === undefined ? functionToString.call(this) : source;\n };\n nativeSources.set(patchedToString, functionToString.call(functionToString));\n Function.prototype.toString = patchedToString;\n\n /**\n * Replace a native accessor on a prototype, keeping the shape a real one has:\n * an enumerable, configurable getter with no setter, named `get <property>`.\n */\n function defineNativeGetter(target, property, produce) {\n if (!target) {\n return;\n }\n const descriptor = Object.getOwnPropertyDescriptor(target, property);\n if (descriptor && !descriptor.configurable) {\n return;\n }\n const getter = asNative(() => produce(), `get ${property}`);\n try {\n objectDefineProperty(target, property, {\n get: getter,\n set: undefined,\n enumerable: descriptor ? descriptor.enumerable : true,\n configurable: true,\n });\n } catch {\n // Some engines refuse redefinition on cross-origin-ish prototypes.\n }\n }\n\n const navigatorPrototype = scope.Navigator && scope.Navigator.prototype;\n const workerNavigatorPrototype =\n scope.WorkerNavigator && scope.WorkerNavigator.prototype;\n\n function defineNavigator(property, value) {\n defineNativeGetter(navigatorPrototype, property, () => value);\n defineNativeGetter(workerNavigatorPrototype, property, () => value);\n }\n\n if (config.webdriver !== undefined) {\n defineNavigator(\'webdriver\', config.webdriver);\n }\n if (config.deviceMemory !== undefined) {\n defineNavigator(\'deviceMemory\', config.deviceMemory);\n }\n if (config.hardwareConcurrency !== undefined) {\n defineNavigator(\'hardwareConcurrency\', config.hardwareConcurrency);\n }\n if (config.vendor !== undefined) {\n defineNavigator(\'vendor\', config.vendor);\n }\n if (config.platform !== undefined) {\n defineNavigator(\'platform\', config.platform);\n }\n if (config.maxTouchPoints !== undefined) {\n defineNavigator(\'maxTouchPoints\', config.maxTouchPoints);\n }\n if (config.doNotTrack !== undefined) {\n defineNativeGetter(\n navigatorPrototype,\n \'doNotTrack\',\n () => config.doNotTrack\n );\n }\n if (config.languages !== undefined) {\n // navigator.languages is a frozen array in Chrome; hand out a fresh frozen\n // copy so page code cannot mutate the one we hold.\n defineNavigator(\'language\', config.languages[0]);\n defineNativeGetter(navigatorPrototype, \'languages\', () =>\n Object.freeze(config.languages.slice())\n );\n defineNativeGetter(workerNavigatorPrototype, \'languages\', () =>\n Object.freeze(config.languages.slice())\n );\n }\n\n const screenPrototype = scope.Screen && scope.Screen.prototype;\n if (config.screen) {\n for (const property of Object.keys(config.screen)) {\n const value = config.screen[property];\n defineNativeGetter(screenPrototype, property, () => value);\n }\n }\n\n if (config.webgl) {\n // 0x9245/0x9246 are UNMASKED_VENDOR_WEBGL and UNMASKED_RENDERER_WEBGL from\n // the WEBGL_debug_renderer_info extension; 0x1F00/0x1F01 are the plain\n // VENDOR and RENDERER constants that need no extension at all.\n const replacements = new Map();\n if (config.webgl.unmaskedVendor !== undefined) {\n replacements.set(0x9245, config.webgl.unmaskedVendor);\n }\n if (config.webgl.unmaskedRenderer !== undefined) {\n replacements.set(0x9246, config.webgl.unmaskedRenderer);\n }\n if (config.webgl.vendor !== undefined) {\n replacements.set(0x1f00, config.webgl.vendor);\n }\n if (config.webgl.renderer !== undefined) {\n replacements.set(0x1f01, config.webgl.renderer);\n }\n for (const name of [\'WebGLRenderingContext\', \'WebGL2RenderingContext\']) {\n const prototype = scope[name] && scope[name].prototype;\n if (!prototype || typeof prototype.getParameter !== \'function\') {\n continue;\n }\n const original = prototype.getParameter;\n const patched = asNative(function getParameter(parameter) {\n if (replacements.has(parameter)) {\n return replacements.get(parameter);\n }\n return original.call(this, parameter);\n }, \'getParameter\');\n prototype.getParameter = patched;\n }\n }\n}\n";Expand description
The shared payload source, embedded at compile time.