(function () { 'use strict'; var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; // @ts-expect-error const IS_WORKER_SCOPE = !self.document && self.WorkerGlobalScope; // Detect Browser function getEngine() { const x = [].constructor; try { (-1).toFixed(-1); } catch (err) { return err.message.length + (x + '').split(x.name).join('').length; } } const ENGINE_IDENTIFIER = getEngine(); const IS_BLINK = ENGINE_IDENTIFIER == 80; const IS_GECKO = ENGINE_IDENTIFIER == 58; const IS_WEBKIT = ENGINE_IDENTIFIER == 77; const JS_ENGINE = ({ 80: 'V8', 58: 'SpiderMonkey', 77: 'JavaScriptCore', })[ENGINE_IDENTIFIER] || null; const LIKE_BRAVE = IS_BLINK && 'flat' in Array.prototype /* Chrome 69 */ && !('ReportingObserver' in self /* Brave */); function braveBrowser() { const brave = ('brave' in navigator && // @ts-ignore Object.getPrototypeOf(navigator.brave).constructor.name == 'Brave' && // @ts-ignore navigator.brave.isBrave.toString() == 'function isBrave() { [native code] }'); return brave; } function getBraveMode() { const mode = { unknown: false, allow: false, standard: false, strict: false, }; try { // strict mode adds float frequency data AnalyserNode const strictMode = () => { try { window.OfflineAudioContext = ( // @ts-ignore OfflineAudioContext || webkitOfflineAudioContext); } catch (err) { } if (!window.OfflineAudioContext) { return false; } const context = new OfflineAudioContext(1, 1, 44100); const analyser = context.createAnalyser(); const data = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData(data); const strict = new Set(data).size > 1; // native only has -Infinity return strict; }; if (strictMode()) { mode.strict = true; return mode; } // standard and strict mode do not have chrome plugins const chromePlugins = /(Chrom(e|ium)|Microsoft Edge) PDF (Plugin|Viewer)/; const pluginsList = [...navigator.plugins]; const hasChromePlugins = pluginsList .filter((plugin) => chromePlugins.test(plugin.name)).length == 2; if (pluginsList.length && !hasChromePlugins) { mode.standard = true; return mode; } mode.allow = true; return mode; } catch (e) { mode.unknown = true; return mode; } } const getBraveUnprotectedParameters = (parameters) => { const blocked = new Set([ 'FRAGMENT_SHADER.HIGH_FLOAT.precision', 'FRAGMENT_SHADER.HIGH_FLOAT.rangeMax', 'FRAGMENT_SHADER.HIGH_FLOAT.rangeMin', 'FRAGMENT_SHADER.HIGH_INT.precision', 'FRAGMENT_SHADER.HIGH_INT.rangeMax', 'FRAGMENT_SHADER.HIGH_INT.rangeMin', 'FRAGMENT_SHADER.LOW_FLOAT.precision', 'FRAGMENT_SHADER.LOW_FLOAT.rangeMax', 'FRAGMENT_SHADER.LOW_FLOAT.rangeMin', 'FRAGMENT_SHADER.MEDIUM_FLOAT.precision', 'FRAGMENT_SHADER.MEDIUM_FLOAT.rangeMax', 'FRAGMENT_SHADER.MEDIUM_FLOAT.rangeMin', 'MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS', 'MAX_COMBINED_UNIFORM_BLOCKS', 'MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS', 'MAX_DRAW_BUFFERS_WEBGL', 'MAX_FRAGMENT_INPUT_COMPONENTS', 'MAX_FRAGMENT_UNIFORM_BLOCKS', 'MAX_FRAGMENT_UNIFORM_COMPONENTS', 'MAX_TEXTURE_MAX_ANISOTROPY_EXT', 'MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS', 'MAX_UNIFORM_BUFFER_BINDINGS', 'MAX_VARYING_COMPONENTS', 'MAX_VERTEX_OUTPUT_COMPONENTS', 'MAX_VERTEX_UNIFORM_BLOCKS', 'MAX_VERTEX_UNIFORM_COMPONENTS', 'SHADING_LANGUAGE_VERSION', 'UNMASKED_RENDERER_WEBGL', 'UNMASKED_VENDOR_WEBGL', 'VERSION', 'VERTEX_SHADER.HIGH_FLOAT.precision', 'VERTEX_SHADER.HIGH_FLOAT.rangeMax', 'VERTEX_SHADER.HIGH_FLOAT.rangeMin', 'VERTEX_SHADER.HIGH_INT.precision', 'VERTEX_SHADER.HIGH_INT.rangeMax', 'VERTEX_SHADER.HIGH_INT.rangeMin', 'VERTEX_SHADER.LOW_FLOAT.precision', 'VERTEX_SHADER.LOW_FLOAT.rangeMax', 'VERTEX_SHADER.LOW_FLOAT.rangeMin', 'VERTEX_SHADER.MEDIUM_FLOAT.precision', 'VERTEX_SHADER.MEDIUM_FLOAT.rangeMax', 'VERTEX_SHADER.MEDIUM_FLOAT.rangeMin', ]); const safeParameters = Object.keys(parameters).reduce((acc, curr) => { if (blocked.has(curr)) { return acc; } acc[curr] = parameters[curr]; return acc; }, {}); return safeParameters; }; // system const getOS = (userAgent) => { const os = ( // order is important /windows phone/ig.test(userAgent) ? 'Windows Phone' : /win(dows|16|32|64|95|98|nt)|wow64/ig.test(userAgent) ? 'Windows' : /android/ig.test(userAgent) ? 'Android' : /cros/ig.test(userAgent) ? 'Chrome OS' : /linux/ig.test(userAgent) ? 'Linux' : /ipad/ig.test(userAgent) ? 'iPad' : /iphone/ig.test(userAgent) ? 'iPhone' : /ipod/ig.test(userAgent) ? 'iPod' : /ios/ig.test(userAgent) ? 'iOS' : /mac/ig.test(userAgent) ? 'Mac' : 'Other'); return os; }; function getReportedPlatform(userAgent, platform) { // user agent os lie const userAgentOS = ( // order is important /win(dows|16|32|64|95|98|nt)|wow64/ig.test(userAgent) ? "Windows" /* PlatformClassifier.WINDOWS */ : /android|linux|cros/ig.test(userAgent) ? "Linux" /* PlatformClassifier.LINUX */ : /(i(os|p(ad|hone|od)))|mac/ig.test(userAgent) ? "Apple" /* PlatformClassifier.APPLE */ : "Other" /* PlatformClassifier.OTHER */); if (!platform) return [userAgentOS]; const platformOS = ( // order is important /win/ig.test(platform) ? "Windows" /* PlatformClassifier.WINDOWS */ : /android|arm|linux/ig.test(platform) ? "Linux" /* PlatformClassifier.LINUX */ : /(i(os|p(ad|hone|od)))|mac/ig.test(platform) ? "Apple" /* PlatformClassifier.APPLE */ : "Other" /* PlatformClassifier.OTHER */); return [userAgentOS, platformOS]; } const { userAgent: navUserAgent, platform: navPlatform } = self.navigator || {}; const [USER_AGENT_OS, PLATFORM_OS] = getReportedPlatform(navUserAgent, navPlatform); const decryptUserAgent = ({ ua, os, isBrave }) => { const apple = /ipad|iphone|ipod|ios|mac/ig.test(os); const isOpera = /OPR\//g.test(ua); const isVivaldi = /Vivaldi/g.test(ua); const isDuckDuckGo = /DuckDuckGo/g.test(ua); const isYandex = /YaBrowser/g.test(ua); const paleMoon = ua.match(/(palemoon)\/(\d+)./i); const edge = ua.match(/(edgios|edg|edge|edga)\/(\d+)./i); const edgios = edge && /edgios/i.test(edge[1]); const chromium = ua.match(/(crios|chrome)\/(\d+)./i); const firefox = ua.match(/(fxios|firefox)\/(\d+)./i); const likeSafari = (/AppleWebKit/g.test(ua) && /Safari/g.test(ua)); const safari = (likeSafari && !firefox && !chromium && !edge && ua.match(/(version)\/(\d+)\.(\d|\.)+\s(mobile|safari)/i)); if (chromium) { const browser = chromium[1]; const version = chromium[2]; const like = (isOpera ? ' Opera' : isVivaldi ? ' Vivaldi' : isDuckDuckGo ? ' DuckDuckGo' : isYandex ? ' Yandex' : edge ? ' Edge' : isBrave ? ' Brave' : ''); return `${browser} ${version}${like}`; } else if (edgios) { const browser = edge[1]; const version = edge[2]; return `${browser} ${version}`; } else if (firefox) { const browser = paleMoon ? paleMoon[1] : firefox[1]; const version = paleMoon ? paleMoon[2] : firefox[2]; return `${browser} ${version}`; } else if (apple && safari) { const browser = 'Safari'; const version = safari[2]; return `${browser} ${version}`; } return 'unknown'; }; const getUserAgentPlatform = ({ userAgent, excludeBuild = true }) => { if (!userAgent) { return 'unknown'; } // patterns const nonPlatformParenthesis = /\((khtml|unlike|vizio|like gec|internal dummy|org\.eclipse|openssl|ipv6|via translate|safari|cardamon).+|xt\d+\)/ig; const parenthesis = /\((.+)\)/; const android = /((android).+)/i; const androidNoise = /^(linux|[a-z]|wv|mobile|[a-z]{2}(-|_)[a-z]{2}|[a-z]{2})$|windows|(rv:|trident|webview|iemobile).+/i; const androidBuild = /build\/.+\s|\sbuild\/.+/i; const androidRelease = /android( |-)\d+/i; const windows = /((windows).+)/i; const windowsNoise = /^(windows|ms(-|)office|microsoft|compatible|[a-z]|x64|[a-z]{2}(-|_)[a-z]{2}|[a-z]{2})$|(rv:|outlook|ms(-|)office|microsoft|trident|\.net|msie|httrack|media center|infopath|aol|opera|iemobile|webbrowser).+/i; const windows64bitCPU = /w(ow|in)64/i; const cros = /cros/i; const crosNoise = /^([a-z]|x11|[a-z]{2}(-|_)[a-z]{2}|[a-z]{2})$|(rv:|trident).+/i; const crosBuild = /\d+\.\d+\.\d+/i; const linux = /linux|x11|ubuntu|debian/i; const linuxNoise = /^([a-z]|x11|unknown|compatible|[a-z]{2}(-|_)[a-z]{2}|[a-z]{2})$|(rv:|java|oracle|\+http|http|unknown|mozilla|konqueror|valve).+/i; const apple = /(cpu iphone|cpu os|iphone os|mac os|macos|intel os|ppc mac).+/i; const appleNoise = /^([a-z]|macintosh|compatible|mimic|[a-z]{2}(-|_)[a-z]{2}|[a-z]{2}|rv|\d+\.\d+)$|(rv:|silk|valve).+/i; const appleRelease = /(ppc |intel |)(mac|mac |)os (x |x|)(\d{2}(_|\.)\d{1,2}|\d{2,})/i; const otherOS = /((symbianos|nokia|blackberry|morphos|mac).+)|\/linux|freebsd|symbos|series \d+|win\d+|unix|hp-ux|bsdi|bsd|x86_64/i; const isDevice = (list, device) => list.filter((x) => device.test(x)).length; userAgent = userAgent.trim().replace(/\s{2,}/, ' ').replace(nonPlatformParenthesis, ''); if (parenthesis.test(userAgent)) { const platformSection = userAgent.match(parenthesis)[0]; const identifiers = platformSection.slice(1, -1).replace(/,/g, ';').split(';').map((x) => x.trim()); if (isDevice(identifiers, android)) { return identifiers // @ts-ignore .map((x) => androidRelease.test(x) ? androidRelease.exec(x)[0].replace('-', ' ') : x) .filter((x) => !(androidNoise.test(x))) .join(' ') .replace((excludeBuild ? androidBuild : ''), '') .trim().replace(/\s{2,}/, ' '); } else if (isDevice(identifiers, windows)) { return identifiers .filter((x) => !(windowsNoise.test(x))) .join(' ') .replace(/\sNT (\d+\.\d+)/, (match, version) => { return (version == '10.0' ? ' 10' : version == '6.3' ? ' 8.1' : version == '6.2' ? ' 8' : version == '6.1' ? ' 7' : version == '6.0' ? ' Vista' : version == '5.2' ? ' XP Pro' : version == '5.1' ? ' XP' : version == '5.0' ? ' 2000' : version == '4.0' ? match : ' ' + version); }) .replace(windows64bitCPU, '(64-bit)') .trim().replace(/\s{2,}/, ' '); } else if (isDevice(identifiers, cros)) { return identifiers .filter((x) => !(crosNoise.test(x))) .join(' ') .replace((excludeBuild ? crosBuild : ''), '') .trim().replace(/\s{2,}/, ' '); } else if (isDevice(identifiers, linux)) { return identifiers .filter((x) => !(linuxNoise.test(x))) .join(' ') .trim().replace(/\s{2,}/, ' '); } else if (isDevice(identifiers, apple)) { return identifiers .map((x) => { if (appleRelease.test(x)) { // @ts-ignore const release = appleRelease.exec(x)[0]; const versionMap = { '10_7': 'Lion', '10_8': 'Mountain Lion', '10_9': 'Mavericks', '10_10': 'Yosemite', '10_11': 'El Capitan', '10_12': 'Sierra', '10_13': 'High Sierra', '10_14': 'Mojave', '10_15': 'Catalina', '11': 'Big Sur', '12': 'Monterey', '13': 'Ventura', }; const version = ((/(\d{2}(_|\.)\d{1,2}|\d{2,})/.exec(release) || [])[0] || '').replace(/\./g, '_'); const isOSX = /^10/.test(version); const id = isOSX ? version : (/^\d{2,}/.exec(version) || [])[0]; const codeName = versionMap[id]; return codeName ? `macOS ${codeName}` : release; } return x; }) .filter((x) => !(appleNoise.test(x))) .join(' ') .replace(/\slike mac.+/ig, '') .trim().replace(/\s{2,}/, ' '); } else { const other = identifiers.filter((x) => otherOS.test(x)); if (other.length) { return other.join(' ').trim().replace(/\s{2,}/, ' '); } return identifiers.join(' '); } } else { return 'unknown'; } }; const computeWindowsRelease = ({ platform, platformVersion, fontPlatformVersion }) => { if ((platform != 'Windows') || !(IS_BLINK && CSS.supports('accent-color', 'initial'))) { return; } const platformVersionNumber = +(/(\d+)\./.exec(platformVersion) || [])[1]; // https://github.com/WICG/ua-client-hints/issues/220#issuecomment-870858413 // https://docs.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11 // https://docs.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-guidance const release = { '0.1.0': '7', '0.2.0': '8', '0.3.0': '8.1', '1.0.0': '10 (1507)', '2.0.0': '10 (1511)', '3.0.0': '10 (1607)', '4.0.0': '10 (1703)', '5.0.0': '10 (1709)', '6.0.0': '10 (1803)', '7.0.0': '10 (1809)', '8.0.0': '10 (1903|1909)', '10.0.0': '10 (2004|20H2|21H1)', '11.0.0': '10', '12.0.0': '10', }; const oldFontPlatformVersionNumber = (/7|8\.1|8/.exec(fontPlatformVersion) || [])[0]; const version = (platformVersionNumber >= 13 ? '11' : platformVersionNumber == 0 && oldFontPlatformVersionNumber ? oldFontPlatformVersionNumber : (release[platformVersion] || 'Unknown')); return (`Windows ${version} [${platformVersion}]`); }; // attempt restore from User-Agent Reduction const isUAPostReduction = (userAgent) => { const matcher = /Mozilla\/5\.0 \((Macintosh; Intel Mac OS X 10_15_7|Windows NT 10\.0; Win64; x64|(X11; (CrOS|Linux) x86_64)|(Linux; Android 10(; K|)))\) AppleWebKit\/537\.36 \(KHTML, like Gecko\) Chrome\/\d+\.0\.0\.0( Mobile|) Safari\/537\.36/; const unifiedPlatform = (matcher.exec(userAgent) || [])[1]; return IS_BLINK && !!unifiedPlatform; }; const createPerformanceLogger = () => { const log = {}; let total = 0; return { logTestResult: ({ test, passed, time = 0 }) => { total += time; const timeString = `${time.toFixed(2)}ms`; log[test] = timeString; const color = passed ? '#4cca9f' : 'lightcoral'; const result = passed ? 'passed' : 'failed'; const symbol = passed ? 'āœ”' : '-'; return console.log(`%c${symbol}${time ? ` (${timeString})` : ''} ${test} ${result}`, `color:${color}`); }, getLog: () => log, getTotal: () => total, }; }; const performanceLogger = createPerformanceLogger(); const { logTestResult } = performanceLogger; const createTimer = () => { let start = 0; const log = []; return { stop: () => { if (start) { log.push(performance.now() - start); return log.reduce((acc, n) => acc += n, 0); } return start; }, start: () => { start = performance.now(); return start; }, }; }; const queueEvent = (timer, delay = 0) => { timer.stop(); return new Promise((resolve) => setTimeout(() => resolve(timer.start()), delay)) .catch((e) => { }); }; const formatEmojiSet = (emojiSet, limit = 3) => { const maxLen = (limit * 2) + 3; const list = (emojiSet || []); return list.length > maxLen ? `${emojiSet.slice(0, limit).join('')}...${emojiSet.slice(-limit).join('')}` : list.join(''); }; const EMOJIS = [ [128512], [9786], [129333, 8205, 9794, 65039], [9832], [9784], [9895], [8265], [8505], [127987, 65039, 8205, 9895, 65039], [129394], [9785], [9760], [129489, 8205, 129456], [129487, 8205, 9794, 65039], [9975], [129489, 8205, 129309, 8205, 129489], [9752], [9968], [9961], [9972], [9992], [9201], [9928], [9730], [9969], [9731], [9732], [9976], [9823], [9937], [9000], [9993], [9999], [128105, 8205, 10084, 65039, 8205, 128139, 8205, 128104], [128104, 8205, 128105, 8205, 128103, 8205, 128102], [128104, 8205, 128105, 8205, 128102], // android 11 [128512], [169], [174], [8482], [128065, 65039, 8205, 128488, 65039], // other [10002], [9986], [9935], [9874], [9876], [9881], [9939], [9879], [9904], [9905], [9888], [9762], [9763], [11014], [8599], [10145], [11013], [9883], [10017], [10013], [9766], [9654], [9197], [9199], [9167], [9792], [9794], [10006], [12336], [9877], [9884], [10004], [10035], [10055], [9724], [9642], [10083], [10084], [9996], [9757], [9997], [10052], [9878], [8618], [9775], [9770], [9774], [9745], [10036], [127344], [127359], ].map((emojiCode) => String.fromCodePoint(...emojiCode)); const CSS_FONT_FAMILY = ` 'Segoe Fluent Icons', 'Ink Free', 'Bahnschrift', 'Segoe MDL2 Assets', 'HoloLens MDL2 Assets', 'Leelawadee UI', 'Javanese Text', 'Segoe UI Emoji', 'Aldhabi', 'Gadugi', 'Myanmar Text', 'Nirmala UI', 'Lucida Console', 'Cambria Math', 'Bai Jamjuree', 'Chakra Petch', 'Charmonman', 'Fahkwang', 'K2D', 'Kodchasan', 'KoHo', 'Sarabun', 'Srisakdi', 'Galvji', 'MuktaMahee Regular', 'InaiMathi Bold', 'American Typewriter Semibold', 'Futura Bold', 'SignPainter-HouseScript Semibold', 'PingFang HK Light', 'Kohinoor Devanagari Medium', 'Luminari', 'Geneva', 'Helvetica Neue', 'Droid Sans Mono', 'Dancing Script', 'Roboto', 'Ubuntu', 'Liberation Mono', 'Source Code Pro', 'DejaVu Sans', 'OpenSymbol', 'Chilanka', 'Cousine', 'Arimo', 'Jomolhari', 'MONO', 'Noto Color Emoji', sans-serif !important `; const hashSlice = (x) => !x ? x : x.slice(0, 8); function getGpuBrand(gpu) { if (!gpu) return null; const gpuBrandMatcher = /(adreno|amd|apple|intel|llvm|mali|microsoft|nvidia|parallels|powervr|samsung|swiftshader|virtualbox|vmware)/i; const brand = (/radeon/i.test(gpu) ? 'AMD' : /geforce/i.test(gpu) ? 'NVIDIA' : (gpuBrandMatcher.exec(gpu)?.[0] || 'other').toLocaleUpperCase()); return brand; } // collect fingerprints for analysis const Analysis = {}; // use if needed to stable fingerprint const LowerEntropy = { AUDIO: false, CANVAS: false, FONTS: false, SCREEN: false, TIME_ZONE: false, WEBGL: false, }; // template views function patch(oldEl, newEl, fn) { if (!oldEl) return null; oldEl.parentNode?.replaceChild(newEl, oldEl); return typeof fn === 'function' ? fn() : true; } function html(templateStr, ...expressionSet) { const template = document.createElement('template'); template.innerHTML = templateStr.map((s, i) => `${s}${expressionSet[i] || ''}`).join(''); return document.importNode(template.content, true); } // template helpers const HTMLNote = { UNKNOWN: 'unknown', UNSUPPORTED: 'unsupported', BLOCKED: 'blocked', LIED: 'lied', SECRET: 'secret', }; const count = (arr) => arr && arr.constructor.name === 'Array' ? '' + (arr.length) : '0'; const getDiffs = ({ stringA, stringB, charDiff = false, decorate = (diff) => `[${diff}]` }) => { if (!stringA || !stringB) return; const splitter = charDiff ? '' : ' '; const listA = ('' + stringA).split(splitter); const listB = ('' + stringB).split(splitter); const listBWithDiffs = listB.map((x, i) => { const matcher = listA[i]; const match = x == matcher; return !match ? decorate(x) : x; }); return listBWithDiffs.join(splitter); }; // modal component const modal = (name, result, linkname = 'details') => { if (!result.length) { return ''; } return ` `; }; const createErrorsCaptured = () => { const errors = []; return { getErrors: () => errors, captureError: (error, customMessage = '') => { const type = { Error: true, EvalError: true, InternalError: true, RangeError: true, ReferenceError: true, SyntaxError: true, TypeError: true, URIError: true, InvalidStateError: true, SecurityError: true, }; const hasInnerSpace = (s) => /.+(\s).+/g.test(s); // ignore AOPR noise console.error(error); // log error to educate const { name, message } = error; const trustedMessage = (!hasInnerSpace(message) ? undefined : !customMessage ? message : `${message} [${customMessage}]`); const trustedName = type[name] ? name : undefined; errors.push({ trustedName, trustedMessage }); return undefined; }, }; }; const errorsCaptured = createErrorsCaptured(); const { captureError } = errorsCaptured; const attempt = (fn, customMessage = '') => { try { return fn(); } catch (error) { if (customMessage) { return captureError(error, customMessage); } return captureError(error); } }; const caniuse = (fn, objChainList = [], args = [], method = false) => { let api; try { api = fn(); } catch (error) { return undefined; } let i; const len = objChainList.length; let chain = api; try { for (i = 0; i < len; i++) { const obj = objChainList[i]; chain = chain[obj]; } } catch (error) { return undefined; } return (method && args.length ? chain.apply(api, args) : method && !args.length ? chain.apply(api) : chain); }; // Log performance time const timer = (logStart) => { let start = 0; try { start = performance.now(); } catch (error) { captureError(error); } return (logEnd) => { let end = 0; try { end = performance.now() - start; logEnd && console.log(`${logEnd}: ${end / 1000} seconds`); return end; } catch (error) { captureError(error); return 0; } }; }; const getCapturedErrors = () => ({ data: errorsCaptured.getErrors() }); /* eslint-disable new-cap */ /* eslint-disable no-unused-vars */ // warm up while we detect lies try { speechSynthesis.getVoices(); } catch (err) { } // Collect lies detected function createLieRecords() { const records = {}; return { getRecords: () => records, documentLie: (name, lie) => { const isArray = lie instanceof Array; if (records[name]) { if (isArray) { return (records[name] = [...records[name], ...lie]); } return records[name].push(lie); } return isArray ? (records[name] = lie) : (records[name] = [lie]); }, }; } const lieRecords = createLieRecords(); const { documentLie } = lieRecords; const GHOST = ` height: 100vh; width: 100vw; position: absolute; left:-10000px; visibility: hidden; `; function getRandomValues() { return (String.fromCharCode(Math.random() * 26 + 97) + Math.random().toString(36).slice(-7)); } function getBehemothIframe(win) { try { if (!IS_BLINK) return win; const div = win.document.createElement('div'); div.setAttribute('id', getRandomValues()); div.setAttribute('style', GHOST); div.innerHTML = `
`; win.document.body.appendChild(div); const iframe = [...[...div.childNodes][0].childNodes][0]; if (!iframe) return null; const { contentWindow } = iframe || {}; if (!contentWindow) return null; const div2 = contentWindow.document.createElement('div'); div2.innerHTML = `
`; contentWindow.document.body.appendChild(div2); const iframe2 = [...[...div2.childNodes][0].childNodes][0]; return iframe2.contentWindow; } catch (error) { captureError(error, 'client blocked behemoth iframe'); return win; } } const RAND = getRandomValues(); const HAS_REFLECT = 'Reflect' in self; function isTypeError(err) { return err.constructor.name == 'TypeError'; } function failsTypeError({ spawnErr, withStack, final }) { try { spawnErr(); throw Error(); } catch (err) { if (!isTypeError(err)) return true; return withStack ? withStack(err) : false; } finally { final && final(); } } function failsWithError(fn) { try { fn(); return false; } catch (err) { return true; } } function hasKnownToString(name) { return { [`function ${name}() { [native code] }`]: true, [`function get ${name}() { [native code] }`]: true, [`function () { [native code] }`]: true, [`function ${name}() {${'\n'} [native code]${'\n'}}`]: true, [`function get ${name}() {${'\n'} [native code]${'\n'}}`]: true, [`function () {${'\n'} [native code]${'\n'}}`]: true, }; } function hasValidStack(err, reg, i = 1) { if (i === 0) return reg.test(err.message); return reg.test(err.stack.split('\n')[i]); } const AT_FUNCTION = /at Function\.toString /; const AT_OBJECT = /at Object\.toString/; const FUNCTION_INSTANCE = /at (Function\.)?\[Symbol.hasInstance\]/; // useful if < Chrome 102 const PROXY_INSTANCE = /at (Proxy\.)?\[Symbol.hasInstance\]/; // useful if < Chrome 102 const STRICT_MODE = /strict mode/; function queryLies({ scope, apiFunction, proto, obj, lieProps, }) { if (typeof apiFunction != 'function') { return { lied: 0, lieTypes: [], }; } const name = apiFunction.name.replace(/get\s/, ''); const objName = obj?.name; const nativeProto = Object.getPrototypeOf(apiFunction); let lies = { // custom lie string names ['failed illegal error']: !!obj && failsTypeError({ spawnErr: () => obj.prototype[name], }), ['failed undefined properties']: (!!obj && /^(screen|navigator)$/i.test(objName) && !!(Object.getOwnPropertyDescriptor(self[objName.toLowerCase()], name) || (HAS_REFLECT && Reflect.getOwnPropertyDescriptor(self[objName.toLowerCase()], name)))), ['failed call interface error']: failsTypeError({ spawnErr: () => { // @ts-expect-error new apiFunction(); apiFunction.call(proto); }, }), ['failed apply interface error']: failsTypeError({ spawnErr: () => { // @ts-expect-error new apiFunction(); apiFunction.apply(proto); }, }), ['failed new instance error']: failsTypeError({ // @ts-expect-error spawnErr: () => new apiFunction(), }), ['failed class extends error']: !IS_WEBKIT && failsTypeError({ spawnErr: () => { // @ts-expect-error class Fake extends apiFunction { } }, }), ['failed null conversion error']: failsTypeError({ spawnErr: () => Object.setPrototypeOf(apiFunction, null).toString(), final: () => Object.setPrototypeOf(apiFunction, nativeProto), }), ['failed toString']: (!hasKnownToString(name)[scope.Function.prototype.toString.call(apiFunction)] || !hasKnownToString('toString')[scope.Function.prototype.toString.call(apiFunction.toString)]), ['failed "prototype" in function']: 'prototype' in apiFunction, ['failed descriptor']: !!(Object.getOwnPropertyDescriptor(apiFunction, 'arguments') || Reflect.getOwnPropertyDescriptor(apiFunction, 'arguments') || Object.getOwnPropertyDescriptor(apiFunction, 'caller') || Reflect.getOwnPropertyDescriptor(apiFunction, 'caller') || Object.getOwnPropertyDescriptor(apiFunction, 'prototype') || Reflect.getOwnPropertyDescriptor(apiFunction, 'prototype') || Object.getOwnPropertyDescriptor(apiFunction, 'toString') || Reflect.getOwnPropertyDescriptor(apiFunction, 'toString')), ['failed own property']: !!(apiFunction.hasOwnProperty('arguments') || apiFunction.hasOwnProperty('caller') || apiFunction.hasOwnProperty('prototype') || apiFunction.hasOwnProperty('toString')), ['failed descriptor keys']: (Object.keys(Object.getOwnPropertyDescriptors(apiFunction)).sort().toString() != 'length,name'), ['failed own property names']: (Object.getOwnPropertyNames(apiFunction).sort().toString() != 'length,name'), ['failed own keys names']: HAS_REFLECT && (Reflect.ownKeys(apiFunction).sort().toString() != 'length,name'), // Proxy Detection ['failed object toString error']: (failsTypeError({ spawnErr: () => Object.create(apiFunction).toString(), withStack: (err) => IS_BLINK && !hasValidStack(err, AT_FUNCTION), }) || failsTypeError({ spawnErr: () => Object.create(new Proxy(apiFunction, {})).toString(), withStack: (err) => IS_BLINK && !hasValidStack(err, AT_OBJECT), })), ['failed at incompatible proxy error']: failsTypeError({ spawnErr: () => { apiFunction.arguments; apiFunction.caller; }, withStack: (err) => IS_GECKO && !hasValidStack(err, STRICT_MODE, 0), }), ['failed at toString incompatible proxy error']: failsTypeError({ spawnErr: () => { apiFunction.toString.arguments; apiFunction.toString.caller; }, withStack: (err) => IS_GECKO && !hasValidStack(err, STRICT_MODE, 0), }), ['failed at too much recursion error']: failsTypeError({ spawnErr: () => { Object.setPrototypeOf(apiFunction, Object.create(apiFunction)).toString(); }, final: () => Object.setPrototypeOf(apiFunction, nativeProto), }), }; // conditionally increase difficulty const detectProxies = (name == 'toString' || !!lieProps['Function.toString'] || !!lieProps['Permissions.query']); if (detectProxies) { const proxy1 = new Proxy(apiFunction, {}); const proxy2 = new Proxy(apiFunction, {}); const proxy3 = new Proxy(apiFunction, {}); lies = { ...lies, // Advanced Proxy Detection ['failed at too much recursion __proto__ error']: !failsTypeError({ spawnErr: () => { // @ts-expect-error apiFunction.__proto__ = proxy; apiFunction++; }, final: () => Object.setPrototypeOf(apiFunction, nativeProto), }), ['failed at chain cycle error']: !failsTypeError({ spawnErr: () => { Object.setPrototypeOf(proxy1, Object.create(proxy1)).toString(); }, final: () => Object.setPrototypeOf(proxy1, nativeProto), }), ['failed at chain cycle __proto__ error']: !failsTypeError({ spawnErr: () => { // @ts-expect-error proxy2.__proto__ = proxy2; proxy2++; }, final: () => Object.setPrototypeOf(proxy2, nativeProto), }), ['failed at reflect set proto']: HAS_REFLECT && failsTypeError({ spawnErr: () => { Reflect.setPrototypeOf(apiFunction, Object.create(apiFunction)); RAND in apiFunction; throw new TypeError(); }, final: () => Object.setPrototypeOf(apiFunction, nativeProto), }), ['failed at reflect set proto proxy']: HAS_REFLECT && !failsTypeError({ spawnErr: () => { Reflect.setPrototypeOf(proxy3, Object.create(proxy3)); RAND in proxy3; }, final: () => Object.setPrototypeOf(proxy3, nativeProto), }), ['failed at instanceof check error']: IS_BLINK && (failsTypeError({ spawnErr: () => { apiFunction instanceof apiFunction; }, withStack: (err) => !hasValidStack(err, FUNCTION_INSTANCE), }) || failsTypeError({ spawnErr: () => { const proxy = new Proxy(apiFunction, {}); proxy instanceof proxy; }, withStack: (err) => !hasValidStack(err, PROXY_INSTANCE), })), ['failed at define properties']: IS_BLINK && HAS_REFLECT && failsWithError(() => { Object.defineProperty(apiFunction, '', { configurable: true }).toString(); Reflect.deleteProperty(apiFunction, ''); }), }; } const lieTypes = Object.keys(lies).filter((key) => !!lies[key]); return { lied: lieTypes.length, lieTypes, }; } function createLieDetector(scope) { const isSupported = (obj) => typeof obj != 'undefined' && !!obj; const props = {}; // lie list and detail const propsSearched = []; // list of properties searched return { getProps: () => props, getPropsSearched: () => propsSearched, searchLies: (fn, config) => { const { target, ignore } = config || {}; let obj; // check if api is blocked or not supported try { obj = fn(); if (!isSupported(obj)) { return; } } catch (error) { return; } const interfaceObject = !!obj.prototype ? obj.prototype : obj; [...new Set([ ...Object.getOwnPropertyNames(interfaceObject), ...Object.keys(interfaceObject), // backup ])].sort().forEach((name) => { const skip = (name == 'constructor' || (target && !new Set(target).has(name)) || (ignore && new Set(ignore).has(name))); if (skip) return; const objectNameString = /\s(.+)\]/; const apiName = `${obj.name ? obj.name : objectNameString.test(obj) ? objectNameString.exec(obj)?.[1] : undefined}.${name}`; propsSearched.push(apiName); try { const proto = obj.prototype ? obj.prototype : obj; let res; // response from getLies // search if function try { const apiFunction = proto[name]; // may trigger TypeError if (typeof apiFunction == 'function') { res = queryLies({ scope, apiFunction: proto[name], proto, obj: null, lieProps: props, }); if (res.lied) { documentLie(apiName, res.lieTypes); return (props[apiName] = res.lieTypes); } return; } // since there is no TypeError and the typeof is not a function, // handle invalid values and ignore name, length, and constants if (name != 'name' && name != 'length' && name[0] !== name[0].toUpperCase()) { const lie = ['failed descriptor.value undefined']; documentLie(apiName, lie); return (props[apiName] = lie); } } catch (error) { } // else search getter function // @ts-ignore const getterFunction = Object.getOwnPropertyDescriptor(proto, name).get; res = queryLies({ scope, apiFunction: getterFunction, proto, obj, lieProps: props, }); // send the obj for special tests if (res.lied) { documentLie(apiName, res.lieTypes); return (props[apiName] = res.lieTypes); } return; } catch (error) { const lie = `failed prototype test execution`; documentLie(apiName, lie); return (props[apiName] = [lie]); } }); }, }; } function getPhantomIframe() { if (IS_WORKER_SCOPE) return { iframeWindow: self }; try { const numberOfIframes = self.length; const frag = new DocumentFragment(); const div = document.createElement('div'); const id = getRandomValues(); div.setAttribute('id', id); frag.appendChild(div); div.innerHTML = `
`; document.body.appendChild(frag); const iframeWindow = self[numberOfIframes]; const phantomWindow = getBehemothIframe(iframeWindow); return { iframeWindow: phantomWindow || self, div }; } catch (error) { captureError(error, 'client blocked phantom iframe'); return { iframeWindow: self }; } } const { iframeWindow: PHANTOM_DARKNESS, div: PARENT_PHANTOM } = getPhantomIframe() || {}; function getPrototypeLies(scope) { const lieDetector = createLieDetector(scope); const { searchLies, } = lieDetector; // search lies: remove target to search all properties // test Function.toString first to determine the depth of the search searchLies(() => Function, { target: [ 'toString', ], ignore: [ 'caller', 'arguments', ], }); // other APIs searchLies(() => AnalyserNode); searchLies(() => AudioBuffer, { target: [ 'copyFromChannel', 'getChannelData', ], }); searchLies(() => BiquadFilterNode, { target: [ 'getFrequencyResponse', ], }); searchLies(() => CanvasRenderingContext2D, { target: [ 'getImageData', 'getLineDash', 'isPointInPath', 'isPointInStroke', 'measureText', 'quadraticCurveTo', 'fillText', 'strokeText', 'font', ], }); searchLies(() => CSSStyleDeclaration, { target: [ 'setProperty', ], }); // @ts-expect-error searchLies(() => CSS2Properties, { target: [ 'setProperty', ], }); searchLies(() => Date, { target: [ 'getDate', 'getDay', 'getFullYear', 'getHours', 'getMinutes', 'getMonth', 'getTime', 'getTimezoneOffset', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMonth', 'setSeconds', 'setTime', 'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString', 'toLocaleTimeString', 'toString', 'toTimeString', 'valueOf', ], }); // @ts-expect-error if not supported searchLies(() => GPU, { target: [ 'requestAdapter', ], }); // @ts-expect-error if not supported searchLies(() => GPUAdapter, { target: [ 'requestAdapterInfo', ], }); searchLies(() => Intl.DateTimeFormat, { target: [ 'format', 'formatRange', 'formatToParts', 'resolvedOptions', ], }); searchLies(() => Document, { target: [ 'createElement', 'createElementNS', 'getElementById', 'getElementsByClassName', 'getElementsByName', 'getElementsByTagName', 'getElementsByTagNameNS', 'referrer', 'write', 'writeln', ], ignore: [ // Gecko 'onreadystatechange', 'onmouseenter', 'onmouseleave', ], }); searchLies(() => DOMRect); searchLies(() => DOMRectReadOnly); searchLies(() => Element, { target: [ 'append', 'appendChild', 'getBoundingClientRect', 'getClientRects', 'insertAdjacentElement', 'insertAdjacentHTML', 'insertAdjacentText', 'insertBefore', 'prepend', 'replaceChild', 'replaceWith', 'setAttribute', ], }); searchLies(() => FontFace, { target: [ 'family', 'load', 'status', ], }); searchLies(() => HTMLCanvasElement); searchLies(() => HTMLElement, { target: [ 'clientHeight', 'clientWidth', 'offsetHeight', 'offsetWidth', 'scrollHeight', 'scrollWidth', ], ignore: [ // Gecko 'onmouseenter', 'onmouseleave', ], }); searchLies(() => HTMLIFrameElement, { target: [ 'contentDocument', 'contentWindow', ], }); searchLies(() => IntersectionObserverEntry, { target: [ 'boundingClientRect', 'intersectionRect', 'rootBounds', ], }); searchLies(() => Math, { target: [ 'acos', 'acosh', 'asinh', 'atan', 'atan2', 'atanh', 'cbrt', 'cos', 'cosh', 'exp', 'expm1', 'log', 'log10', 'log1p', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', ], }); searchLies(() => MediaDevices, { target: [ 'enumerateDevices', 'getDisplayMedia', 'getUserMedia', ], }); searchLies(() => Navigator, { target: [ 'appCodeName', 'appName', 'appVersion', 'buildID', 'connection', 'deviceMemory', 'getBattery', 'getGamepads', 'getVRDisplays', 'hardwareConcurrency', 'language', 'languages', 'maxTouchPoints', 'mimeTypes', 'oscpu', 'platform', 'plugins', 'product', 'productSub', 'sendBeacon', 'serviceWorker', 'storage', 'userAgent', 'vendor', 'vendorSub', 'webdriver', 'gpu', ], }); searchLies(() => Node, { target: [ 'appendChild', 'insertBefore', 'replaceChild', ], }); // @ts-expect-error searchLies(() => OffscreenCanvas, { target: [ 'convertToBlob', 'getContext', ], }); // @ts-expect-error searchLies(() => OffscreenCanvasRenderingContext2D, { target: [ 'getImageData', 'getLineDash', 'isPointInPath', 'isPointInStroke', 'measureText', 'quadraticCurveTo', 'font', ], }); searchLies(() => Permissions, { target: [ 'query', ], }); searchLies(() => Range, { target: [ 'getBoundingClientRect', 'getClientRects', ], }); // @ts-expect-error searchLies(() => Intl.RelativeTimeFormat, { target: [ 'resolvedOptions', ], }); searchLies(() => Screen); searchLies(() => speechSynthesis, { target: [ 'getVoices', ], }); searchLies(() => String, { target: [ 'fromCodePoint', ], }); searchLies(() => StorageManager, { target: [ 'estimate', ], }); searchLies(() => SVGRect); searchLies(() => SVGRectElement, { target: [ 'getBBox', ], }); searchLies(() => SVGTextContentElement, { target: [ 'getExtentOfChar', 'getSubStringLength', 'getComputedTextLength', ], }); searchLies(() => TextMetrics); searchLies(() => WebGLRenderingContext, { target: [ 'bufferData', 'getParameter', 'readPixels', ], }); searchLies(() => WebGL2RenderingContext, { target: [ 'bufferData', 'getParameter', 'readPixels', ], }); /* potential targets: RTCPeerConnection Plugin PluginArray MimeType MimeTypeArray Worker History */ // return lies list and detail const props = lieDetector.getProps(); const propsSearched = lieDetector.getPropsSearched(); return { lieDetector, lieList: Object.keys(props).sort(), lieDetail: props, lieCount: Object.keys(props).reduce((acc, key) => acc + props[key].length, 0), propsSearched, }; } // start program const start = performance.now(); const { lieDetector, lieList, lieDetail, // lieCount, propsSearched, } = getPrototypeLies(PHANTOM_DARKNESS); // execute and destructure the list and detail // disregard Function.prototype.toString lies when determining if the API can be trusted const getNonFunctionToStringLies = (x) => !x ? x : x.filter((x) => !/object toString|toString incompatible proxy/.test(x)).length; let lieProps; let prototypeLies; let PROTO_BENCHMARK = 0; if (!IS_WORKER_SCOPE) { lieProps = (() => { const props = lieDetector.getProps(); return Object.keys(props).reduce((acc, key) => { acc[key] = getNonFunctionToStringLies(props[key]); return acc; }, {}); })(); prototypeLies = JSON.parse(JSON.stringify(lieDetail)); const perf = performance.now() - start; PROTO_BENCHMARK = +perf.toFixed(2); const message = `${propsSearched.length} API properties analyzed in ${PROTO_BENCHMARK}ms (${lieList.length} corrupted)`; setTimeout(() => console.log(message), 3000); } const getPluginLies = (plugins, mimeTypes) => { const lies = []; // collect lie types const pluginsOwnPropertyNames = Object.getOwnPropertyNames(plugins).filter((name) => isNaN(+name)); const mimeTypesOwnPropertyNames = Object.getOwnPropertyNames(mimeTypes).filter((name) => isNaN(+name)); // cast to array const pluginsList = [...plugins]; const mimeTypesList = [...mimeTypes]; // get initial trusted mimeType names const trustedMimeTypes = new Set(mimeTypesOwnPropertyNames); // get initial trusted plugin names const excludeDuplicates = (arr) => [...new Set(arr)]; const mimeTypeEnabledPlugins = excludeDuplicates(mimeTypesList.map((mimeType) => mimeType.enabledPlugin)); const trustedPluginNames = new Set(pluginsOwnPropertyNames); const mimeTypeEnabledPluginsNames = mimeTypeEnabledPlugins.map((plugin) => plugin && plugin.name); const trustedPluginNamesArray = [...trustedPluginNames]; trustedPluginNamesArray.forEach((name) => { const validName = new Set(mimeTypeEnabledPluginsNames).has(name); if (!validName) { trustedPluginNames.delete(name); } }); // 3. Expect MimeType object in plugins const invalidPlugins = pluginsList.filter((plugin) => { try { const validMimeType = Object.getPrototypeOf(plugin[0]).constructor.name == 'MimeType'; if (!validMimeType) { trustedPluginNames.delete(plugin.name); } return !validMimeType; } catch (error) { trustedPluginNames.delete(plugin.name); return true; // sign of tampering } }); if (invalidPlugins.length) { lies.push('missing mimetype'); } // 4. Expect valid MimeType(s) in plugin const pluginMimeTypes = pluginsList .map((plugin) => Object.values(plugin)).flat(); const pluginMimeTypesNames = pluginMimeTypes.map((mimetype) => mimetype.type); pluginMimeTypesNames.forEach((name) => { const validName = trustedMimeTypes.has(name); if (!validName) { trustedMimeTypes.delete(name); } }); pluginsList.forEach((plugin) => { const pluginMimeTypes = Object.values(plugin).map((mimetype) => mimetype.type); return pluginMimeTypes.forEach((mimetype) => { if (!trustedMimeTypes.has(mimetype)) { lies.push('invalid mimetype'); return trustedPluginNames.delete(plugin.name); } return; }); }); return { validPlugins: pluginsList.filter((plugin) => trustedPluginNames.has(plugin.name)), validMimeTypes: mimeTypesList.filter((mimeType) => trustedMimeTypes.has(mimeType.type)), lies: [...new Set(lies)], // remove duplicates }; }; const getLies = () => { const records = lieRecords.getRecords(); const totalLies = Object.keys(records).reduce((acc, key) => { acc += records[key].length; return acc; }, 0); return { data: records, totalLies }; }; // Detect proxy behavior const proxyBehavior = (x) => typeof x == 'function' ? true : false; const GIBBERS = /[cC]f|[jJ][bcdfghlmprsty]|[qQ][bcdfghjklmnpsty]|[vV][bfhjkmpt]|[xX][dkrz]|[yY]y|[zZ][fr]|[cCxXzZ]j|[bBfFgGjJkKpPvVqQtTwWyYzZ]q|[cCfFgGjJpPqQwW]v|[jJqQvV]w|[bBcCdDfFgGhHjJkKmMpPqQsSvVwWxXzZ]x|[bBfFhHjJkKmMpPqQ]z/g; // Detect gibberish const gibberish = (str, { strict = false } = {}) => { if (!str) return []; // test letter case sequence const letterCaseSequenceGibbers = []; const tests = [ /([A-Z]{3,}[a-z])/g, // ABCd /([a-z][A-Z]{3,})/g, // aBCD /([a-z][A-Z]{2,}[a-z])/g, // aBC...z /([a-z][\d]{2,}[a-z])/g, // a##...b /([A-Z][\d]{2,}[a-z])/g, // A##...b /([a-z][\d]{2,}[A-Z])/g, // a##...B ]; tests.forEach((regExp) => { const match = str.match(regExp); if (match) { return letterCaseSequenceGibbers.push(match.join(', ')); } return; }); // test letter sequence const letterSequenceGibbers = []; const clean = str.replace(/\d|\W|_/g, ' ').replace(/\s+/g, ' ').trim().split(' ').join('_'); const len = clean.length; const arr = [...clean]; arr.forEach((char, index) => { const nextIndex = index + 1; const nextChar = arr[nextIndex]; const isWordSequence = nextChar !== '_' && char !== '_' && nextIndex !== len; if (isWordSequence) { const combo = char + nextChar; if (GIBBERS.test(combo)) letterSequenceGibbers.push(combo); } }); const gibbers = [ // ignore sequence if less than 3 exist ...(!strict && (letterSequenceGibbers.length < 3) ? [] : letterSequenceGibbers), ...(!strict && (letterCaseSequenceGibbers.length < 4) ? [] : letterCaseSequenceGibbers), ]; const allow = [ // known gibbers 'bz', 'cf', 'fx', 'mx', 'vb', 'xd', 'gx', 'PCIe', 'vm', 'NVIDIAGa', ]; return gibbers.filter((x) => !allow.includes(x)); }; // WebGL Renderer helpers function compressWebGLRenderer(x) { if (!x) return; return ('' + x) .replace(/ANGLE \(|\sDirect3D.+|\sD3D.+|\svs_.+\)|\((DRM|POLARIS|LLVM).+|Mesa.+|(ATI|INTEL)-.+|Metal\s-\s.+|NVIDIA\s[\d|\.]+/ig, '') .replace(/(\s(ti|\d{1,2}GB|super)$)/ig, '') .replace(/\s{2,}/g, ' ') .trim() .replace(/((r|g)(t|)(x|s|\d) |Graphics |GeForce |Radeon (HD |Pro |))(\d+)/i, (...args) => { return `${args[1]}${args[6][0]}${args[6].slice(1).replace(/\d/g, '0')}s`; }); } const getWebGLRendererParts = (x) => { const knownParts = [ 'AMD', 'ANGLE', 'ASUS', 'ATI', 'ATI Radeon', 'ATI Technologies Inc', 'Adreno', 'Android Emulator', 'Apple', 'Apple GPU', 'Apple M1', 'Chipset', 'D3D11', 'Direct3D', 'Express Chipset', 'GeForce', 'Generation', 'Generic Renderer', 'Google', 'Google SwiftShader', 'Graphics', 'Graphics Media Accelerator', 'HD Graphics Family', 'Intel', 'Intel(R) HD Graphics', 'Intel(R) UHD Graphics', 'Iris', 'KBL Graphics', 'Mali', 'Mesa', 'Mesa DRI', 'Metal', 'Microsoft', 'Microsoft Basic Render Driver', 'Microsoft Corporation', 'NVIDIA', 'NVIDIA Corporation', 'NVIDIAGameReadyD3D', 'OpenGL', 'OpenGL Engine', 'Open Source Technology Center', 'Parallels', 'Parallels Display Adapter', 'PCIe', 'Plus Graphics', 'PowerVR', 'Pro Graphics', 'Quadro', 'Radeon', 'Radeon Pro', 'Radeon Pro Vega', 'Samsung', 'SSE2', 'VMware', 'VMware SVGA 3D', 'Vega', 'VirtualBox', 'VirtualBox Graphics Adapter', 'Vulkan', 'Xe Graphics', 'llvmpipe', ]; const parts = [...knownParts].filter((name) => ('' + x).includes(name)); return [...new Set(parts)].sort().join(', '); }; const getWebGLRendererConfidence = (x) => { if (!x) { return; } const parts = getWebGLRendererParts(x); const hasKnownParts = parts.length; const hasBlankSpaceNoise = /\s{2,}|^\s|\s$/.test(x); const hasBrokenAngleStructure = /^ANGLE/.test(x) && !(/^ANGLE \((.+)\)/.exec(x) || [])[1]; // https://chromium.googlesource.com/angle/angle/+/83fa18905d8fed4f394e4f30140a83a3e76b1577/src/gpu_info_util/SystemInfo.cpp // https://chromium.googlesource.com/angle/angle/+/83fa18905d8fed4f394e4f30140a83a3e76b1577/src/gpu_info_util/SystemInfo.h // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/gl/gl_version_info.cc /* const knownVendors = [ 'AMD', 'ARM', 'Broadcom', 'Google', 'ImgTec', 'Intel', 'Kazan', 'NVIDIA', 'Qualcomm', 'VeriSilicon', 'Vivante', 'VMWare', 'Apple', 'Unknown' ] const angle = { vendorId: (/^ANGLE \(([^,]+),/.exec(x)||[])[1] || knownVendors.find(vendor => x.includes(vendor)), deviceId: ( (x.match(/,/g)||[]).length == 2 ? (/^ANGLE \(([^,]+), ([^,]+)[,|\)]/.exec(x)||[])[2] : (/^ANGLE \(([^,]+), ([^,]+)[,|\)]/.exec(x)||[])[1] || (/^ANGLE \((.+)\)$/.exec(x)||[])[1] ).replace(/\sDirect3D.+/, '') } */ const gibbers = gibberish(x, { strict: true }).join(', '); const valid = (hasKnownParts && !hasBlankSpaceNoise && !hasBrokenAngleStructure); const confidence = (valid && !gibbers.length ? 'high' : valid && gibbers.length ? 'moderate' : 'low'); const grade = (confidence == 'high' ? 'A' : confidence == 'moderate' ? 'C' : 'F'); const warnings = new Set([ (hasBlankSpaceNoise ? 'found extra spaces' : undefined), (hasBrokenAngleStructure ? 'broken angle structure' : undefined), ]); warnings.delete(undefined); return { parts, warnings: [...warnings], gibbers, confidence, grade, }; }; // Collect trash values const createTrashBin = () => { const bin = []; return { getBin: () => bin, sendToTrash: (name, val, response = undefined) => { const proxyLike = proxyBehavior(val); const value = !proxyLike ? val : 'proxy behavior detected'; bin.push({ name, value }); return response; }, }; }; const trashBin = createTrashBin(); const { sendToTrash } = trashBin; const getTrash = () => ({ trashBin: trashBin.getBin() }); function isFontOSBad(userAgentOS, fonts) { if (!userAgentOS || !fonts || !fonts.length) return false; const fontMap = fonts.reduce((acc, x) => { acc[x] = true; return acc; }, {}); const isLikeWindows = ('Cambria Math' in fontMap || 'Nirmala UI' in fontMap || 'Leelawadee UI' in fontMap || 'HoloLens MDL2 Assets' in fontMap || 'Segoe Fluent Icons' in fontMap); const isLikeApple = ('Helvetica Neue' in fontMap || 'Luminari' in fontMap || 'PingFang HK Light' in fontMap || 'InaiMathi Bold' in fontMap || 'Galvji' in fontMap || 'Chakra Petch' in fontMap); const isLikeLinux = ('Arimo' in fontMap || 'MONO' in fontMap || 'Ubuntu' in fontMap || 'Noto Color Emoji' in fontMap || 'Dancing Script' in fontMap || 'Droid Sans Mono' in fontMap); if (isLikeWindows && userAgentOS != "Windows" /* PlatformClassifier.WINDOWS */) { return true; } else if (isLikeApple && userAgentOS != "Apple" /* PlatformClassifier.APPLE */) { return true; } else if (isLikeLinux && userAgentOS != "Linux" /* PlatformClassifier.LINUX */) { return true; } return false; } let WORKER_TYPE = ''; let WORKER_NAME = ''; async function spawnWorker() { const ask = (fn) => { try { return fn(); } catch (e) { return; } }; function getWorkerPrototypeLies(scope) { const lieDetector = createLieDetector(scope); const { searchLies, } = lieDetector; searchLies(() => Function, { target: [ 'toString', ], ignore: [ 'caller', 'arguments', ], }); // @ts-expect-error searchLies(() => WorkerNavigator, { target: [ 'deviceMemory', 'hardwareConcurrency', 'language', 'languages', 'platform', 'userAgent', ], }); // return lies list and detail const props = lieDetector.getProps(); const propsSearched = lieDetector.getPropsSearched(); return { lieDetector, lieList: Object.keys(props).sort(), lieDetail: props, lieCount: Object.keys(props).reduce((acc, key) => acc + props[key].length, 0), propsSearched, }; } const getUserAgentData = async (navigator) => { if (!('userAgentData' in navigator)) { return; } const data = await navigator.userAgentData.getHighEntropyValues(['platform', 'platformVersion', 'architecture', 'bitness', 'model', 'uaFullVersion']); const { brands, mobile } = navigator.userAgentData || {}; const compressedBrands = (brands, captureVersion = false) => brands .filter((obj) => !/Not/.test(obj.brand)).map((obj) => `${obj.brand}${captureVersion ? ` ${obj.version}` : ''}`); const removeChromium = (brands) => (brands.length > 1 ? brands.filter((brand) => !/Chromium/.test(brand)) : brands); // compress brands if (!data.brands) { data.brands = brands; } data.brandsVersion = compressedBrands(data.brands, true); data.brands = compressedBrands(data.brands); data.brandsVersion = removeChromium(data.brandsVersion); data.brands = removeChromium(data.brands); if (!data.mobile) { data.mobile = mobile; } const dataSorted = Object.keys(data).sort().reduce((acc, key) => { acc[key] = data[key]; return acc; }, {}); return dataSorted; }; const getWebglData = () => ask(() => { // @ts-ignore const canvasOffscreenWebgl = new OffscreenCanvas(256, 256); const contextWebgl = canvasOffscreenWebgl.getContext('webgl'); const rendererInfo = contextWebgl.getExtension('WEBGL_debug_renderer_info'); return { webglVendor: contextWebgl.getParameter(rendererInfo.UNMASKED_VENDOR_WEBGL), webglRenderer: contextWebgl.getParameter(rendererInfo.UNMASKED_RENDERER_WEBGL), }; }); const computeTimezoneOffset = () => { const date = new Date().getDate(); const month = new Date().getMonth(); // @ts-ignore const year = Date().split ` `[3]; // current year const format = (n) => ('' + n).length == 1 ? `0${n}` : n; const dateString = `${month + 1}/${format(date)}/${year}`; const dateStringUTC = `${year}-${format(month + 1)}-${format(date)}`; // @ts-ignore const utc = Date.parse(new Date(dateString)); const now = +new Date(dateStringUTC); return +(((utc - now) / 60000).toFixed(0)); }; const getLocale = () => { const constructors = [ 'Collator', 'DateTimeFormat', 'DisplayNames', 'ListFormat', 'NumberFormat', 'PluralRules', 'RelativeTimeFormat', ]; // @ts-ignore const locale = constructors.reduce((acc, name) => { try { const obj = new Intl[name]; if (!obj) { return acc; } const { locale } = obj.resolvedOptions() || {}; return [...acc, locale]; } catch (error) { return acc; } }, []); return [...new Set(locale)]; }; const getWorkerData = async () => { const timer = createTimer(); await queueEvent(timer); const userAgentData = await getUserAgentData(navigator).catch((error) => console.error(error)); // webgl const { webglVendor, webglRenderer } = getWebglData() || {}; // timezone & locale const timezoneOffset = computeTimezoneOffset(); // eslint-disable-next-line new-cap const timezoneLocation = Intl.DateTimeFormat().resolvedOptions().timeZone; const locale = getLocale(); // navigator const { hardwareConcurrency, language, languages, platform, userAgent, // @ts-expect-error deviceMemory, } = navigator || {}; // prototype lies await queueEvent(timer); const { // lieDetector: lieProps, lieList, lieDetail, // lieCount, // propsSearched, } = getWorkerPrototypeLies(self); // execute and destructure the list and detail // const prototypeLies = JSON.parse(JSON.stringify(lieDetail)) const protoLieLen = lieList.length; // match engine locale to system locale to determine if locale entropy is trusty let systemCurrencyLocale; const lang = ('' + language).split(',')[0]; try { systemCurrencyLocale = (1).toLocaleString((lang || undefined), { style: 'currency', currency: 'USD', currencyDisplay: 'name', minimumFractionDigits: 0, maximumFractionDigits: 0, }); } catch (e) { } const engineCurrencyLocale = (1).toLocaleString(undefined, { style: 'currency', currency: 'USD', currencyDisplay: 'name', minimumFractionDigits: 0, maximumFractionDigits: 0, }); const localeEntropyIsTrusty = engineCurrencyLocale == systemCurrencyLocale; const localeIntlEntropyIsTrusty = new Set(('' + language).split(',')).has('' + locale); const { href, pathname } = self.location || {}; const locationPathNameLie = (!href || !pathname || !/^\/(docs|creepjs|public)|\/creep.js$/.test(pathname) || !new RegExp(`${pathname}$`).test(href)); return { lied: protoLieLen || +locationPathNameLie, lies: { proto: protoLieLen ? lieDetail : false, }, locale: '' + locale, systemCurrencyLocale, engineCurrencyLocale, localeEntropyIsTrusty, localeIntlEntropyIsTrusty, timezoneOffset, timezoneLocation, deviceMemory, hardwareConcurrency, language, languages: '' + languages, platform, userAgent, webglRenderer, webglVendor, userAgentData, }; }; // Compute and communicate from worker scope const onEvent = (eventType, fn) => addEventListener(eventType, fn); const send = (source) => { return getWorkerData().then((data) => source.postMessage(data)); }; if (IS_WORKER_SCOPE) { globalThis.ServiceWorkerGlobalScope ? onEvent('message', (e) => send(e.source)) : globalThis.SharedWorkerGlobalScope ? onEvent('connect', (e) => send(e.ports[0])) : send(self); // DedicatedWorkerGlobalScope } return IS_WORKER_SCOPE ? 0 /* Scope.WORKER */ : 1 /* Scope.WINDOW */; } async function getBestWorkerScope() { try { const timer = createTimer(); await queueEvent(timer); const ask = (fn) => { try { return fn(); } catch (e) { return; } }; const hasConstructor = (x, name) => x && x.__proto__.constructor.name == name; const getDedicatedWorker = ({ scriptSource }) => new Promise((resolve) => { const giveUpOnWorker = setTimeout(() => { return resolve(null); }, 3000); const dedicatedWorker = ask(() => new Worker(scriptSource)); if (!hasConstructor(dedicatedWorker, 'Worker')) return resolve(null); dedicatedWorker.onmessage = (event) => { dedicatedWorker.terminate(); clearTimeout(giveUpOnWorker); return resolve(event.data); }; }); const getSharedWorker = ({ scriptSource }) => new Promise((resolve) => { const giveUpOnWorker = setTimeout(() => { return resolve(null); }, 3000); const sharedWorker = ask(() => new SharedWorker(scriptSource)); if (!hasConstructor(sharedWorker, 'SharedWorker')) return resolve(null); sharedWorker.port.start(); sharedWorker.port.onmessage = (event) => { sharedWorker.port.close(); clearTimeout(giveUpOnWorker); return resolve(event.data); }; }); const getServiceWorker = ({ scriptSource }) => new Promise((resolve) => { const giveUpOnWorker = setTimeout(() => { return resolve(null); }, 4000); if (!ask(() => navigator.serviceWorker.register)) return resolve(null); return navigator.serviceWorker.register(scriptSource).then((registration) => { if (!hasConstructor(registration, 'ServiceWorkerRegistration')) return resolve(null); return navigator.serviceWorker.ready.then((registration) => { // @ts-ignore registration.active.postMessage(undefined); navigator.serviceWorker.onmessage = (event) => { registration.unregister(); clearTimeout(giveUpOnWorker); return resolve(event.data); }; }); }).catch((error) => { console.error(error); clearTimeout(giveUpOnWorker); return resolve(null); }); }); const scriptSource = './creep.js'; WORKER_NAME = 'ServiceWorkerGlobalScope'; WORKER_TYPE = 'service'; // loads fast but is not available in frames let workerScope = await getServiceWorker({ scriptSource }).catch((error) => { captureError(error); console.error(error.message); return; }); if (!(workerScope || {}).userAgent) { WORKER_NAME = 'SharedWorkerGlobalScope'; WORKER_TYPE = 'shared'; // no support in Safari, iOS, and Chrome Android workerScope = await getSharedWorker({ scriptSource }).catch((error) => { captureError(error); console.error(error.message); return; }); } if (!(workerScope || {}).userAgent) { WORKER_NAME = 'DedicatedWorkerGlobalScope'; WORKER_TYPE = 'dedicated'; // device emulators can easily spoof dedicated scope workerScope = await getDedicatedWorker({ scriptSource }).catch((error) => { captureError(error); console.error(error.message); return; }); } if (!(workerScope || {}).userAgent) { return; } workerScope.system = getOS(workerScope.userAgent); workerScope.device = getUserAgentPlatform({ userAgent: workerScope.userAgent }); // detect lies const { system, userAgent, userAgentData, platform, deviceMemory, hardwareConcurrency, } = workerScope || {}; // navigator lies // skip language and languages to respect valid engine language switching bug in Chrome // these are more likely navigator lies, so don't trigger lied worker scope const workerScopeMatchLie = 'does not match worker scope'; if (platform != navigator.platform) { documentLie('Navigator.platform', workerScopeMatchLie); } if (userAgent != navigator.userAgent) { documentLie('Navigator.userAgent', workerScopeMatchLie); } if (hardwareConcurrency && (hardwareConcurrency != navigator.hardwareConcurrency)) { documentLie('Navigator.hardwareConcurrency', workerScopeMatchLie); } // @ts-ignore if (deviceMemory && (deviceMemory != navigator.deviceMemory)) { documentLie('Navigator.deviceMemory', workerScopeMatchLie); } // prototype lies if (workerScope.lies.proto) { const { proto } = workerScope.lies; const keys = Object.keys(proto); keys.forEach((key) => { const api = `WorkerGlobalScope.${key}`; const lies = proto[key]; lies.forEach((lie) => documentLie(api, lie)); }); } // user agent os lie const [userAgentOS, platformOS] = getReportedPlatform(userAgent, platform); if (userAgentOS != platformOS) { workerScope.lied = true; workerScope.lies.os = `${platformOS} platform and ${userAgentOS} user agent do not match`; documentLie('WorkerGlobalScope', workerScope.lies.os); } // user agent engine lie const decryptedName = decryptUserAgent({ ua: userAgent, os: system, isBrave: false, // default false since we are only looking for JS runtime and version }); const userAgentEngine = ((/safari/i.test(decryptedName) || /iphone|ipad/i.test(userAgent)) ? 'JavaScriptCore' : /firefox/i.test(userAgent) ? 'SpiderMonkey' : /chrome/i.test(userAgent) ? 'V8' : undefined); if (userAgentEngine != JS_ENGINE) { workerScope.lied = true; workerScope.lies.engine = `${JS_ENGINE} JS runtime and ${userAgentEngine} user agent do not match`; documentLie('WorkerGlobalScope', workerScope.lies.engine); } // user agent version lie const getVersion = (x) => (/\d+/.exec(x) || [])[0]; const userAgentVersion = getVersion(decryptedName); const userAgentDataVersion = getVersion(userAgentData ? userAgentData.uaFullVersion : ''); const versionSupported = userAgentDataVersion && userAgentVersion; const versionMatch = userAgentDataVersion == userAgentVersion; if (versionSupported && !versionMatch) { workerScope.lied = true; workerScope.lies.version = `userAgentData version ${userAgentDataVersion} and user agent version ${userAgentVersion} do not match`; documentLie('WorkerGlobalScope', workerScope.lies.version); } // platformVersion lie const FEATURE_CASE = IS_BLINK && CSS.supports('accent-color: initial'); const getPlatformVersionLie = (device, userAgentData) => { if (!/windows|mac/i.test(device) || !userAgentData?.platformVersion) { return false; } if (userAgentData.platform == 'macOS') { return FEATURE_CASE ? /_/.test(userAgentData.platformVersion) : false; } const reportedVersionNumber = (/windows ([\d|\.]+)/i.exec(device) || [])[1]; const windows10OrHigherReport = +reportedVersionNumber == 10; const { platformVersion } = userAgentData; const versionMap = { '6.1': '7', '6.2': '8', '6.3': '8.1', '10.0': '10', }; const version = versionMap[platformVersion]; if (!FEATURE_CASE && version) { return version != reportedVersionNumber; } const parts = platformVersion.split('.'); if (parts.length != 3) return true; const windows10OrHigherPlatform = +parts[0] > 0; return ((windows10OrHigherPlatform && !windows10OrHigherReport) || (!windows10OrHigherPlatform && windows10OrHigherReport)); }; const windowsVersionLie = getPlatformVersionLie(workerScope.device, userAgentData); if (windowsVersionLie) { workerScope.lied = true; workerScope.lies.platformVersion = `platform version is fake`; documentLie('WorkerGlobalScope', workerScope.lies.platformVersion); } // capture userAgent version workerScope.userAgentVersion = userAgentVersion; workerScope.userAgentDataVersion = userAgentDataVersion; workerScope.userAgentEngine = userAgentEngine; const gpu = { ...(getWebGLRendererConfidence(workerScope.webglRenderer) || {}), compressedGPU: compressWebGLRenderer(workerScope.webglRenderer), }; logTestResult({ time: timer.stop(), test: `${WORKER_TYPE} worker`, passed: true }); return { ...workerScope, gpu, uaPostReduction: isUAPostReduction(workerScope.userAgent), }; } catch (error) { logTestResult({ test: 'worker', passed: false }); captureError(error, 'workers failed or blocked by client'); return; } } function workerScopeHTML(fp) { if (!fp.workerScope) { return `
Worker
lang/timezone:
${HTMLNote.BLOCKED}
gpu:
${HTMLNote.BLOCKED}
userAgent:
${HTMLNote.BLOCKED}
device:
${HTMLNote.BLOCKED}
userAgentData:
${HTMLNote.BLOCKED}
`; } const { workerScope: data } = fp; const { lied, locale, systemCurrencyLocale, engineCurrencyLocale, localeEntropyIsTrusty, localeIntlEntropyIsTrusty, timezoneOffset, timezoneLocation, deviceMemory, hardwareConcurrency, language, // languages, platform, userAgent, uaPostReduction, webglRenderer, webglVendor, gpu, userAgentData, system, device, $hash, } = data || {}; const { parts, warnings, gibbers, confidence, grade: confidenceGrade, compressedGPU, } = gpu || {}; return ` ${performanceLogger.getLog()[`${WORKER_TYPE} worker`]} ${WORKER_NAME || ''}
Worker${hashSlice($hash)}
lang/timezone:
${localeEntropyIsTrusty ? `${language} (${systemCurrencyLocale})` : `${language} (${engineCurrencyLocale})`} ${locale === language ? '' : localeIntlEntropyIsTrusty ? ` ${locale}` : ` ${locale}`}
${timezoneLocation} (${'' + timezoneOffset})
${confidence ? `confidence: ${confidence}` : ''}gpu:
${webglVendor ? webglVendor : ''} ${webglRenderer ? `
${webglRenderer}` : HTMLNote.UNSUPPORTED}
userAgent:${!uaPostReduction ? '' : `ua reduction`}
${userAgent || HTMLNote.UNSUPPORTED}
device:
${`${system}${platform ? ` (${platform})` : ''}`} ${device ? `
${device}` : HTMLNote.BLOCKED} ${hardwareConcurrency && deviceMemory ? `
cores: ${hardwareConcurrency}, ram: ${deviceMemory}` : hardwareConcurrency && !deviceMemory ? `
cores: ${hardwareConcurrency}` : !hardwareConcurrency && deviceMemory ? `
ram: ${deviceMemory}` : ''}
userAgentData:
${((userAgentData) => { const { architecture, bitness, brandsVersion, uaFullVersion, mobile, model, platformVersion, platform, } = userAgentData || {}; // @ts-ignore const windowsRelease = computeWindowsRelease({ platform, platformVersion }); return !userAgentData ? HTMLNote.UNSUPPORTED : ` ${(brandsVersion || []).join(',')}${uaFullVersion ? ` (${uaFullVersion})` : ''}
${windowsRelease || `${platform} ${platformVersion}`} ${architecture ? `${architecture}${bitness ? `_${bitness}` : ''}` : ''} ${model ? `
${model}` : ''} ${mobile ? '
mobile' : ''} `; })(userAgentData)}
`; } // https://stackoverflow.com/a/22429679 const hashMini = (x) => { const json = `${JSON.stringify(x)}`; const hash = json.split('').reduce((hash, char, i) => { return Math.imul(31, hash) + json.charCodeAt(i) | 0; }, 0x811c9dc5); return ('0000000' + (hash >>> 0).toString(16)).substr(-8); }; // instance id const instanceId = (String.fromCharCode(Math.random() * 26 + 97) + Math.random().toString(36).slice(-7)); // https://stackoverflow.com/a/53490958 // https://stackoverflow.com/a/43383990 // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest const hashify = (x, algorithm = 'SHA-256') => { const json = `${JSON.stringify(x)}`; const jsonBuffer = new TextEncoder().encode(json); return crypto.subtle.digest(algorithm, jsonBuffer).then((hashBuffer) => { const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map((b) => ('00' + b.toString(16)).slice(-2)).join(''); return hashHex; }); }; const getFuzzyHash = async (fp) => { // requires update log (below) when adding new keys to fp const metricKeys = [ 'canvas2d.dataURI', 'canvas2d.emojiSet', 'canvas2d.emojiURI', 'canvas2d.liedTextMetrics', 'canvas2d.mods', 'canvas2d.paintURI', 'canvas2d.paintCpuURI', 'canvas2d.textMetricsSystemSum', 'canvas2d.textURI', 'canvasWebgl.dataURI', 'canvasWebgl.dataURI2', 'canvasWebgl.extensions', 'canvasWebgl.gpu', 'canvasWebgl.parameterOrExtensionLie', 'canvasWebgl.parameters', 'canvasWebgl.pixels', 'canvasWebgl.pixels2', 'capturedErrors.data', 'clientRects.domrectSystemSum', 'clientRects.elementBoundingClientRect', 'clientRects.elementClientRects', 'clientRects.emojiSet', 'clientRects.rangeBoundingClientRect', 'clientRects.rangeClientRects', 'consoleErrors.errors', 'css.computedStyle', 'css.system', 'cssMedia.matchMediaCSS', 'cssMedia.mediaCSS', 'cssMedia.screenQuery', 'features.cssFeatures', 'features.cssVersion', 'features.jsFeatures', 'features.jsFeaturesKeys', 'features.jsVersion', 'features.version', 'features.versionRange', 'features.windowFeatures', 'features.windowVersion', 'fonts.apps', 'fonts.emojiSet', 'fonts.fontFaceLoadFonts', 'fonts.pixelSizeSystemSum', 'fonts.platformVersion', 'headless.chromium', 'headless.headless', 'headless.headlessRating', 'headless.likeHeadless', 'headless.likeHeadlessRating', 'headless.platformEstimate', 'headless.stealth', 'headless.stealthRating', 'headless.systemFonts', 'htmlElementVersion.keys', 'intl.dateTimeFormat', 'intl.displayNames', 'intl.listFormat', 'intl.locale', 'intl.numberFormat', 'intl.pluralRules', 'intl.relativeTimeFormat', 'lies.data', 'lies.totalLies', 'maths.data', 'media.mimeTypes', 'navigator.appVersion', 'navigator.bluetoothAvailability', 'navigator.device', 'navigator.deviceMemory', 'navigator.doNotTrack', 'navigator.globalPrivacyControl', 'navigator.hardwareConcurrency', 'navigator.language', 'navigator.maxTouchPoints', 'navigator.mimeTypes', 'navigator.oscpu', 'navigator.permissions', 'navigator.platform', 'navigator.plugins', 'navigator.properties', 'navigator.system', 'navigator.uaPostReduction', 'navigator.userAgent', 'navigator.userAgentData', 'navigator.userAgentParsed', 'navigator.vendor', 'navigator.webgpu', 'offlineAudioContext.binsSample', 'offlineAudioContext.compressorGainReduction', 'offlineAudioContext.copySample', 'offlineAudioContext.floatFrequencyDataSum', 'offlineAudioContext.floatTimeDomainDataSum', 'offlineAudioContext.noise', 'offlineAudioContext.sampleSum', 'offlineAudioContext.totalUniqueSamples', 'offlineAudioContext.values', 'resistance.engine', 'resistance.extension', 'resistance.extensionHashPattern', 'resistance.mode', 'resistance.privacy', 'resistance.security', 'screen.availHeight', 'screen.availWidth', 'screen.colorDepth', 'screen.height', 'screen.pixelDepth', 'screen.touch', 'screen.width', 'svg.bBox', 'svg.computedTextLength', 'svg.emojiSet', 'svg.extentOfChar', 'svg.subStringLength', 'svg.svgrectSystemSum', 'timezone.location', 'timezone.locationEpoch', 'timezone.locationMeasured', 'timezone.offset', 'timezone.offsetComputed', 'timezone.zone', 'trash.trashBin', 'voices.defaultVoiceLang', 'voices.defaultVoiceName', 'voices.languages', 'voices.local', 'voices.remote', 'windowFeatures.apple', 'windowFeatures.keys', 'windowFeatures.moz', 'windowFeatures.webkit', 'workerScope.device', 'workerScope.deviceMemory', 'workerScope.engineCurrencyLocale', 'workerScope.gpu', 'workerScope.hardwareConcurrency', 'workerScope.language', 'workerScope.languages', 'workerScope.lies', 'workerScope.locale', 'workerScope.localeEntropyIsTrusty', 'workerScope.localeIntlEntropyIsTrusty', 'workerScope.platform', 'workerScope.system', 'workerScope.systemCurrencyLocale', 'workerScope.timezoneLocation', 'workerScope.timezoneOffset', 'workerScope.uaPostReduction', 'workerScope.userAgent', 'workerScope.userAgentData', 'workerScope.userAgentDataVersion', 'workerScope.userAgentEngine', 'workerScope.userAgentVersion', 'workerScope.webglRenderer', 'workerScope.webglVendor', ]; // construct map of all metrics const metricsAll = Object.keys(fp).sort().reduce((acc, sectionKey) => { const section = fp[sectionKey]; const sectionMetrics = Object.keys(section || {}).sort().reduce((acc, key) => { if (key == '$hash' || key == 'lied') { return acc; } return { ...acc, [`${sectionKey}.${key}`]: section[key] }; }, {}); return { ...acc, ...sectionMetrics }; }, {}); // reduce to 64 bins const maxBins = 64; const metricKeysReported = Object.keys(metricsAll); const binSize = Math.ceil(metricKeys.length / maxBins); // update log const devMode = window.location.host != 'abrahamjuliot.github.io'; if (devMode && ('' + metricKeysReported != '' + metricKeys)) { const newKeys = metricKeysReported.filter((key) => !metricKeys.includes(key)); const oldKeys = metricKeys.filter((key) => !metricKeysReported.includes(key)); if (newKeys.length || oldKeys.length) { newKeys.length && console.warn('added fuzzy key(s):\n', newKeys.join('\n')); oldKeys.length && console.warn('removed fuzzy key(s):\n', oldKeys.join('\n')); console.groupCollapsed('update keys for accurate fuzzy hashing:'); console.log(metricKeysReported.map((x) => `'${x}',`).join('\n')); console.groupEnd(); } } // compute fuzzy fingerprint master const fuzzyFpMaster = metricKeys.reduce((acc, key, index) => { if (!index || ((index % binSize) == 0)) { const keySet = metricKeys.slice(index, index + binSize); return { ...acc, ['' + keySet]: keySet.map((key) => metricsAll[key]) }; } return acc; }, {}); // hash each bin await Promise.all(Object.keys(fuzzyFpMaster).map((key) => hashify(fuzzyFpMaster[key]).then((hash) => { fuzzyFpMaster[key] = hash; // swap values for hash return hash; }))); // create fuzzy hash const fuzzyBits = 64; const fuzzyFingerprint = Object.keys(fuzzyFpMaster) .map((key) => fuzzyFpMaster[key][0]) .join('') .padEnd(fuzzyBits, '0'); return fuzzyFingerprint; }; const KnownAudio = { // Blink/WebKit [-20.538286209106445]: [ 124.0434488439787, 124.04344968475198, 124.04347527516074, 124.04347503720783, 124.04347657808103, ], [-20.538288116455078]: [ 124.04347518575378, 124.04347527516074, 124.04344884395687, 124.04344968475198, 124.04347657808103, 124.04347730590962, // pattern (rare) 124.0434765110258, // pattern (rare) 124.04347656317987, // pattern (rare) 124.04375314689969, // pattern (rare) // WebKit 124.0434485301812, 124.0434496849557, 124.043453265891, 124.04345734833623, 124.04345808873768, ], [-20.535268783569336]: [ // Android/Linux 124.080722568091, 124.08072256811283, 124.08072766105033, 124.08072787802666, 124.08072787804849, 124.08074500028306, 124.0807470110085, 124.08075528279005, 124.08075643483608, ], // Gecko [-31.502187728881836]: [35.74996626004577], [-31.502185821533203]: [35.74996031448245, 35.7499681673944, 35.749968223273754], [-31.50218963623047]: [35.74996031448245], [-31.509262084960938]: [35.7383295930922, 35.73833402246237], // WebKit [-29.837873458862305]: [35.10892717540264, 35.10892752557993], [-29.83786964416504]: [35.10893232002854, 35.10893253237009], }; const AUDIO_TRAP = Math.random(); async function hasFakeAudio() { const context = new OfflineAudioContext(1, 100, 44100); const oscillator = context.createOscillator(); oscillator.frequency.value = 0; oscillator.start(0); context.startRendering(); return new Promise((resolve) => { context.oncomplete = (event) => { const channelData = event.renderedBuffer.getChannelData?.(0); if (!channelData) resolve(false); resolve('' + [...new Set(channelData)] !== '0'); }; }).finally(() => oscillator.disconnect()); } async function getOfflineAudioContext() { try { const timer = createTimer(); await queueEvent(timer); try { // @ts-expect-error if unsupported window.OfflineAudioContext = OfflineAudioContext || webkitOfflineAudioContext; } catch (err) { } if (!window.OfflineAudioContext) { logTestResult({ test: 'audio', passed: false }); return; } // detect lies const channelDataLie = lieProps['AudioBuffer.getChannelData']; const copyFromChannelLie = lieProps['AudioBuffer.copyFromChannel']; let lied = (channelDataLie || copyFromChannelLie) || false; const bufferLen = 5000; const context = new OfflineAudioContext(1, bufferLen, 44100); const analyser = context.createAnalyser(); const oscillator = context.createOscillator(); const dynamicsCompressor = context.createDynamicsCompressor(); const biquadFilter = context.createBiquadFilter(); // detect lie const dataArray = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData?.(dataArray); const floatFrequencyUniqueDataSize = new Set(dataArray).size; if (floatFrequencyUniqueDataSize > 1) { lied = true; const floatFrequencyDataLie = `expected -Infinity (silence) and got ${floatFrequencyUniqueDataSize} frequencies`; documentLie(`AnalyserNode.getFloatFrequencyData`, floatFrequencyDataLie); } const values = { ['AnalyserNode.channelCount']: attempt(() => analyser.channelCount), ['AnalyserNode.channelCountMode']: attempt(() => analyser.channelCountMode), ['AnalyserNode.channelInterpretation']: attempt(() => analyser.channelInterpretation), ['AnalyserNode.context.sampleRate']: attempt(() => analyser.context.sampleRate), ['AnalyserNode.fftSize']: attempt(() => analyser.fftSize), ['AnalyserNode.frequencyBinCount']: attempt(() => analyser.frequencyBinCount), ['AnalyserNode.maxDecibels']: attempt(() => analyser.maxDecibels), ['AnalyserNode.minDecibels']: attempt(() => analyser.minDecibels), ['AnalyserNode.numberOfInputs']: attempt(() => analyser.numberOfInputs), ['AnalyserNode.numberOfOutputs']: attempt(() => analyser.numberOfOutputs), ['AnalyserNode.smoothingTimeConstant']: attempt(() => analyser.smoothingTimeConstant), ['AnalyserNode.context.listener.forwardX.maxValue']: attempt(() => { return caniuse(() => analyser.context.listener.forwardX.maxValue); }), ['BiquadFilterNode.gain.maxValue']: attempt(() => biquadFilter.gain.maxValue), ['BiquadFilterNode.frequency.defaultValue']: attempt(() => biquadFilter.frequency.defaultValue), ['BiquadFilterNode.frequency.maxValue']: attempt(() => biquadFilter.frequency.maxValue), ['DynamicsCompressorNode.attack.defaultValue']: attempt(() => dynamicsCompressor.attack.defaultValue), ['DynamicsCompressorNode.knee.defaultValue']: attempt(() => dynamicsCompressor.knee.defaultValue), ['DynamicsCompressorNode.knee.maxValue']: attempt(() => dynamicsCompressor.knee.maxValue), ['DynamicsCompressorNode.ratio.defaultValue']: attempt(() => dynamicsCompressor.ratio.defaultValue), ['DynamicsCompressorNode.ratio.maxValue']: attempt(() => dynamicsCompressor.ratio.maxValue), ['DynamicsCompressorNode.release.defaultValue']: attempt(() => dynamicsCompressor.release.defaultValue), ['DynamicsCompressorNode.release.maxValue']: attempt(() => dynamicsCompressor.release.maxValue), ['DynamicsCompressorNode.threshold.defaultValue']: attempt(() => dynamicsCompressor.threshold.defaultValue), ['DynamicsCompressorNode.threshold.minValue']: attempt(() => dynamicsCompressor.threshold.minValue), ['OscillatorNode.detune.maxValue']: attempt(() => oscillator.detune.maxValue), ['OscillatorNode.detune.minValue']: attempt(() => oscillator.detune.minValue), ['OscillatorNode.frequency.defaultValue']: attempt(() => oscillator.frequency.defaultValue), ['OscillatorNode.frequency.maxValue']: attempt(() => oscillator.frequency.maxValue), ['OscillatorNode.frequency.minValue']: attempt(() => oscillator.frequency.minValue), }; const getRenderedBuffer = (context) => (new Promise((resolve) => { const analyser = context.createAnalyser(); const oscillator = context.createOscillator(); const dynamicsCompressor = context.createDynamicsCompressor(); try { oscillator.type = 'triangle'; oscillator.frequency.value = 10000; dynamicsCompressor.threshold.value = -50; dynamicsCompressor.knee.value = 40; dynamicsCompressor.attack.value = 0; } catch (err) { } oscillator.connect(dynamicsCompressor); dynamicsCompressor.connect(analyser); dynamicsCompressor.connect(context.destination); oscillator.start(0); context.startRendering(); return context.addEventListener('complete', (event) => { try { dynamicsCompressor.disconnect(); oscillator.disconnect(); const floatFrequencyData = new Float32Array(analyser.frequencyBinCount); analyser.getFloatFrequencyData?.(floatFrequencyData); const floatTimeDomainData = new Float32Array(analyser.fftSize); if ('getFloatTimeDomainData' in analyser) { analyser.getFloatTimeDomainData(floatTimeDomainData); } return resolve({ floatFrequencyData, floatTimeDomainData, buffer: event.renderedBuffer, compressorGainReduction: ( // @ts-expect-error if unsupported dynamicsCompressor.reduction.value || // webkit dynamicsCompressor.reduction), }); } catch (error) { return resolve(null); } }); })); await queueEvent(timer); const [audioData, audioIsFake,] = await Promise.all([ getRenderedBuffer(new OfflineAudioContext(1, bufferLen, 44100)), hasFakeAudio().catch(() => false), ]); const { floatFrequencyData, floatTimeDomainData, buffer, compressorGainReduction, } = audioData || {}; await queueEvent(timer); const getSnapshot = (arr, start, end) => { const collection = []; for (let i = start; i < end; i++) { collection.push(arr[i]); } return collection; }; const getSum = (arr) => !arr ? 0 : [...arr] .reduce((acc, curr) => (acc += Math.abs(curr)), 0); const floatFrequencyDataSum = getSum(floatFrequencyData); const floatTimeDomainDataSum = getSum(floatTimeDomainData); const copy = new Float32Array(bufferLen); let bins = new Float32Array(); if (buffer) { buffer.copyFromChannel?.(copy, 0); bins = buffer.getChannelData?.(0) || []; } const copySample = getSnapshot([...copy], 4500, 4600); const binsSample = getSnapshot([...bins], 4500, 4600); const sampleSum = getSum(getSnapshot([...bins], 4500, bufferLen)); // detect lies if (audioIsFake) { lied = true; documentLie('AudioBuffer', 'audio is fake'); } // sample matching const matching = '' + binsSample == '' + copySample; const copyFromChannelSupported = ('copyFromChannel' in AudioBuffer.prototype); if (copyFromChannelSupported && !matching) { lied = true; const audioSampleLie = 'getChannelData and copyFromChannel samples mismatch'; documentLie('AudioBuffer', audioSampleLie); } // sample uniqueness const totalUniqueSamples = new Set([...bins]).size; if (totalUniqueSamples == bufferLen) { const audioUniquenessTrash = `${totalUniqueSamples} unique samples of ${bufferLen} is too high`; sendToTrash('AudioBuffer', audioUniquenessTrash); } // sample noise factor const getRandFromRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; const getCopyFrom = (rand, buffer, copy) => { const { length } = buffer; const max = 20; const start = getRandFromRange(275, length - (max + 1)); const mid = start + max / 2; const end = start + max; buffer.getChannelData(0)[start] = rand; buffer.getChannelData(0)[mid] = rand; buffer.getChannelData(0)[end] = rand; buffer.copyFromChannel(copy, 0); const attack = [ buffer.getChannelData(0)[start] === 0 ? Math.random() : 0, buffer.getChannelData(0)[mid] === 0 ? Math.random() : 0, buffer.getChannelData(0)[end] === 0 ? Math.random() : 0, ]; return [...new Set([...buffer.getChannelData(0), ...copy, ...attack])].filter((x) => x !== 0); }; const getCopyTo = (rand, buffer, copy) => { buffer.copyToChannel(copy.map(() => rand), 0); const frequency = buffer.getChannelData(0)[0]; const dataAttacked = [...buffer.getChannelData(0)] .map((x) => x !== frequency || !x ? Math.random() : x); return dataAttacked.filter((x) => x !== frequency); }; const getNoiseFactor = () => { const length = 2000; try { const result = [...new Set([ ...getCopyFrom(AUDIO_TRAP, new AudioBuffer({ length, sampleRate: 44100 }), new Float32Array(length)), ...getCopyTo(AUDIO_TRAP, new AudioBuffer({ length, sampleRate: 44100 }), new Float32Array(length)), ])]; return +(result.length !== 1 && result.reduce((acc, n) => acc += +n, 0)); } catch (error) { console.error(error); return 0; } }; const noiseFactor = getNoiseFactor(); const noise = (noiseFactor || [...new Set(bins.slice(0, 100))] .reduce((acc, n) => acc += n, 0)); // Locked Patterns const known = { /* BLINK */ // 124.04347527516074/124.04347518575378 '-20.538286209106445,164537.64796829224,502.5999283068122': [124.04347527516074], '-20.538288116455078,164537.64796829224,502.5999283068122': [124.04347527516074], '-20.538288116455078,164537.64795303345,502.5999283068122': [ 124.04347527516074, 124.04347518575378, // sus: 124.04347519320436, 124.04347523045726, ], '-20.538286209106445,164537.64805984497,502.5999283068122': [124.04347527516074], '-20.538288116455078,164537.64805984497,502.5999283068122': [ 124.04347527516074, 124.04347518575378, // sus 124.04347520065494, 124.04347523790784, 124.043475252809, 124.04347526025958, 124.04347522300668, 124.04347523045726, 124.04347524535842, ], // 124.04344884395687 '-20.538288116455078,164881.9727935791,502.59990317908887': [124.04344884395687], '-20.538288116455078,164881.9729309082,502.59990317908887': [124.04344884395687], // 124.0434488439787 '-20.538286209106445,164882.2082748413,502.59990317911434': [124.0434488439787], '-20.538288116455078,164882.20836639404,502.59990317911434': [124.0434488439787], // 124.04344968475198 '-20.538286209106445,164863.45319366455,502.5999033495791': [124.04344968475198], '-20.538288116455078,164863.45319366455,502.5999033495791': [ 124.04344968475198, 124.04375314689969, // rare // sus 124.04341541208123, ], // 124.04347503720783 (rare) '-20.538288116455078,164531.82670593262,502.59992767886797': [ 124.04347503720783, // sus 124.04347494780086, 124.04347495525144, 124.04347499250434, 124.0434750074055, ], // 124.04347657808103 '-20.538286209106445,164540.1567993164,502.59992209258417': [124.04347657808103], '-20.538288116455078,164540.1567993164,502.59992209258417': [ 124.04347657808103, 124.0434765110258, // rare 124.04347656317987, // rare // sus 124.04347657063045, 124.04378004022874, ], '-20.538288116455078,164540.1580810547,502.59992209258417': [124.04347657808103], // 124.080722568091/124.04347730590962 (rare) '-20.535268783569336,164940.360786438,502.69695458233764': [124.080722568091], '-20.538288116455078,164538.55073928833,502.5999307175407': [124.04347730590962], // Android/Linux '-20.535268783569336,164948.14596557617,502.6969545823631': [124.08072256811283], '-20.535268783569336,164926.65912628174,502.6969610930064': [124.08072766105033], '-20.535268783569336,164932.96168518066,502.69696179985476': [124.08072787802666], '-20.535268783569336,164931.54252624512,502.6969617998802': [124.08072787804849], '-20.535268783569336,164591.9659729004,502.6969925059784': [124.08074500028306], '-20.535268783569336,164590.4111480713,502.6969947774742': [124.0807470110085], '-20.535268783569336,164590.41115570068,502.6969947774742': [124.0807470110085], '-20.535268783569336,164593.64263916016,502.69700490119067': [124.08075528279005], '-20.535268783569336,164595.0285797119,502.69700578315314': [124.08075643483608], // sus '-20.538288116455078,164860.96576690674,502.6075748118915': [124.0434496279413], '-20.538288116455078,164860.9938583374,502.6073723861407': [124.04344962817413], '-20.538288116455078,164862.14078521729,502.59991004130643': [124.04345734833623], '-20.538288116455078,164534.50047683716,502.61542110471055': [124.04347520368174], '-20.538288116455078,164535.1324043274,502.6079200572931': [124.04347521997988], '-20.538288116455078,164535.51135635376,502.60633126448374': [124.04347522952594], /* GECKO */ '-31.509262084960938,167722.6894454956,148.42717787250876': [35.7383295930922], '-31.509262084960938,167728.72756958008,148.427184343338': [35.73833402246237], '-31.50218963623047,167721.27517700195,148.47537828609347': [35.74996031448245], '-31.502185821533203,167727.52931976318,148.47542023658752': [35.7499681673944], '-31.502185821533203,167700.7530517578,148.475412953645': [35.749968223273754], '-31.502187728881836,167697.23177337646,148.47541113197803': [35.74996626004577], /* WEBKIT */ '-20.538288116455078,164873.80361557007,502.59989904452596': [124.0434485301812], '-20.538288116455078,164863.47760391235,502.5999033453372': [124.0434496849557], '-20.538288116455078,164876.62466049194,502.5998911961724': [124.043453265891], '-20.538288116455078,164862.14879989624,502.59991004130643': [124.04345734833623], '-20.538288116455078,164896.54167175293,502.5999054916465': [124.04345808873768], '-29.837873458862305,163206.43050384521,0': [35.10892717540264], '-29.837873458862305,163224.69785308838,0': [35.10892752557993], '-29.83786964416504,163209.17245483398,0': [35.10893232002854], '-29.83786964416504,163202.77336883545,0': [35.10893253237009], }; if (noise) { lied = true; documentLie('AudioBuffer', 'sample noise detected'); } const pattern = '' + [ compressorGainReduction, floatFrequencyDataSum, floatTimeDomainDataSum, ]; const knownPattern = known[pattern]; if (knownPattern && !knownPattern.includes(sampleSum)) { LowerEntropy.AUDIO = true; sendToTrash('AudioBuffer', 'suspicious frequency data'); } logTestResult({ time: timer.stop(), test: 'audio', passed: true }); return { totalUniqueSamples, compressorGainReduction, floatFrequencyDataSum, floatTimeDomainDataSum, sampleSum, binsSample, copySample: copyFromChannelSupported ? copySample : [undefined], values, noise, lied, }; } catch (error) { logTestResult({ test: 'audio', passed: false }); captureError(error, 'OfflineAudioContext failed or blocked by client'); return; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any function audioHTML(fp) { if (!fp.offlineAudioContext) { return `
Audio
sum: ${HTMLNote.BLOCKED}
gain: ${HTMLNote.BLOCKED}
freq: ${HTMLNote.BLOCKED}
time: ${HTMLNote.BLOCKED}
trap: ${HTMLNote.BLOCKED}
unique: ${HTMLNote.BLOCKED}
data: ${HTMLNote.BLOCKED}
copy: ${HTMLNote.BLOCKED}
values: ${HTMLNote.BLOCKED}
`; } const { offlineAudioContext: { $hash, totalUniqueSamples, compressorGainReduction, floatFrequencyDataSum, floatTimeDomainDataSum, sampleSum, binsSample, copySample, lied, noise, values, }, } = fp; const knownSums = KnownAudio[compressorGainReduction] || []; const validAudio = sampleSum && compressorGainReduction && knownSums.length; const matchesKnownAudio = knownSums.includes(sampleSum); return `
${performanceLogger.getLog().audio} Audio${hashSlice($hash)}
sum: ${!sampleSum ? HTMLNote.BLOCKED : (!validAudio || matchesKnownAudio) ? sampleSum : getDiffs({ stringA: knownSums[0], stringB: sampleSum, charDiff: true, decorate: (diff) => `${diff}`, })}
gain: ${compressorGainReduction || HTMLNote.BLOCKED}
freq: ${floatFrequencyDataSum || HTMLNote.BLOCKED}
time: ${floatTimeDomainDataSum || HTMLNote.UNSUPPORTED}
trap: ${!noise ? AUDIO_TRAP : getDiffs({ stringA: AUDIO_TRAP, stringB: noise, charDiff: true, decorate: (diff) => `${diff}`, })}
unique: ${totalUniqueSamples}
data:${'' + binsSample[0] == 'undefined' ? ` ${HTMLNote.BLOCKED}` : `${hashMini(binsSample)}`}
copy:${'' + copySample[0] == 'undefined' ? ` ${HTMLNote.BLOCKED}` : `${hashMini(copySample)}`}
values: ${modal('creep-offline-audio-context', Object.keys(values).map((key) => `
${key}: ${values[key]}
`).join(''), hashMini(values))}
`; } // inspired by https://arkenfox.github.io/TZP/tests/canvasnoise.html let pixelImageRandom = ''; const getPixelMods = () => { const pattern1 = []; const pattern2 = []; const len = 8; // canvas dimensions const alpha = 255; const visualMultiplier = 5; try { // create 2 canvas contexts const options = { willReadFrequently: true, desynchronized: true, }; const canvasDisplay1 = document.createElement('canvas'); const canvasDisplay2 = document.createElement('canvas'); const canvas1 = document.createElement('canvas'); const canvas2 = document.createElement('canvas'); const contextDisplay1 = canvasDisplay1.getContext('2d', options); const contextDisplay2 = canvasDisplay2.getContext('2d', options); const context1 = canvas1.getContext('2d', options); const context2 = canvas2.getContext('2d', options); if (!contextDisplay1 || !contextDisplay2 || !context1 || !context2) { throw new Error('canvas context blocked'); } // set the dimensions canvasDisplay1.width = len * visualMultiplier; canvasDisplay1.height = len * visualMultiplier; canvasDisplay2.width = len * visualMultiplier; canvasDisplay2.height = len * visualMultiplier; canvas1.width = len; canvas1.height = len; canvas2.width = len; canvas2.height = len; [...Array(len)].forEach((e, x) => [...Array(len)].forEach((e, y) => { const red = ~~(Math.random() * 256); const green = ~~(Math.random() * 256); const blue = ~~(Math.random() * 256); const colors = `${red}, ${green}, ${blue}, ${alpha}`; context1.fillStyle = `rgba(${colors})`; context1.fillRect(x, y, 1, 1); // capture data in visuals contextDisplay1.fillStyle = `rgba(${colors})`; contextDisplay1.fillRect(x * visualMultiplier, y * visualMultiplier, 1 * visualMultiplier, 1 * visualMultiplier); return pattern1.push(colors); // collect the pixel pattern })); [...Array(len)].forEach((e, x) => [...Array(len)].forEach((e, y) => { // get context1 pixel data and mirror to context2 const { data: [red, green, blue, alpha], } = context1.getImageData(x, y, 1, 1) || {}; const colors = `${red}, ${green}, ${blue}, ${alpha}`; context2.fillStyle = `rgba(${colors})`; context2.fillRect(x, y, 1, 1); // capture noise in visuals const { data: [red2, green2, blue2, alpha2], } = context2.getImageData(x, y, 1, 1) || {}; const colorsDisplay = ` ${red != red2 ? red2 : 255}, ${green != green2 ? green2 : 255}, ${blue != blue2 ? blue2 : 255}, ${alpha != alpha2 ? alpha2 : 1} `; contextDisplay2.fillStyle = `rgba(${colorsDisplay})`; contextDisplay2.fillRect(x * visualMultiplier, y * visualMultiplier, 1 * visualMultiplier, 1 * visualMultiplier); return pattern2.push(colors); // collect the pixel pattern })); // compare the pattern collections and collect diffs const patternDiffs = []; const rgbaChannels = new Set(); [...Array(pattern1.length)].forEach((e, i) => { const pixelColor1 = pattern1[i]; const pixelColor2 = pattern2[i]; if (pixelColor1 != pixelColor2) { const rgbaValues1 = pixelColor1.split(','); const rgbaValues2 = pixelColor2.split(','); const colors = [ rgbaValues1[0] != rgbaValues2[0] ? 'r' : '', rgbaValues1[1] != rgbaValues2[1] ? 'g' : '', rgbaValues1[2] != rgbaValues2[2] ? 'b' : '', rgbaValues1[3] != rgbaValues2[3] ? 'a' : '', ].join(''); rgbaChannels.add(colors); patternDiffs.push([i, colors]); } }); pixelImageRandom = canvasDisplay1.toDataURL(); // template use only const pixelImage = canvasDisplay2.toDataURL(); const rgba = rgbaChannels.size ? [...rgbaChannels].sort().join(', ') : undefined; const pixels = patternDiffs.length || undefined; return { rgba, pixels, pixelImage }; } catch (error) { return console.error(error); } }; // based on and inspired by https://github.com/antoinevastel/picasso-like-canvas-fingerprinting const paintCanvas = ({ canvas, context, strokeText = false, cssFontFamily = '', area = { width: 50, height: 50 }, rounds = 10, maxShadowBlur = 50, seed = 500, offset = 2001000001, multiplier = 15000, }) => { if (!context) { return; } context.clearRect(0, 0, canvas.width, canvas.height); canvas.width = area.width; canvas.height = area.height; if (canvas.style) { canvas.style.display = 'none'; } const createPicassoSeed = ({ seed, offset, multiplier }) => { let current = Number(seed) % Number(offset); const getNextSeed = () => { current = (Number(multiplier) * current) % Number(offset); return current; }; return { getNextSeed, }; }; const picassoSeed = createPicassoSeed({ seed, offset, multiplier }); const { getNextSeed } = picassoSeed; const patchSeed = (current, offset, maxBound, computeFloat) => { const result = (((current - 1) / offset) * (maxBound || 1)) || 0; return computeFloat ? result : Math.floor(result); }; const addRandomCanvasGradient = (context, offset, area, colors, getNextSeed) => { const { width, height } = area; const canvasGradient = context.createRadialGradient(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, width)); canvasGradient.addColorStop(0, colors[patchSeed(getNextSeed(), offset, colors.length)]); canvasGradient.addColorStop(1, colors[patchSeed(getNextSeed(), offset, colors.length)]); context.fillStyle = canvasGradient; }; const colors = [ '#FF6633', '#FFB399', '#FF33FF', '#FFFF99', '#00B3E6', '#E6B333', '#3366E6', '#999966', '#99FF99', '#B34D4D', '#80B300', '#809900', '#E6B3B3', '#6680B3', '#66991A', '#FF99E6', '#CCFF1A', '#FF1A66', '#E6331A', '#33FFCC', '#66994D', '#B366CC', '#4D8000', '#B33300', '#CC80CC', '#66664D', '#991AFF', '#E666FF', '#4DB3FF', '#1AB399', '#E666B3', '#33991A', '#CC9999', '#B3B31A', '#00E680', '#4D8066', '#809980', '#E6FF80', '#1AFF33', '#999933', '#FF3380', '#CCCC00', '#66E64D', '#4D80CC', '#9900B3', '#E64D66', '#4DB380', '#FF4D4D', '#99E6E6', '#6666FF', ]; const drawOutlineOfText = (context, offset, area, getNextSeed) => { const { width, height } = area; const fontSize = 2.99; context.font = `${height / fontSize}px ${cssFontFamily.replace(/!important/gm, '')}`; context.strokeText('šŸ‘¾A', patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, width)); }; const createCircularArc = (context, offset, area, getNextSeed) => { const { width, height } = area; context.beginPath(); context.arc(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, Math.min(width, height)), patchSeed(getNextSeed(), offset, 2 * Math.PI, true), patchSeed(getNextSeed(), offset, 2 * Math.PI, true)); context.stroke(); }; const createBezierCurve = (context, offset, area, getNextSeed) => { const { width, height } = area; context.beginPath(); context.moveTo(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height)); context.bezierCurveTo(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height)); context.stroke(); }; const createQuadraticCurve = (context, offset, area, getNextSeed) => { const { width, height } = area; context.beginPath(); context.moveTo(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height)); context.quadraticCurveTo(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height)); context.stroke(); }; const createEllipticalArc = (context, offset, area, getNextSeed) => { if (!('ellipse' in context)) { return; } const { width, height } = area; context.beginPath(); context.ellipse(patchSeed(getNextSeed(), offset, width), patchSeed(getNextSeed(), offset, height), patchSeed(getNextSeed(), offset, Math.floor(width / 2)), patchSeed(getNextSeed(), offset, Math.floor(height / 2)), patchSeed(getNextSeed(), offset, 2 * Math.PI, true), patchSeed(getNextSeed(), offset, 2 * Math.PI, true), patchSeed(getNextSeed(), offset, 2 * Math.PI, true)); context.stroke(); }; const methods = [ createCircularArc, createBezierCurve, createQuadraticCurve, ]; if (!IS_WEBKIT) methods.push(createEllipticalArc); // unstable in webkit if (strokeText) methods.push(drawOutlineOfText); [...Array(rounds)].forEach((x) => { addRandomCanvasGradient(context, offset, area, colors, getNextSeed); context.shadowBlur = patchSeed(getNextSeed(), offset, maxShadowBlur, true); context.shadowColor = colors[patchSeed(getNextSeed(), offset, colors.length)]; const nextMethod = methods[patchSeed(getNextSeed(), offset, methods.length)]; nextMethod(context, offset, area, getNextSeed); context.fill(); }); return; }; async function getCanvas2d() { try { const timer = createTimer(); await queueEvent(timer); const dataLie = lieProps['HTMLCanvasElement.toDataURL']; const contextLie = lieProps['HTMLCanvasElement.getContext']; const imageDataLie = (lieProps['CanvasRenderingContext2D.fillText'] || lieProps['CanvasRenderingContext2D.font'] || lieProps['CanvasRenderingContext2D.getImageData'] || lieProps['CanvasRenderingContext2D.strokeText']); const codePointLie = lieProps['String.fromCodePoint']; let textMetricsLie = (lieProps['CanvasRenderingContext2D.measureText'] || lieProps['TextMetrics.actualBoundingBoxAscent'] || lieProps['TextMetrics.actualBoundingBoxDescent'] || lieProps['TextMetrics.actualBoundingBoxLeft'] || lieProps['TextMetrics.actualBoundingBoxRight'] || lieProps['TextMetrics.fontBoundingBoxAscent'] || lieProps['TextMetrics.fontBoundingBoxDescent'] || lieProps['TextMetrics.width']); let lied = (dataLie || contextLie || imageDataLie || textMetricsLie || codePointLie) || false; // create canvas context let win = window; if (!LIKE_BRAVE && PHANTOM_DARKNESS) { win = PHANTOM_DARKNESS; } const doc = win.document; const canvas = doc.createElement('canvas'); const context = canvas.getContext('2d'); const canvasCPU = doc.createElement('canvas'); const contextCPU = canvasCPU.getContext('2d', { desynchronized: true, willReadFrequently: true, }); if (!context) { throw new Error('canvas context blocked'); } await queueEvent(timer); const imageSizeMax = IS_WEBKIT ? 50 : 75; // webkit is unstable paintCanvas({ canvas, context, strokeText: true, cssFontFamily: CSS_FONT_FAMILY, area: { width: imageSizeMax, height: imageSizeMax }, rounds: 10, }); const dataURI = canvas.toDataURL(); await queueEvent(timer); const mods = getPixelMods(); // TextMetrics: get emoji set and system await queueEvent(timer); context.font = `10px ${CSS_FONT_FAMILY.replace(/!important/gm, '')}`; const pattern = new Set(); const emojiSet = EMOJIS.reduce((emojiSet, emoji) => { const { actualBoundingBoxAscent, actualBoundingBoxDescent, actualBoundingBoxLeft, actualBoundingBoxRight, fontBoundingBoxAscent, fontBoundingBoxDescent, width, } = context.measureText(emoji) || {}; const dimensions = [ actualBoundingBoxAscent, actualBoundingBoxDescent, actualBoundingBoxLeft, actualBoundingBoxRight, fontBoundingBoxAscent, fontBoundingBoxDescent, width, ].join(','); if (!pattern.has(dimensions)) { pattern.add(dimensions); emojiSet.add(emoji); } return emojiSet; }, new Set()); // textMetrics System Sum const textMetricsSystemSum = 0.00001 * [...pattern].map((x) => { return x.split(',').reduce((acc, x) => acc += (+x || 0), 0); }).reduce((acc, x) => acc += x, 0); // Paint const maxSize = 75; await queueEvent(timer); paintCanvas({ canvas, context, area: { width: maxSize, height: maxSize }, }); // clears image const paintURI = canvas.toDataURL(); // Paint with CPU await queueEvent(timer); paintCanvas({ canvas: canvasCPU, context: contextCPU, area: { width: maxSize, height: maxSize }, }); // clears image const paintCpuURI = canvasCPU.toDataURL(); // Text context.restore(); context.clearRect(0, 0, canvas.width, canvas.height); canvas.width = 50; canvas.height = 50; context.font = `50px ${CSS_FONT_FAMILY.replace(/!important/gm, '')}`; context.fillText('A', 7, 37); const textURI = canvas.toDataURL(); // Emoji context.restore(); context.clearRect(0, 0, canvas.width, canvas.height); canvas.width = 50; canvas.height = 50; context.font = `35px ${CSS_FONT_FAMILY.replace(/!important/gm, '')}`; context.fillText('šŸ‘¾', 0, 37); const emojiURI = canvas.toDataURL(); // lies context.clearRect(0, 0, canvas.width, canvas.height); if ((mods && mods.pixels) || !!Math.max(...context.getImageData(0, 0, 8, 8).data)) { lied = true; documentLie(`CanvasRenderingContext2D.getImageData`, `pixel data modified`); } // verify low entropy image data canvas.width = 2; canvas.height = 2; context.fillStyle = '#000'; context.fillRect(0, 0, canvas.width, canvas.height); context.fillStyle = '#fff'; context.fillRect(2, 2, 1, 1); context.beginPath(); context.arc(0, 0, 2, 0, 1, true); context.closePath(); context.fill(); const imageDataLowEntropy = context.getImageData(0, 0, 2, 2).data.join(''); const KnownImageData = { BLINK: [ '255255255255178178178255246246246255555555255', '255255255255192192192255240240240255484848255', '255255255255177177177255246246246255535353255', '255255255255128128128255191191191255646464255', '255255255255178178178255247247247255565656255', // ? '255255255255174174174255242242242255474747255', '255255255255229229229255127127127255686868255', '255255255255192192192255244244244255535353255', ], GECKO: [ '255255255255191191191255207207207255646464255', '255255255255192192192255240240240255484848255', '255255255255191191191255239239239255646464255', '255255255255191191191255223223223255606060255', // ? '255255255255171171171255223223223255606060255', // ? '255255255255188188188255245245245255525252255', ], WEBKIT: [ '255255255255185185185255233233233255474747255', '255255255255185185185255229229229255474747255', '255255255255185185185255218218218255474747255', '255255255255192192192255240240240255484848255', '255255255255178178178255247247247255565656255', '255255255255178178178255247247247255565656255', '255255255255192192192255240240240255484848255', '255255255255186186186255218218218255464646255', ], }; Analysis.imageDataLowEntropy = imageDataLowEntropy; if (IS_BLINK && !KnownImageData.BLINK.includes(imageDataLowEntropy)) { LowerEntropy.CANVAS = true; } else if (IS_GECKO && !KnownImageData.GECKO.includes(imageDataLowEntropy)) { LowerEntropy.CANVAS = true; } else if (IS_WEBKIT && !KnownImageData.WEBKIT.includes(imageDataLowEntropy)) { LowerEntropy.CANVAS = true; } if (LowerEntropy.CANVAS) { sendToTrash('CanvasRenderingContext2D.getImageData', 'suspicious pixel data'); } const getTextMetricsFloatLie = (context) => { const isFloat = (n) => n % 1 !== 0; const { actualBoundingBoxAscent: abba, actualBoundingBoxDescent: abbd, actualBoundingBoxLeft: abbl, actualBoundingBoxRight: abbr, fontBoundingBoxAscent: fbba, fontBoundingBoxDescent: fbbd, // width: w, } = context.measureText('') || {}; const lied = [ abba, abbd, abbl, abbr, fbba, fbbd, ].find((x) => isFloat((x || 0))); return lied; }; await queueEvent(timer); if (getTextMetricsFloatLie(context)) { textMetricsLie = true; lied = true; documentLie('CanvasRenderingContext2D.measureText', 'metric noise detected'); } logTestResult({ time: timer.stop(), test: 'canvas 2d', passed: true }); return { dataURI, paintURI, paintCpuURI, textURI, emojiURI, mods, textMetricsSystemSum, liedTextMetrics: textMetricsLie, emojiSet: [...emojiSet], lied, }; } catch (error) { logTestResult({ test: 'canvas 2d', passed: false }); captureError(error); return; } } function canvasHTML(fp) { if (!fp.canvas2d) { return `
Canvas 2d ${HTMLNote.BLOCKED}
data: ${HTMLNote.BLOCKED}
rendering:
${HTMLNote.BLOCKED}
${HTMLNote.BLOCKED}
textMetrics:
${HTMLNote.BLOCKED}
`; } const { canvas2d: { lied, dataURI, paintURI, paintCpuURI, textURI, emojiURI, mods, emojiSet, textMetricsSystemSum, $hash, }, } = fp; const { pixels, rgba, pixelImage } = mods || {}; const modPercent = pixels ? Math.round((pixels / 400) * 100) : 0; const hash = { dataURI: hashMini(dataURI), textURI: hashMini(textURI), emojiURI: hashMini(emojiURI), paintURI: hashMini(paintURI), paintCpuURI: hashMini(paintCpuURI), }; const dataTemplate = ` ${textURI ? `
` : ''}
text: ${!textURI ? HTMLNote.BLOCKED : hash.textURI}

${emojiURI ? `
` : ''}
emoji: ${!emojiURI ? HTMLNote.BLOCKED : hash.emojiURI}

${paintURI ? `
` : ''}
paint (GPU): ${!paintURI ? HTMLNote.BLOCKED : hash.paintURI}

${paintCpuURI ? `
` : ''}
paint (CPU): ${!paintCpuURI ? HTMLNote.BLOCKED : hash.paintCpuURI}

${dataURI ? `
` : ''}
combined: ${!dataURI ? HTMLNote.BLOCKED : hash.dataURI} `; // rgba: "b, g, gb, r, rb, rg, rgb" const rgbaHTML = !rgba ? rgba : rgba.split(', ').map((s) => s.split('').map((c) => { const css = { r: 'red', g: 'green', b: 'blue', }; return ``; }).join('')).join(' '); const emojiHelpTitle = `CanvasRenderingContext2D.measureText()\nhash: ${hashMini(emojiSet)}\n${emojiSet.map((x, i) => i && (i % 6 == 0) ? `${x}\n` : x).join('')}`; return `
${performanceLogger.getLog()['canvas 2d']} Canvas 2d${hashSlice($hash)}
data: ${modal('creep-canvas-data', dataTemplate, hashMini({ dataURI, pixelImage, paintURI, paintCpuURI, textURI, emojiURI, }))}
rendering: ${rgba ? `${modPercent}% rgba noise ${rgbaHTML}` : ''}
${textURI ? `
` : ''} ${emojiURI ? `
` : ''} ${paintCpuURI ? `
` : ''} ${dataURI ? `
` : ''}
${rgba ? `
` : ''}
textMetrics:
${textMetricsSystemSum || HTMLNote.UNSUPPORTED} ${formatEmojiSet(emojiSet)}
`; } function getCSS() { const computeStyle = (type, { require: [captureError] }) => { try { // get CSSStyleDeclaration const cssStyleDeclaration = (type == 'getComputedStyle' ? getComputedStyle(document.body) : type == 'HTMLElement.style' ? document.body.style : // @ts-ignore type == 'CSSRuleList.style' ? document.styleSheets[0].cssRules[0].style : undefined); if (!cssStyleDeclaration) { throw new TypeError('invalid argument string'); } // get properties const proto = Object.getPrototypeOf(cssStyleDeclaration); const prototypeProperties = Object.getOwnPropertyNames(proto); const ownEnumerablePropertyNames = []; const cssVar = /^--.*$/; Object.keys(cssStyleDeclaration).forEach((key) => { const numericKey = !isNaN(+key); const value = cssStyleDeclaration[key]; const customPropKey = cssVar.test(key); const customPropValue = cssVar.test(value); if (numericKey && !customPropValue) { return ownEnumerablePropertyNames.push(value); } else if (!numericKey && !customPropKey) { return ownEnumerablePropertyNames.push(key); } return; }); // get properties in prototype chain (required only in chrome) const propertiesInPrototypeChain = {}; const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); const uncapitalize = (str) => str.charAt(0).toLowerCase() + str.slice(1); const removeFirstChar = (str) => str.slice(1); const caps = /[A-Z]/g; ownEnumerablePropertyNames.forEach((key) => { if (propertiesInPrototypeChain[key]) { return; } // determine attribute type const isNamedAttribute = key.indexOf('-') > -1; const isAliasAttribute = caps.test(key); // reduce key for computation const firstChar = key.charAt(0); const isPrefixedName = isNamedAttribute && firstChar == '-'; const isCapitalizedAlias = isAliasAttribute && firstChar == firstChar.toUpperCase(); key = (isPrefixedName ? removeFirstChar(key) : isCapitalizedAlias ? uncapitalize(key) : key); // find counterpart in CSSStyleDeclaration object or its prototype chain if (isNamedAttribute) { const aliasAttribute = key.split('-').map((word, index) => index == 0 ? word : capitalize(word)).join(''); if (aliasAttribute in cssStyleDeclaration) { propertiesInPrototypeChain[aliasAttribute] = true; } else if (capitalize(aliasAttribute) in cssStyleDeclaration) { propertiesInPrototypeChain[capitalize(aliasAttribute)] = true; } } else if (isAliasAttribute) { const namedAttribute = key.replace(caps, (char) => '-' + char.toLowerCase()); if (namedAttribute in cssStyleDeclaration) { propertiesInPrototypeChain[namedAttribute] = true; } else if (`-${namedAttribute}` in cssStyleDeclaration) { propertiesInPrototypeChain[`-${namedAttribute}`] = true; } } return; }); // compile keys const keys = [ ...new Set([ ...prototypeProperties, ...ownEnumerablePropertyNames, ...Object.keys(propertiesInPrototypeChain), ]), ]; // @ts-ignore const interfaceName = ('' + proto).match(/\[object (.+)\]/)[1]; return { keys, interfaceName }; } catch (error) { captureError(error); return; } }; const getSystemStyles = (el) => { try { const colors = [ 'ActiveBorder', 'ActiveCaption', 'ActiveText', 'AppWorkspace', 'Background', 'ButtonBorder', 'ButtonFace', 'ButtonHighlight', 'ButtonShadow', 'ButtonText', 'Canvas', 'CanvasText', 'CaptionText', 'Field', 'FieldText', 'GrayText', 'Highlight', 'HighlightText', 'InactiveBorder', 'InactiveCaption', 'InactiveCaptionText', 'InfoBackground', 'InfoText', 'LinkText', 'Mark', 'MarkText', 'Menu', 'MenuText', 'Scrollbar', 'ThreeDDarkShadow', 'ThreeDFace', 'ThreeDHighlight', 'ThreeDLightShadow', 'ThreeDShadow', 'VisitedText', 'Window', 'WindowFrame', 'WindowText', ]; const fonts = [ 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', ]; const getStyles = (el) => ({ colors: colors.map((color) => { el.setAttribute('style', `background-color: ${color} !important`); return { [color]: getComputedStyle(el).backgroundColor, }; }), fonts: fonts.map((font) => { el.setAttribute('style', `font: ${font} !important`); const computedStyle = getComputedStyle(el); return { [font]: `${computedStyle.fontSize} ${computedStyle.fontFamily}`, }; }), }); if (!el) { el = document.createElement('div'); document.body.append(el); const systemStyles = getStyles(el); el.parentNode.removeChild(el); return systemStyles; } return getStyles(el); } catch (error) { captureError(error); return; } }; try { const timer = createTimer(); timer.start(); const computedStyle = computeStyle('getComputedStyle', { require: [captureError] }); const system = getSystemStyles(PARENT_PHANTOM); logTestResult({ time: timer.stop(), test: 'computed style', passed: true }); return { computedStyle, system, }; } catch (error) { logTestResult({ test: 'computed style', passed: false }); captureError(error); return; } } function cssHTML(fp) { if (!fp.css) { return `
Computed Style
keys (0): ${HTMLNote.BLOCKED}
system styles: ${HTMLNote.BLOCKED}
`; } const { css: data, } = fp; const { $hash, computedStyle, system, } = data; const colorsLen = system.colors.length; const gradientColors = system.colors.map((color, index) => { const name = Object.values(color)[0]; return (index == 0 ? `${name}, ${name} ${((index + 1) / colorsLen * 100).toFixed(2)}%` : index == colorsLen - 1 ? `${name} ${((index - 1) / colorsLen * 100).toFixed(2)}%, ${name} 100%` : `${name} ${(index / colorsLen * 100).toFixed(2)}%, ${name} ${((index + 1) / colorsLen * 100).toFixed(2)}%`); }); const id = 'creep-css-style-declaration-version'; return `
${performanceLogger.getLog()['computed style']} Computed Style${hashSlice($hash)}
keys (${!computedStyle ? '0' : count(computedStyle.keys)}): ${!computedStyle ? HTMLNote.BLOCKED : modal('creep-computed-style', computedStyle.keys.join(', '), hashMini(computedStyle))}
system styles: ${system && system.colors ? modal(`${id}-system-styles`, [ ...system.colors.map((color) => { const key = Object.keys(color)[0]; const val = color[key]; return `
${key}: ${val}
`; }), ...system.fonts.map((font) => { const key = Object.keys(font)[0]; const val = font[key]; return `
${key}: ${val}
`; }), ].join(''), hashMini(system)) : HTMLNote.BLOCKED}
`; } function getCSSMedia() { const gcd = (a, b) => b == 0 ? a : gcd(b, a % b); const getAspectRatio = (width, height) => { const r = gcd(width, height); const aspectRatio = `${width / r}/${height / r}`; return aspectRatio; }; const query = ({ body, type, rangeStart, rangeLen }) => { const html = [...Array(rangeLen)].map((slot, i) => { i += rangeStart; return `@media(device-${type}:${i}px){body{--device-${type}:${i};}}`; }).join(''); body.innerHTML = ``; const style = getComputedStyle(body); return style.getPropertyValue(`--device-${type}`).trim(); }; const getScreenMedia = ({ body, width, height }) => { let widthMatch = query({ body, type: 'width', rangeStart: width, rangeLen: 1 }); let heightMatch = query({ body, type: 'height', rangeStart: height, rangeLen: 1 }); if (widthMatch && heightMatch) { return { width, height }; } const rangeLen = 1000; [...Array(10)].find((slot, i) => { if (!widthMatch) { widthMatch = query({ body, type: 'width', rangeStart: i * rangeLen, rangeLen }); } if (!heightMatch) { heightMatch = query({ body, type: 'height', rangeStart: i * rangeLen, rangeLen }); } return widthMatch && heightMatch; }); return { width: +widthMatch, height: +heightMatch }; }; try { const timer = createTimer(); timer.start(); const win = PHANTOM_DARKNESS.window; const { body } = win.document; const { width, availWidth, height, availHeight } = win.screen; const noTaskbar = !(width - availWidth || height - availHeight); if (screen.width !== width || (width > 800 && noTaskbar)) { LowerEntropy.IFRAME_SCREEN = true; } const deviceAspectRatio = getAspectRatio(width, height); const matchMediaCSS = { ['prefers-reduced-motion']: (win.matchMedia('(prefers-reduced-motion: no-preference)').matches ? 'no-preference' : win.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'reduce' : undefined), ['prefers-color-scheme']: ( // prefer main window matchMedia('(prefers-color-scheme: light)').matches ? 'light' : matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : undefined), monochrome: (win.matchMedia('(monochrome)').matches ? 'monochrome' : win.matchMedia('(monochrome: 0)').matches ? 'non-monochrome' : undefined), ['inverted-colors']: (win.matchMedia('(inverted-colors: inverted)').matches ? 'inverted' : win.matchMedia('(inverted-colors: none)').matches ? 'none' : undefined), ['forced-colors']: (win.matchMedia('(forced-colors: none)').matches ? 'none' : win.matchMedia('(forced-colors: active)').matches ? 'active' : undefined), ['any-hover']: (win.matchMedia('(any-hover: hover)').matches ? 'hover' : win.matchMedia('(any-hover: none)').matches ? 'none' : undefined), hover: (win.matchMedia('(hover: hover)').matches ? 'hover' : win.matchMedia('(hover: none)').matches ? 'none' : undefined), ['any-pointer']: (win.matchMedia('(any-pointer: fine)').matches ? 'fine' : win.matchMedia('(any-pointer: coarse)').matches ? 'coarse' : win.matchMedia('(any-pointer: none)').matches ? 'none' : undefined), pointer: (win.matchMedia('(pointer: fine)').matches ? 'fine' : win.matchMedia('(pointer: coarse)').matches ? 'coarse' : win.matchMedia('(pointer: none)').matches ? 'none' : undefined), ['device-aspect-ratio']: (win.matchMedia(`(device-aspect-ratio: ${deviceAspectRatio})`).matches ? deviceAspectRatio : undefined), ['device-screen']: (win.matchMedia(`(device-width: ${width}px) and (device-height: ${height}px)`).matches ? `${width} x ${height}` : undefined), ['display-mode']: (win.matchMedia('(display-mode: fullscreen)').matches ? 'fullscreen' : win.matchMedia('(display-mode: standalone)').matches ? 'standalone' : win.matchMedia('(display-mode: minimal-ui)').matches ? 'minimal-ui' : win.matchMedia('(display-mode: browser)').matches ? 'browser' : undefined), ['color-gamut']: (win.matchMedia('(color-gamut: rec2020)').matches ? 'rec2020' : win.matchMedia('(color-gamut: p3)').matches ? 'p3' : win.matchMedia('(color-gamut: srgb)').matches ? 'srgb' : undefined), orientation: ( // prefer main window matchMedia('(orientation: landscape)').matches ? 'landscape' : matchMedia('(orientation: portrait)').matches ? 'portrait' : undefined), }; body.innerHTML = ` `; const style = getComputedStyle(body); const mediaCSS = { ['prefers-reduced-motion']: style.getPropertyValue('--prefers-reduced-motion').trim() || undefined, ['prefers-color-scheme']: style.getPropertyValue('--prefers-color-scheme').trim() || undefined, monochrome: style.getPropertyValue('--monochrome').trim() || undefined, ['inverted-colors']: style.getPropertyValue('--inverted-colors').trim() || undefined, ['forced-colors']: style.getPropertyValue('--forced-colors').trim() || undefined, ['any-hover']: style.getPropertyValue('--any-hover').trim() || undefined, hover: style.getPropertyValue('--hover').trim() || undefined, ['any-pointer']: style.getPropertyValue('--any-pointer').trim() || undefined, pointer: style.getPropertyValue('--pointer').trim() || undefined, ['device-aspect-ratio']: style.getPropertyValue('--device-aspect-ratio').trim() || undefined, ['device-screen']: style.getPropertyValue('--device-screen').trim() || undefined, ['display-mode']: style.getPropertyValue('--display-mode').trim() || undefined, ['color-gamut']: style.getPropertyValue('--color-gamut').trim() || undefined, orientation: style.getPropertyValue('--orientation').trim() || undefined, }; // get screen query const screenQuery = getScreenMedia({ body, width, height }); logTestResult({ time: timer.stop(), test: 'css media', passed: true }); return { mediaCSS, matchMediaCSS, screenQuery }; } catch (error) { logTestResult({ test: 'css media', passed: false }); captureError(error); return; } } function cssMediaHTML(fp) { if (!fp.css) { return `
CSS Media Queries
@media: ${HTMLNote.BLOCKED}
matchMedia: ${HTMLNote.BLOCKED}
touch device: ${HTMLNote.BLOCKED}
screen query: ${HTMLNote.BLOCKED}
`; } const { cssMedia: data, } = fp; const { $hash, mediaCSS, matchMediaCSS, screenQuery, } = data; return `
${performanceLogger.getLog()['css media']} CSS Media Queries${hashSlice($hash)}
@media: ${!mediaCSS || !Object.keys(mediaCSS).filter((key) => !!mediaCSS[key]).length ? HTMLNote.BLOCKED : modal('creep-css-media', `@media

${Object.keys(mediaCSS).map((key) => `${key}: ${mediaCSS[key] || HTMLNote.UNSUPPORTED}`).join('
')}`, hashMini(mediaCSS))}
matchMedia: ${!matchMediaCSS || !Object.keys(matchMediaCSS).filter((key) => !!matchMediaCSS[key]).length ? HTMLNote.BLOCKED : modal('creep-css-match-media', `matchMedia

${Object.keys(matchMediaCSS).map((key) => `${key}: ${matchMediaCSS[key] || HTMLNote.UNSUPPORTED}`).join('
')}`, hashMini(matchMediaCSS))}
touch device: ${!mediaCSS ? HTMLNote.BLOCKED : mediaCSS['any-pointer'] == 'coarse' ? true : HTMLNote.UNKNOWN}
screen query: ${!screenQuery ? HTMLNote.BLOCKED : `${screenQuery.width} x ${screenQuery.height}`}
`; } function getHTMLElementVersion() { try { const timer = createTimer(); timer.start(); const keys = []; // eslint-disable-next-line guard-for-in for (const key in document.documentElement) { keys.push(key); } logTestResult({ time: timer.stop(), test: 'html element', passed: true }); return { keys }; } catch (error) { logTestResult({ test: 'html element', passed: false }); captureError(error); return; } } function htmlElementVersionHTML(fp) { if (!fp.htmlElementVersion) { return `
HTMLElement
keys (0): ${HTMLNote.Blocked}
`; } const { htmlElementVersion: { $hash, keys, }, } = fp; return `
${performanceLogger.getLog()['html element']} HTMLElement${hashSlice($hash)}
keys (${count(keys)}): ${keys && keys.length ? modal('creep-html-element-version', keys.join(', ')) : HTMLNote.Blocked}
`; } function getRectSum(rect) { return Object.keys(rect).reduce((acc, key) => acc += rect[key], 0) / 100000000; } // inspired by // https://privacycheck.sec.lrz.de/active/fp_gcr/fp_getclientrects.html // https://privacycheck.sec.lrz.de/active/fp_e/fp_emoji.html async function getClientRects() { try { const timer = createTimer(); await queueEvent(timer); const toNativeObject = (domRect) => { return { bottom: domRect.bottom, height: domRect.height, left: domRect.left, right: domRect.right, width: domRect.width, top: domRect.top, x: domRect.x, y: domRect.y, }; }; let lied = (lieProps['Element.getClientRects'] || lieProps['Element.getBoundingClientRect'] || lieProps['Range.getClientRects'] || lieProps['Range.getBoundingClientRect'] || lieProps['String.fromCodePoint']) || false; const DOC = (PHANTOM_DARKNESS && PHANTOM_DARKNESS.document && PHANTOM_DARKNESS.document.body ? PHANTOM_DARKNESS.document : document); const getBestRect = (el) => { let range; if (!lieProps['Element.getClientRects']) { return el.getClientRects()[0]; } else if (!lieProps['Element.getBoundingClientRect']) { return el.getBoundingClientRect(); } else if (!lieProps['Range.getClientRects']) { range = DOC.createRange(); range.selectNode(el); return range.getClientRects()[0]; } range = DOC.createRange(); range.selectNode(el); return range.getBoundingClientRect(); }; const rectsId = `${instanceId}-client-rects-div`; const divElement = document.createElement('div'); divElement.setAttribute('id', rectsId); DOC.body.appendChild(divElement); patch(divElement, html `
${EMOJIS.map((emoji) => { return `
${emoji}
`; }).join('')}
`); // get emoji set and system const pattern = new Set(); await queueEvent(timer); const emojiElems = [...DOC.getElementsByClassName('domrect-emoji')]; const emojiSet = emojiElems.reduce((emojiSet, el, i) => { const emoji = EMOJIS[i]; const { height, width } = getBestRect(el); const dimensions = `${width},${height}`; if (!pattern.has(dimensions)) { pattern.add(dimensions); emojiSet.add(emoji); } return emojiSet; }, new Set()); const domrectSystemSum = 0.00001 * [...pattern].map((x) => { return x.split(',').reduce((acc, x) => acc += (+x || 0), 0); }).reduce((acc, x) => acc += x, 0); // get clientRects const range = document.createRange(); const rectElems = DOC.getElementsByClassName('rects'); const elementClientRects = [...rectElems].map((el) => { return toNativeObject(el.getClientRects()[0]); }); const elementBoundingClientRect = [...rectElems].map((el) => { return toNativeObject(el.getBoundingClientRect()); }); const rangeClientRects = [...rectElems].map((el) => { range.selectNode(el); return toNativeObject(range.getClientRects()[0]); }); const rangeBoundingClientRect = [...rectElems].map((el) => { range.selectNode(el); return toNativeObject(range.getBoundingClientRect()); }); // detect failed shift calculation // inspired by https://arkenfox.github.io/TZP const rect4 = [...rectElems][3]; const { top: initialTop } = elementClientRects[3]; rect4.classList.add('shift-dom-rect'); const { top: shiftedTop } = toNativeObject(rect4.getClientRects()[0]); rect4.classList.remove('shift-dom-rect'); const { top: unshiftedTop } = toNativeObject(rect4.getClientRects()[0]); const diff = initialTop - shiftedTop; const unshiftLie = diff != (unshiftedTop - shiftedTop); if (unshiftLie) { lied = true; documentLie('Element.getClientRects', 'failed unshift calculation'); } // detect failed math calculation lie let mathLie = false; elementClientRects.forEach((rect) => { const { right, left, width, bottom, top, height, x, y } = rect; if (right - left != width || bottom - top != height || right - x != width || bottom - y != height) { lied = true; mathLie = true; } return; }); if (mathLie) { documentLie('Element.getClientRects', 'failed math calculation'); } // detect equal elements mismatch lie const { right: right1, left: left1 } = elementClientRects[10]; const { right: right2, left: left2 } = elementClientRects[11]; if (right1 != right2 || left1 != left2) { documentLie('Element.getClientRects', 'equal elements mismatch'); lied = true; } // detect unknown rotate dimensions const knownEl = [...DOC.getElementsByClassName('rect-known')][0]; const knownDimensions = toNativeObject(knownEl.getClientRects()[0]); const knownHash = hashMini(knownDimensions); if (IS_BLINK) { if (devicePixelRatio === 1 && knownHash !== '9d9215cc') { documentLie('Element.getClientRects', 'unknown rotate dimensions'); lied = true; } } else if (IS_GECKO) { const Rotate = { 'e38453f0': true, // 100, etc }; if (!Rotate[knownHash]) { documentLie('Element.getClientRects', 'unknown rotate dimensions'); lied = true; } } // detect ghost dimensions const ghostEl = [...DOC.getElementsByClassName('rect-ghost')][0]; const ghostDimensions = toNativeObject(ghostEl.getClientRects()[0]); const hasGhostDimensions = Object.keys(ghostDimensions) .some((key) => ghostDimensions[key] !== 0); if (hasGhostDimensions) { documentLie('Element.getClientRects', 'unknown ghost dimensions'); lied = true; } DOC.body.removeChild(DOC.getElementById(rectsId)); logTestResult({ time: timer.stop(), test: 'rects', passed: true }); return { elementClientRects, elementBoundingClientRect, rangeClientRects, rangeBoundingClientRect, emojiSet: [...emojiSet], domrectSystemSum, lied, }; } catch (error) { logTestResult({ test: 'rects', passed: false }); captureError(error); return; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any function clientRectsHTML(fp) { if (!fp.clientRects) { return `
DOMRect
elems A: ${HTMLNote.BLOCKED}
elems B: ${HTMLNote.BLOCKED}
range A: ${HTMLNote.BLOCKED}
range B: ${HTMLNote.BLOCKED}
${HTMLNote.BLOCKED}
`; } const { clientRects: { $hash, elementClientRects, elementBoundingClientRect, rangeClientRects, rangeBoundingClientRect, emojiSet, domrectSystemSum, lied, }, } = fp; const computeDiffs = (rects) => { if (!rects || !rects.length) { return; } const expectedSum = rects.reduce((acc, rect) => { const { right, left, width, bottom, top, height } = rect; const expected = { width: right - left, height: bottom - top, right: left + width, left: right - width, bottom: top + height, top: bottom - height, x: right - width, y: bottom - height, }; return acc += getRectSum(expected); }, 0); const actualSum = rects.reduce((acc, rect) => acc += getRectSum(rect), 0); return getDiffs({ stringA: actualSum, stringB: expectedSum, charDiff: true, decorate: (diff) => `${diff}`, }); }; const helpTitle = `Element.getClientRects()\nhash: ${hashMini(emojiSet)}\n${emojiSet.map((x, i) => i && (i % 6 == 0) ? `${x}\n` : x).join('')}`; return `
${performanceLogger.getLog().rects} DOMRect${hashSlice($hash)}
elems A: ${computeDiffs(elementClientRects)}
elems B: ${computeDiffs(elementBoundingClientRect)}
range A: ${computeDiffs(rangeClientRects)}
range B: ${computeDiffs(rangeBoundingClientRect)}
${domrectSystemSum || HTMLNote.UNSUPPORTED} ${formatEmojiSet(emojiSet)}
`; } function getErrors(errFns) { const errors = []; let i; const len = errFns.length; for (i = 0; i < len; i++) { try { errFns[i](); } catch (err) { errors.push(err.message); } } return errors; } function getConsoleErrors() { try { const timer = createTimer(); timer.start(); const errorTests = [ () => new Function('alert(")')(), () => new Function('const foo;foo.bar')(), () => new Function('null.bar')(), () => new Function('abc.xyz = 123')(), () => new Function('const foo;foo.bar')(), () => new Function('(1).toString(1000)')(), () => new Function('[...undefined].length')(), () => new Function('var x = new Array(-1)')(), () => new Function('const a=1; const a=2;')(), ]; const errors = getErrors(errorTests); logTestResult({ time: timer.stop(), test: 'console errors', passed: true }); return { errors }; } catch (error) { logTestResult({ test: 'console errors', passed: false }); captureError(error); return; } } function consoleErrorsHTML(fp) { if (!fp.consoleErrors) { return `
Error
results: ${HTMLNote.BLOCKED}
`; } const { consoleErrors: { $hash, errors, }, } = fp; const results = Object.keys(errors).map((key) => { const value = errors[key]; return `${+key + 1}: ${value}`; }); return `
${performanceLogger.getLog()['console errors']} Error${hashSlice($hash)}
results: ${modal('creep-console-errors', results.join('
'))}
`; } /* Steps to update: 0. get beta release desktop/mobile 1. get diffs from template 2. update feature list 3. update stable features object */ const getStableFeatures = () => ({ 'Chrome': { version: 115, windowKeys: `Object, Function, Array, Number, parseFloat, parseInt, Infinity, NaN, undefined, Boolean, String, Symbol, Date, Promise, RegExp, Error, AggregateError, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, globalThis, JSON, Math, Intl, ArrayBuffer, Atomics, Uint8Array, Int8Array, Uint16Array, Int16Array, Uint32Array, Int32Array, Float32Array, Float64Array, Uint8ClampedArray, BigUint64Array, BigInt64Array, DataView, Map, BigInt, Set, WeakMap, WeakSet, Proxy, Reflect, FinalizationRegistry, WeakRef, decodeURI, decodeURIComponent, encodeURI, encodeURIComponent, escape, unescape, eval, isFinite, isNaN, console, Option, Image, Audio, webkitURL, webkitRTCPeerConnection, webkitMediaStream, WebKitMutationObserver, WebKitCSSMatrix, XSLTProcessor, XPathResult, XPathExpression, XPathEvaluator, XMLSerializer, XMLHttpRequestUpload, XMLHttpRequestEventTarget, XMLHttpRequest, XMLDocument, WritableStreamDefaultWriter, WritableStreamDefaultController, WritableStream, Worker, Window, WheelEvent, WebSocket, WebGLVertexArrayObject, WebGLUniformLocation, WebGLTransformFeedback, WebGLTexture, WebGLSync, WebGLShaderPrecisionFormat, WebGLShader, WebGLSampler, WebGLRenderingContext, WebGLRenderbuffer, WebGLQuery, WebGLProgram, WebGLFramebuffer, WebGLContextEvent, WebGLBuffer, WebGLActiveInfo, WebGL2RenderingContext, WaveShaperNode, VisualViewport, VirtualKeyboardGeometryChangeEvent, ValidityState, VTTCue, UserActivation, URLSearchParams, URLPattern, URL, UIEvent, TrustedTypePolicyFactory, TrustedTypePolicy, TrustedScriptURL, TrustedScript, TrustedHTML, TreeWalker, TransitionEvent, TransformStreamDefaultController, TransformStream, TrackEvent, TouchList, TouchEvent, Touch, TimeRanges, TextTrackList, TextTrackCueList, TextTrackCue, TextTrack, TextMetrics, TextEvent, TextEncoderStream, TextEncoder, TextDecoderStream, TextDecoder, Text, TaskSignal, TaskPriorityChangeEvent, TaskController, TaskAttributionTiming, SyncManager, SubmitEvent, StyleSheetList, StyleSheet, StylePropertyMapReadOnly, StylePropertyMap, StorageEvent, Storage, StereoPannerNode, StaticRange, SourceBufferList, SourceBuffer, ShadowRoot, Selection, SecurityPolicyViolationEvent, ScriptProcessorNode, ScreenOrientation, Screen, Scheduling, Scheduler, SVGViewElement, SVGUseElement, SVGUnitTypes, SVGTransformList, SVGTransform, SVGTitleElement, SVGTextPositioningElement, SVGTextPathElement, SVGTextElement, SVGTextContentElement, SVGTSpanElement, SVGSymbolElement, SVGSwitchElement, SVGStyleElement, SVGStringList, SVGStopElement, SVGSetElement, SVGScriptElement, SVGSVGElement, SVGRectElement, SVGRect, SVGRadialGradientElement, SVGPreserveAspectRatio, SVGPolylineElement, SVGPolygonElement, SVGPointList, SVGPoint, SVGPatternElement, SVGPathElement, SVGNumberList, SVGNumber, SVGMetadataElement, SVGMatrix, SVGMaskElement, SVGMarkerElement, SVGMPathElement, SVGLinearGradientElement, SVGLineElement, SVGLengthList, SVGLength, SVGImageElement, SVGGraphicsElement, SVGGradientElement, SVGGeometryElement, SVGGElement, SVGForeignObjectElement, SVGFilterElement, SVGFETurbulenceElement, SVGFETileElement, SVGFESpotLightElement, SVGFESpecularLightingElement, SVGFEPointLightElement, SVGFEOffsetElement, SVGFEMorphologyElement, SVGFEMergeNodeElement, SVGFEMergeElement, SVGFEImageElement, SVGFEGaussianBlurElement, SVGFEFuncRElement, SVGFEFuncGElement, SVGFEFuncBElement, SVGFEFuncAElement, SVGFEFloodElement, SVGFEDropShadowElement, SVGFEDistantLightElement, SVGFEDisplacementMapElement, SVGFEDiffuseLightingElement, SVGFEConvolveMatrixElement, SVGFECompositeElement, SVGFEComponentTransferElement, SVGFEColorMatrixElement, SVGFEBlendElement, SVGEllipseElement, SVGElement, SVGDescElement, SVGDefsElement, SVGComponentTransferFunctionElement, SVGClipPathElement, SVGCircleElement, SVGAnimationElement, SVGAnimatedTransformList, SVGAnimatedString, SVGAnimatedRect, SVGAnimatedPreserveAspectRatio, SVGAnimatedNumberList, SVGAnimatedNumber, SVGAnimatedLengthList, SVGAnimatedLength, SVGAnimatedInteger, SVGAnimatedEnumeration, SVGAnimatedBoolean, SVGAnimatedAngle, SVGAnimateTransformElement, SVGAnimateMotionElement, SVGAnimateElement, SVGAngle, SVGAElement, Response, ResizeObserverSize, ResizeObserverEntry, ResizeObserver, Request, ReportingObserver, ReadableStreamDefaultReader, ReadableStreamDefaultController, ReadableStreamBYOBRequest, ReadableStreamBYOBReader, ReadableStream, ReadableByteStreamController, Range, RadioNodeList, RTCTrackEvent, RTCStatsReport, RTCSessionDescription, RTCSctpTransport, RTCRtpTransceiver, RTCRtpSender, RTCRtpReceiver, RTCPeerConnectionIceEvent, RTCPeerConnectionIceErrorEvent, RTCPeerConnection, RTCIceTransport, RTCIceCandidate, RTCErrorEvent, RTCError, RTCEncodedVideoFrame, RTCEncodedAudioFrame, RTCDtlsTransport, RTCDataChannelEvent, RTCDataChannel, RTCDTMFToneChangeEvent, RTCDTMFSender, RTCCertificate, PromiseRejectionEvent, ProgressEvent, Profiler, ProcessingInstruction, PopStateEvent, PointerEvent, PluginArray, Plugin, PictureInPictureWindow, PictureInPictureEvent, PeriodicWave, PerformanceTiming, PerformanceServerTiming, PerformanceResourceTiming, PerformancePaintTiming, PerformanceObserverEntryList, PerformanceObserver, PerformanceNavigationTiming, PerformanceNavigation, PerformanceMeasure, PerformanceMark, PerformanceLongTaskTiming, PerformanceEventTiming, PerformanceEntry, PerformanceElementTiming, Performance, Path2D, PannerNode, PageTransitionEvent, OverconstrainedError, OscillatorNode, OffscreenCanvasRenderingContext2D, OffscreenCanvas, OfflineAudioContext, OfflineAudioCompletionEvent, NodeList, NodeIterator, NodeFilter, Node, NetworkInformation, Navigator, NavigationTransition, NavigationHistoryEntry, NavigationDestination, NavigationCurrentEntryChangeEvent, Navigation, NavigateEvent, NamedNodeMap, MutationRecord, MutationObserver, MutationEvent, MouseEvent, MimeTypeArray, MimeType, MessagePort, MessageEvent, MessageChannel, MediaStreamTrackProcessor, MediaStreamTrackGenerator, MediaStreamTrackEvent, MediaStreamTrack, MediaStreamEvent, MediaStreamAudioSourceNode, MediaStreamAudioDestinationNode, MediaStream, MediaSourceHandle, MediaSource, MediaRecorder, MediaQueryListEvent, MediaQueryList, MediaList, MediaError, MediaEncryptedEvent, MediaElementAudioSourceNode, MediaCapabilities, Location, LayoutShiftAttribution, LayoutShift, LargestContentfulPaint, KeyframeEffect, KeyboardEvent, IntersectionObserverEntry, IntersectionObserver, InputEvent, InputDeviceInfo, InputDeviceCapabilities, ImageData, ImageCapture, ImageBitmapRenderingContext, ImageBitmap, IdleDeadline, IIRFilterNode, IDBVersionChangeEvent, IDBTransaction, IDBRequest, IDBOpenDBRequest, IDBObjectStore, IDBKeyRange, IDBIndex, IDBFactory, IDBDatabase, IDBCursorWithValue, IDBCursor, History, Headers, HashChangeEvent, HTMLVideoElement, HTMLUnknownElement, HTMLUListElement, HTMLTrackElement, HTMLTitleElement, HTMLTimeElement, HTMLTextAreaElement, HTMLTemplateElement, HTMLTableSectionElement, HTMLTableRowElement, HTMLTableElement, HTMLTableColElement, HTMLTableCellElement, HTMLTableCaptionElement, HTMLStyleElement, HTMLSpanElement, HTMLSourceElement, HTMLSlotElement, HTMLSelectElement, HTMLScriptElement, HTMLQuoteElement, HTMLProgressElement, HTMLPreElement, HTMLPictureElement, HTMLParamElement, HTMLParagraphElement, HTMLOutputElement, HTMLOptionsCollection, HTMLOptionElement, HTMLOptGroupElement, HTMLObjectElement, HTMLOListElement, HTMLModElement, HTMLMeterElement, HTMLMetaElement, HTMLMenuElement, HTMLMediaElement, HTMLMarqueeElement, HTMLMapElement, HTMLLinkElement, HTMLLegendElement, HTMLLabelElement, HTMLLIElement, HTMLInputElement, HTMLImageElement, HTMLIFrameElement, HTMLHtmlElement, HTMLHeadingElement, HTMLHeadElement, HTMLHRElement, HTMLFrameSetElement, HTMLFrameElement, HTMLFormElement, HTMLFormControlsCollection, HTMLFontElement, HTMLFieldSetElement, HTMLEmbedElement, HTMLElement, HTMLDocument, HTMLDivElement, HTMLDirectoryElement, HTMLDialogElement, HTMLDetailsElement, HTMLDataListElement, HTMLDataElement, HTMLDListElement, HTMLCollection, HTMLCanvasElement, HTMLButtonElement, HTMLBodyElement, HTMLBaseElement, HTMLBRElement, HTMLAudioElement, HTMLAreaElement, HTMLAnchorElement, HTMLAllCollection, GeolocationPositionError, GeolocationPosition, GeolocationCoordinates, Geolocation, GamepadHapticActuator, GamepadEvent, GamepadButton, Gamepad, GainNode, FormDataEvent, FormData, FontFaceSetLoadEvent, FontFace, FocusEvent, FileReader, FileList, File, FeaturePolicy, External, EventTarget, EventSource, EventCounts, Event, ErrorEvent, ElementInternals, Element, DynamicsCompressorNode, DragEvent, DocumentType, DocumentFragment, Document, DelayNode, DecompressionStream, DataTransferItemList, DataTransferItem, DataTransfer, DOMTokenList, DOMStringMap, DOMStringList, DOMRectReadOnly, DOMRectList, DOMRect, DOMQuad, DOMPointReadOnly, DOMPoint, DOMParser, DOMMatrixReadOnly, DOMMatrix, DOMImplementation, DOMException, DOMError, CustomStateSet, CustomEvent, CustomElementRegistry, Crypto, CountQueuingStrategy, ConvolverNode, ConstantSourceNode, CompressionStream, CompositionEvent, Comment, CloseEvent, ClipboardEvent, CharacterData, ChannelSplitterNode, ChannelMergerNode, CanvasRenderingContext2D, CanvasPattern, CanvasGradient, CanvasCaptureMediaStreamTrack, CSSVariableReferenceValue, CSSUnparsedValue, CSSUnitValue, CSSTranslate, CSSTransformValue, CSSTransformComponent, CSSSupportsRule, CSSStyleValue, CSSStyleSheet, CSSStyleRule, CSSStyleDeclaration, CSSSkewY, CSSSkewX, CSSSkew, CSSScale, CSSRuleList, CSSRule, CSSRotate, CSSPropertyRule, CSSPositionValue, CSSPerspective, CSSPageRule, CSSNumericValue, CSSNumericArray, CSSNamespaceRule, CSSMediaRule, CSSMatrixComponent, CSSMathValue, CSSMathSum, CSSMathProduct, CSSMathNegate, CSSMathMin, CSSMathMax, CSSMathInvert, CSSMathClamp, CSSLayerStatementRule, CSSLayerBlockRule, CSSKeywordValue, CSSKeyframesRule, CSSKeyframeRule, CSSImportRule, CSSImageValue, CSSGroupingRule, CSSFontPaletteValuesRule, CSSFontFaceRule, CSSCounterStyleRule, CSSContainerRule, CSSConditionRule, CSS, CDATASection, ByteLengthQueuingStrategy, BroadcastChannel, BlobEvent, Blob, BiquadFilterNode, BeforeUnloadEvent, BeforeInstallPromptEvent, BaseAudioContext, BarProp, AudioWorkletNode, AudioSinkInfo, AudioScheduledSourceNode, AudioProcessingEvent, AudioParamMap, AudioParam, AudioNode, AudioListener, AudioDestinationNode, AudioContext, AudioBufferSourceNode, AudioBuffer, Attr, AnimationEvent, AnimationEffect, Animation, AnalyserNode, AbstractRange, AbortSignal, AbortController, window, self, document, name, location, customElements, history, navigation, locationbar, menubar, personalbar, scrollbars, statusbar, toolbar, status, closed, frames, length, top, opener, parent, frameElement, navigator, origin, external, screen, innerWidth, innerHeight, scrollX, pageXOffset, scrollY, pageYOffset, visualViewport, screenX, screenY, outerWidth, outerHeight, devicePixelRatio, event, clientInformation, offscreenBuffering, screenLeft, screenTop, styleMedia, onsearch, isSecureContext, trustedTypes, performance, onappinstalled, onbeforeinstallprompt, crypto, indexedDB, sessionStorage, localStorage, onbeforexrselect, onabort, onbeforeinput, onblur, oncancel, oncanplay, oncanplaythrough, onchange, onclick, onclose, oncontextlost, oncontextmenu, oncontextrestored, oncuechange, ondblclick, ondrag, ondragend, ondragenter, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, onerror, onfocus, onformdata, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onload, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmouseenter, onmouseleave, onmousemove, onmouseout, onmouseover, onmouseup, onmousewheel, onpause, onplay, onplaying, onprogress, onratechange, onreset, onresize, onscroll, onsecuritypolicyviolation, onseeked, onseeking, onselect, onslotchange, onstalled, onsubmit, onsuspend, ontimeupdate, ontoggle, onvolumechange, onwaiting, onwebkitanimationend, onwebkitanimationiteration, onwebkitanimationstart, onwebkittransitionend, onwheel, onauxclick, ongotpointercapture, onlostpointercapture, onpointerdown, onpointermove, onpointerrawupdate, onpointerup, onpointercancel, onpointerover, onpointerout, onpointerenter, onpointerleave, onselectstart, onselectionchange, onanimationend, onanimationiteration, onanimationstart, ontransitionrun, ontransitionstart, ontransitionend, ontransitioncancel, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onlanguagechange, onmessage, onmessageerror, onoffline, ononline, onpagehide, onpageshow, onpopstate, onrejectionhandled, onstorage, onunhandledrejection, onunload, crossOriginIsolated, scheduler, alert, atob, blur, btoa, cancelAnimationFrame, cancelIdleCallback, captureEvents, clearInterval, clearTimeout, close, confirm, createImageBitmap, fetch, find, focus, getComputedStyle, getSelection, matchMedia, moveBy, moveTo, open, postMessage, print, prompt, queueMicrotask, releaseEvents, reportError, requestAnimationFrame, requestIdleCallback, resizeBy, resizeTo, scroll, scrollBy, scrollTo, setInterval, setTimeout, stop, structuredClone, webkitCancelAnimationFrame, webkitRequestAnimationFrame, chrome, WebAssembly, credentialless, caches, cookieStore, ondevicemotion, ondeviceorientation, ondeviceorientationabsolute, launchQueue, onbeforematch, onbeforetoggle, AbsoluteOrientationSensor, Accelerometer, AudioWorklet, BatteryManager, Cache, CacheStorage, Clipboard, ClipboardItem, CookieChangeEvent, CookieStore, CookieStoreManager, Credential, CredentialsContainer, CryptoKey, DeviceMotionEvent, DeviceMotionEventAcceleration, DeviceMotionEventRotationRate, DeviceOrientationEvent, FederatedCredential, GravitySensor, Gyroscope, Keyboard, KeyboardLayoutMap, LinearAccelerationSensor, Lock, LockManager, MIDIAccess, MIDIConnectionEvent, MIDIInput, MIDIInputMap, MIDIMessageEvent, MIDIOutput, MIDIOutputMap, MIDIPort, MediaDeviceInfo, MediaDevices, MediaKeyMessageEvent, MediaKeySession, MediaKeyStatusMap, MediaKeySystemAccess, MediaKeys, NavigationPreloadManager, NavigatorManagedData, OrientationSensor, PasswordCredential, RelativeOrientationSensor, Sanitizer, ScreenDetailed, ScreenDetails, Sensor, SensorErrorEvent, ServiceWorker, ServiceWorkerContainer, ServiceWorkerRegistration, StorageManager, SubtleCrypto, VirtualKeyboard, WebTransport, WebTransportBidirectionalStream, WebTransportDatagramDuplexStream, WebTransportError, Worklet, XRDOMOverlayState, XRLayer, XRWebGLBinding, AudioData, EncodedAudioChunk, EncodedVideoChunk, ImageTrack, ImageTrackList, VideoColorSpace, VideoFrame, AudioDecoder, AudioEncoder, ImageDecoder, VideoDecoder, VideoEncoder, AuthenticatorAssertionResponse, AuthenticatorAttestationResponse, AuthenticatorResponse, PublicKeyCredential, Bluetooth, BluetoothCharacteristicProperties, BluetoothDevice, BluetoothRemoteGATTCharacteristic, BluetoothRemoteGATTDescriptor, BluetoothRemoteGATTServer, BluetoothRemoteGATTService, CaptureController, EyeDropper, FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemHandle, FileSystemWritableFileStream, FontData, FragmentDirective, GPU, GPUAdapter, GPUAdapterInfo, GPUBindGroup, GPUBindGroupLayout, GPUBuffer, GPUBufferUsage, GPUCanvasContext, GPUColorWrite, GPUCommandBuffer, GPUCommandEncoder, GPUCompilationInfo, GPUCompilationMessage, GPUComputePassEncoder, GPUComputePipeline, GPUDevice, GPUDeviceLostInfo, GPUError, GPUExternalTexture, GPUInternalError, GPUMapMode, GPUOutOfMemoryError, GPUPipelineError, GPUPipelineLayout, GPUQuerySet, GPUQueue, GPURenderBundle, GPURenderBundleEncoder, GPURenderPassEncoder, GPURenderPipeline, GPUSampler, GPUShaderModule, GPUShaderStage, GPUSupportedFeatures, GPUSupportedLimits, GPUTexture, GPUTextureUsage, GPUTextureView, GPUUncapturedErrorEvent, GPUValidationError, HID, HIDConnectionEvent, HIDDevice, HIDInputReportEvent, IdentityCredential, IdleDetector, LaunchParams, LaunchQueue, OTPCredential, PaymentAddress, PaymentRequest, PaymentResponse, PaymentMethodChangeEvent, Presentation, PresentationAvailability, PresentationConnection, PresentationConnectionAvailableEvent, PresentationConnectionCloseEvent, PresentationConnectionList, PresentationReceiver, PresentationRequest, Serial, SerialPort, ToggleEvent, USB, USBAlternateInterface, USBConfiguration, USBConnectionEvent, USBDevice, USBEndpoint, USBInTransferResult, USBInterface, USBIsochronousInTransferPacket, USBIsochronousInTransferResult, USBIsochronousOutTransferPacket, USBIsochronousOutTransferResult, USBOutTransferResult, WakeLock, WakeLockSentinel, WindowControlsOverlay, WindowControlsOverlayGeometryChangeEvent, XRAnchor, XRAnchorSet, XRBoundedReferenceSpace, XRCPUDepthInformation, XRCamera, XRDepthInformation, XRFrame, XRHitTestResult, XRHitTestSource, XRInputSource, XRInputSourceArray, XRInputSourceEvent, XRInputSourcesChangeEvent, XRLightEstimate, XRLightProbe, XRPose, XRRay, XRReferenceSpace, XRReferenceSpaceEvent, XRRenderState, XRRigidTransform, XRSession, XRSessionEvent, XRSpace, XRSystem, XRTransientInputHitTestResult, XRTransientInputHitTestSource, XRView, XRViewerPose, XRViewport, XRWebGLDepthInformation, XRWebGLLayer, getScreenDetails, queryLocalFonts, showDirectoryPicker, showOpenFilePicker, showSaveFilePicker, originAgentCluster, speechSynthesis, oncontentvisibilityautostatechange, onscrollend, AnimationPlaybackEvent, AnimationTimeline, CSSAnimation, CSSTransition, DocumentTimeline, BackgroundFetchManager, BackgroundFetchRecord, BackgroundFetchRegistration, BluetoothUUID, BrowserCaptureMediaStreamTrack, CropTarget, ContentVisibilityAutoStateChangeEvent, DelegatedInkTrailPresenter, Ink, Highlight, HighlightRegistry, MathMLElement, MediaMetadata, MediaSession, NavigatorUAData, Notification, PaymentManager, PaymentRequestUpdateEvent, PeriodicSyncManager, PermissionStatus, Permissions, PushManager, PushSubscription, PushSubscriptionOptions, RemotePlayback, SharedWorker, SpeechSynthesisErrorEvent, SpeechSynthesisEvent, SpeechSynthesisUtterance, VideoPlaybackQuality, ViewTransition, webkitSpeechGrammar, webkitSpeechGrammarList, webkitSpeechRecognition, webkitSpeechRecognitionError, webkitSpeechRecognitionEvent, openDatabase, webkitRequestFileSystem, webkitResolveLocalFileSystemURL`, cssKeys: `cssText, length, parentRule, cssFloat, getPropertyPriority, getPropertyValue, item, removeProperty, setProperty, constructor, accent-color, align-content, align-items, align-self, alignment-baseline, animation-composition, animation-delay, animation-direction, animation-duration, animation-fill-mode, animation-iteration-count, animation-name, animation-play-state, animation-timing-function, app-region, appearance, backdrop-filter, backface-visibility, background-attachment, background-blend-mode, background-clip, background-color, background-image, background-origin, background-position, background-repeat, background-size, baseline-shift, baseline-source, block-size, border-block-end-color, border-block-end-style, border-block-end-width, border-block-start-color, border-block-start-style, border-block-start-width, border-bottom-color, border-bottom-left-radius, border-bottom-right-radius, border-bottom-style, border-bottom-width, border-collapse, border-end-end-radius, border-end-start-radius, border-image-outset, border-image-repeat, border-image-slice, border-image-source, border-image-width, border-inline-end-color, border-inline-end-style, border-inline-end-width, border-inline-start-color, border-inline-start-style, border-inline-start-width, border-left-color, border-left-style, border-left-width, border-right-color, border-right-style, border-right-width, border-start-end-radius, border-start-start-radius, border-top-color, border-top-left-radius, border-top-right-radius, border-top-style, border-top-width, bottom, box-shadow, box-sizing, break-after, break-before, break-inside, buffered-rendering, caption-side, caret-color, clear, clip, clip-path, clip-rule, color, color-interpolation, color-interpolation-filters, color-rendering, column-count, column-gap, column-rule-color, column-rule-style, column-rule-width, column-span, column-width, contain-intrinsic-block-size, contain-intrinsic-height, contain-intrinsic-inline-size, contain-intrinsic-size, contain-intrinsic-width, container-name, container-type, content, cursor, cx, cy, d, direction, display, dominant-baseline, empty-cells, fill, fill-opacity, fill-rule, filter, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, float, flood-color, flood-opacity, font-family, font-kerning, font-optical-sizing, font-palette, font-size, font-stretch, font-style, font-synthesis-small-caps, font-synthesis-style, font-synthesis-weight, font-variant, font-variant-alternates, font-variant-caps, font-variant-east-asian, font-variant-ligatures, font-variant-numeric, font-weight, grid-auto-columns, grid-auto-flow, grid-auto-rows, grid-column-end, grid-column-start, grid-row-end, grid-row-start, grid-template-areas, grid-template-columns, grid-template-rows, height, hyphenate-character, hyphenate-limit-chars, hyphens, image-orientation, image-rendering, initial-letter, inline-size, inset-block-end, inset-block-start, inset-inline-end, inset-inline-start, isolation, justify-content, justify-items, justify-self, left, letter-spacing, lighting-color, line-break, line-height, list-style-image, list-style-position, list-style-type, margin-block-end, margin-block-start, margin-bottom, margin-inline-end, margin-inline-start, margin-left, margin-right, margin-top, marker-end, marker-mid, marker-start, mask-type, math-depth, math-shift, math-style, max-block-size, max-height, max-inline-size, max-width, min-block-size, min-height, min-inline-size, min-width, mix-blend-mode, object-fit, object-position, object-view-box, offset-distance, offset-path, offset-rotate, opacity, order, orphans, outline-color, outline-offset, outline-style, outline-width, overflow-anchor, overflow-clip-margin, overflow-wrap, overflow-x, overflow-y, overscroll-behavior-block, overscroll-behavior-inline, padding-block-end, padding-block-start, padding-bottom, padding-inline-end, padding-inline-start, padding-left, padding-right, padding-top, paint-order, perspective, perspective-origin, pointer-events, position, r, resize, right, rotate, row-gap, ruby-position, rx, ry, scale, scroll-behavior, scroll-margin-block-end, scroll-margin-block-start, scroll-margin-inline-end, scroll-margin-inline-start, scroll-padding-block-end, scroll-padding-block-start, scroll-padding-inline-end, scroll-padding-inline-start, scrollbar-gutter, shape-image-threshold, shape-margin, shape-outside, shape-rendering, speak, stop-color, stop-opacity, stroke, stroke-dasharray, stroke-dashoffset, stroke-linecap, stroke-linejoin, stroke-miterlimit, stroke-opacity, stroke-width, tab-size, table-layout, text-align, text-align-last, text-anchor, text-decoration, text-decoration-color, text-decoration-line, text-decoration-skip-ink, text-decoration-style, text-emphasis-color, text-emphasis-position, text-emphasis-style, text-indent, text-overflow, text-rendering, text-shadow, text-size-adjust, text-transform, text-underline-position, text-wrap, top, touch-action, transform, transform-origin, transform-style, transition-delay, transition-duration, transition-property, transition-timing-function, translate, unicode-bidi, user-select, vector-effect, vertical-align, view-transition-name, visibility, white-space-collapse, widows, width, will-change, word-break, word-spacing, writing-mode, x, y, z-index, zoom, -webkit-border-horizontal-spacing, -webkit-border-image, -webkit-border-vertical-spacing, -webkit-box-align, -webkit-box-decoration-break, -webkit-box-direction, -webkit-box-flex, -webkit-box-ordinal-group, -webkit-box-orient, -webkit-box-pack, -webkit-box-reflect, -webkit-font-smoothing, -webkit-highlight, -webkit-line-break, -webkit-line-clamp, -webkit-locale, -webkit-mask-box-image, -webkit-mask-box-image-outset, -webkit-mask-box-image-repeat, -webkit-mask-box-image-slice, -webkit-mask-box-image-source, -webkit-mask-box-image-width, -webkit-mask-clip, -webkit-mask-composite, -webkit-mask-image, -webkit-mask-origin, -webkit-mask-position, -webkit-mask-repeat, -webkit-mask-size, -webkit-print-color-adjust, -webkit-rtl-ordering, -webkit-tap-highlight-color, -webkit-text-combine, -webkit-text-decorations-in-effect, -webkit-text-fill-color, -webkit-text-orientation, -webkit-text-security, -webkit-text-stroke-color, -webkit-text-stroke-width, -webkit-user-drag, -webkit-user-modify, -webkit-writing-mode, accentColor, additiveSymbols, alignContent, alignItems, alignSelf, alignmentBaseline, all, animation, animationComposition, animationDelay, animationDirection, animationDuration, animationFillMode, animationIterationCount, animationName, animationPlayState, animationTimingFunction, appRegion, ascentOverride, aspectRatio, backdropFilter, backfaceVisibility, background, backgroundAttachment, backgroundBlendMode, backgroundClip, backgroundColor, backgroundImage, backgroundOrigin, backgroundPosition, backgroundPositionX, backgroundPositionY, backgroundRepeat, backgroundRepeatX, backgroundRepeatY, backgroundSize, basePalette, baselineShift, baselineSource, blockSize, border, borderBlock, borderBlockColor, borderBlockEnd, borderBlockEndColor, borderBlockEndStyle, borderBlockEndWidth, borderBlockStart, borderBlockStartColor, borderBlockStartStyle, borderBlockStartWidth, borderBlockStyle, borderBlockWidth, borderBottom, borderBottomColor, borderBottomLeftRadius, borderBottomRightRadius, borderBottomStyle, borderBottomWidth, borderCollapse, borderColor, borderEndEndRadius, borderEndStartRadius, borderImage, borderImageOutset, borderImageRepeat, borderImageSlice, borderImageSource, borderImageWidth, borderInline, borderInlineColor, borderInlineEnd, borderInlineEndColor, borderInlineEndStyle, borderInlineEndWidth, borderInlineStart, borderInlineStartColor, borderInlineStartStyle, borderInlineStartWidth, borderInlineStyle, borderInlineWidth, borderLeft, borderLeftColor, borderLeftStyle, borderLeftWidth, borderRadius, borderRight, borderRightColor, borderRightStyle, borderRightWidth, borderSpacing, borderStartEndRadius, borderStartStartRadius, borderStyle, borderTop, borderTopColor, borderTopLeftRadius, borderTopRightRadius, borderTopStyle, borderTopWidth, borderWidth, boxShadow, boxSizing, breakAfter, breakBefore, breakInside, bufferedRendering, captionSide, caretColor, clipPath, clipRule, colorInterpolation, colorInterpolationFilters, colorRendering, colorScheme, columnCount, columnFill, columnGap, columnRule, columnRuleColor, columnRuleStyle, columnRuleWidth, columnSpan, columnWidth, columns, contain, containIntrinsicBlockSize, containIntrinsicHeight, containIntrinsicInlineSize, containIntrinsicSize, containIntrinsicWidth, container, containerName, containerType, contentVisibility, counterIncrement, counterReset, counterSet, descentOverride, dominantBaseline, emptyCells, fallback, fillOpacity, fillRule, flex, flexBasis, flexDirection, flexFlow, flexGrow, flexShrink, flexWrap, floodColor, floodOpacity, font, fontDisplay, fontFamily, fontFeatureSettings, fontKerning, fontOpticalSizing, fontPalette, fontSize, fontStretch, fontStyle, fontSynthesis, fontSynthesisSmallCaps, fontSynthesisStyle, fontSynthesisWeight, fontVariant, fontVariantAlternates, fontVariantCaps, fontVariantEastAsian, fontVariantLigatures, fontVariantNumeric, fontVariationSettings, fontWeight, forcedColorAdjust, gap, grid, gridArea, gridAutoColumns, gridAutoFlow, gridAutoRows, gridColumn, gridColumnEnd, gridColumnGap, gridColumnStart, gridGap, gridRow, gridRowEnd, gridRowGap, gridRowStart, gridTemplate, gridTemplateAreas, gridTemplateColumns, gridTemplateRows, hyphenateCharacter, hyphenateLimitChars, imageOrientation, imageRendering, inherits, initialLetter, initialValue, inlineSize, inset, insetBlock, insetBlockEnd, insetBlockStart, insetInline, insetInlineEnd, insetInlineStart, justifyContent, justifyItems, justifySelf, letterSpacing, lightingColor, lineBreak, lineGapOverride, lineHeight, listStyle, listStyleImage, listStylePosition, listStyleType, margin, marginBlock, marginBlockEnd, marginBlockStart, marginBottom, marginInline, marginInlineEnd, marginInlineStart, marginLeft, marginRight, marginTop, marker, markerEnd, markerMid, markerStart, mask, maskType, mathDepth, mathShift, mathStyle, maxBlockSize, maxHeight, maxInlineSize, maxWidth, minBlockSize, minHeight, minInlineSize, minWidth, mixBlendMode, negative, objectFit, objectPosition, objectViewBox, offset, offsetDistance, offsetPath, offsetRotate, outline, outlineColor, outlineOffset, outlineStyle, outlineWidth, overflow, overflowAnchor, overflowClipMargin, overflowWrap, overflowX, overflowY, overrideColors, overscrollBehavior, overscrollBehaviorBlock, overscrollBehaviorInline, overscrollBehaviorX, overscrollBehaviorY, pad, padding, paddingBlock, paddingBlockEnd, paddingBlockStart, paddingBottom, paddingInline, paddingInlineEnd, paddingInlineStart, paddingLeft, paddingRight, paddingTop, page, pageBreakAfter, pageBreakBefore, pageBreakInside, pageOrientation, paintOrder, perspectiveOrigin, placeContent, placeItems, placeSelf, pointerEvents, prefix, quotes, range, rowGap, rubyPosition, scrollBehavior, scrollMargin, scrollMarginBlock, scrollMarginBlockEnd, scrollMarginBlockStart, scrollMarginBottom, scrollMarginInline, scrollMarginInlineEnd, scrollMarginInlineStart, scrollMarginLeft, scrollMarginRight, scrollMarginTop, scrollPadding, scrollPaddingBlock, scrollPaddingBlockEnd, scrollPaddingBlockStart, scrollPaddingBottom, scrollPaddingInline, scrollPaddingInlineEnd, scrollPaddingInlineStart, scrollPaddingLeft, scrollPaddingRight, scrollPaddingTop, scrollSnapAlign, scrollSnapStop, scrollSnapType, scrollbarGutter, shapeImageThreshold, shapeMargin, shapeOutside, shapeRendering, size, sizeAdjust, speakAs, src, stopColor, stopOpacity, strokeDasharray, strokeDashoffset, strokeLinecap, strokeLinejoin, strokeMiterlimit, strokeOpacity, strokeWidth, suffix, symbols, syntax, system, tabSize, tableLayout, textAlign, textAlignLast, textAnchor, textCombineUpright, textDecoration, textDecorationColor, textDecorationLine, textDecorationSkipInk, textDecorationStyle, textDecorationThickness, textEmphasis, textEmphasisColor, textEmphasisPosition, textEmphasisStyle, textIndent, textOrientation, textOverflow, textRendering, textShadow, textSizeAdjust, textTransform, textUnderlineOffset, textUnderlinePosition, textWrap, touchAction, transformBox, transformOrigin, transformStyle, transition, transitionDelay, transitionDuration, transitionProperty, transitionTimingFunction, unicodeBidi, unicodeRange, userSelect, vectorEffect, verticalAlign, viewTransitionName, webkitAlignContent, webkitAlignItems, webkitAlignSelf, webkitAnimation, webkitAnimationDelay, webkitAnimationDirection, webkitAnimationDuration, webkitAnimationFillMode, webkitAnimationIterationCount, webkitAnimationName, webkitAnimationPlayState, webkitAnimationTimingFunction, webkitAppRegion, webkitAppearance, webkitBackfaceVisibility, webkitBackgroundClip, webkitBackgroundOrigin, webkitBackgroundSize, webkitBorderAfter, webkitBorderAfterColor, webkitBorderAfterStyle, webkitBorderAfterWidth, webkitBorderBefore, webkitBorderBeforeColor, webkitBorderBeforeStyle, webkitBorderBeforeWidth, webkitBorderBottomLeftRadius, webkitBorderBottomRightRadius, webkitBorderEnd, webkitBorderEndColor, webkitBorderEndStyle, webkitBorderEndWidth, webkitBorderHorizontalSpacing, webkitBorderImage, webkitBorderRadius, webkitBorderStart, webkitBorderStartColor, webkitBorderStartStyle, webkitBorderStartWidth, webkitBorderTopLeftRadius, webkitBorderTopRightRadius, webkitBorderVerticalSpacing, webkitBoxAlign, webkitBoxDecorationBreak, webkitBoxDirection, webkitBoxFlex, webkitBoxOrdinalGroup, webkitBoxOrient, webkitBoxPack, webkitBoxReflect, webkitBoxShadow, webkitBoxSizing, webkitClipPath, webkitColumnBreakAfter, webkitColumnBreakBefore, webkitColumnBreakInside, webkitColumnCount, webkitColumnGap, webkitColumnRule, webkitColumnRuleColor, webkitColumnRuleStyle, webkitColumnRuleWidth, webkitColumnSpan, webkitColumnWidth, webkitColumns, webkitFilter, webkitFlex, webkitFlexBasis, webkitFlexDirection, webkitFlexFlow, webkitFlexGrow, webkitFlexShrink, webkitFlexWrap, webkitFontFeatureSettings, webkitFontSmoothing, webkitHighlight, webkitHyphenateCharacter, webkitJustifyContent, webkitLineBreak, webkitLineClamp, webkitLocale, webkitLogicalHeight, webkitLogicalWidth, webkitMarginAfter, webkitMarginBefore, webkitMarginEnd, webkitMarginStart, webkitMask, webkitMaskBoxImage, webkitMaskBoxImageOutset, webkitMaskBoxImageRepeat, webkitMaskBoxImageSlice, webkitMaskBoxImageSource, webkitMaskBoxImageWidth, webkitMaskClip, webkitMaskComposite, webkitMaskImage, webkitMaskOrigin, webkitMaskPosition, webkitMaskPositionX, webkitMaskPositionY, webkitMaskRepeat, webkitMaskRepeatX, webkitMaskRepeatY, webkitMaskSize, webkitMaxLogicalHeight, webkitMaxLogicalWidth, webkitMinLogicalHeight, webkitMinLogicalWidth, webkitOpacity, webkitOrder, webkitPaddingAfter, webkitPaddingBefore, webkitPaddingEnd, webkitPaddingStart, webkitPerspective, webkitPerspectiveOrigin, webkitPerspectiveOriginX, webkitPerspectiveOriginY, webkitPrintColorAdjust, webkitRtlOrdering, webkitRubyPosition, webkitShapeImageThreshold, webkitShapeMargin, webkitShapeOutside, webkitTapHighlightColor, webkitTextCombine, webkitTextDecorationsInEffect, webkitTextEmphasis, webkitTextEmphasisColor, webkitTextEmphasisPosition, webkitTextEmphasisStyle, webkitTextFillColor, webkitTextOrientation, webkitTextSecurity, webkitTextSizeAdjust, webkitTextStroke, webkitTextStrokeColor, webkitTextStrokeWidth, webkitTransform, webkitTransformOrigin, webkitTransformOriginX, webkitTransformOriginY, webkitTransformOriginZ, webkitTransformStyle, webkitTransition, webkitTransitionDelay, webkitTransitionDuration, webkitTransitionProperty, webkitTransitionTimingFunction, webkitUserDrag, webkitUserModify, webkitUserSelect, webkitWritingMode, whiteSpace, whiteSpaceCollapse, willChange, wordBreak, wordSpacing, wordWrap, writingMode, zIndex, additive-symbols, ascent-override, aspect-ratio, background-position-x, background-position-y, background-repeat-x, background-repeat-y, base-palette, border-block, border-block-color, border-block-end, border-block-start, border-block-style, border-block-width, border-bottom, border-color, border-image, border-inline, border-inline-color, border-inline-end, border-inline-start, border-inline-style, border-inline-width, border-left, border-radius, border-right, border-spacing, border-style, border-top, border-width, color-scheme, column-fill, column-rule, content-visibility, counter-increment, counter-reset, counter-set, descent-override, flex-flow, font-display, font-feature-settings, font-synthesis, font-variation-settings, forced-color-adjust, grid-area, grid-column, grid-column-gap, grid-gap, grid-row, grid-row-gap, grid-template, initial-value, inset-block, inset-inline, line-gap-override, list-style, margin-block, margin-inline, override-colors, overscroll-behavior, overscroll-behavior-x, overscroll-behavior-y, padding-block, padding-inline, page-break-after, page-break-before, page-break-inside, page-orientation, place-content, place-items, place-self, scroll-margin, scroll-margin-block, scroll-margin-bottom, scroll-margin-inline, scroll-margin-left, scroll-margin-right, scroll-margin-top, scroll-padding, scroll-padding-block, scroll-padding-bottom, scroll-padding-inline, scroll-padding-left, scroll-padding-right, scroll-padding-top, scroll-snap-align, scroll-snap-stop, scroll-snap-type, size-adjust, speak-as, text-combine-upright, text-decoration-thickness, text-emphasis, text-orientation, text-underline-offset, transform-box, unicode-range, -webkit-align-content, -webkit-align-items, -webkit-align-self, -webkit-animation, -webkit-animation-delay, -webkit-animation-direction, -webkit-animation-duration, -webkit-animation-fill-mode, -webkit-animation-iteration-count, -webkit-animation-name, -webkit-animation-play-state, -webkit-animation-timing-function, -webkit-app-region, -webkit-appearance, -webkit-backface-visibility, -webkit-background-clip, -webkit-background-origin, -webkit-background-size, -webkit-border-after, -webkit-border-after-color, -webkit-border-after-style, -webkit-border-after-width, -webkit-border-before, -webkit-border-before-color, -webkit-border-before-style, -webkit-border-before-width, -webkit-border-bottom-left-radius, -webkit-border-bottom-right-radius, -webkit-border-end, -webkit-border-end-color, -webkit-border-end-style, -webkit-border-end-width, -webkit-border-radius, -webkit-border-start, -webkit-border-start-color, -webkit-border-start-style, -webkit-border-start-width, -webkit-border-top-left-radius, -webkit-border-top-right-radius, -webkit-box-shadow, -webkit-box-sizing, -webkit-clip-path, -webkit-column-break-after, -webkit-column-break-before, -webkit-column-break-inside, -webkit-column-count, -webkit-column-gap, -webkit-column-rule, -webkit-column-rule-color, -webkit-column-rule-style, -webkit-column-rule-width, -webkit-column-span, -webkit-column-width, -webkit-columns, -webkit-filter, -webkit-flex, -webkit-flex-basis, -webkit-flex-direction, -webkit-flex-flow, -webkit-flex-grow, -webkit-flex-shrink, -webkit-flex-wrap, -webkit-font-feature-settings, -webkit-hyphenate-character, -webkit-justify-content, -webkit-logical-height, -webkit-logical-width, -webkit-margin-after, -webkit-margin-before, -webkit-margin-end, -webkit-margin-start, -webkit-mask, -webkit-mask-position-x, -webkit-mask-position-y, -webkit-mask-repeat-x, -webkit-mask-repeat-y, -webkit-max-logical-height, -webkit-max-logical-width, -webkit-min-logical-height, -webkit-min-logical-width, -webkit-opacity, -webkit-order, -webkit-padding-after, -webkit-padding-before, -webkit-padding-end, -webkit-padding-start, -webkit-perspective, -webkit-perspective-origin, -webkit-perspective-origin-x, -webkit-perspective-origin-y, -webkit-ruby-position, -webkit-shape-image-threshold, -webkit-shape-margin, -webkit-shape-outside, -webkit-text-emphasis, -webkit-text-emphasis-color, -webkit-text-emphasis-position, -webkit-text-emphasis-style, -webkit-text-size-adjust, -webkit-text-stroke, -webkit-transform, -webkit-transform-origin, -webkit-transform-origin-x, -webkit-transform-origin-y, -webkit-transform-origin-z, -webkit-transform-style, -webkit-transition, -webkit-transition-delay, -webkit-transition-duration, -webkit-transition-property, -webkit-transition-timing-function, -webkit-user-select, white-space, word-wrap`, jsKeys: 'Object.assign, Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.hasOwn, Object.is, Object.preventExtensions, Object.seal, Object.create, Object.defineProperties, Object.defineProperty, Object.freeze, Object.getPrototypeOf, Object.setPrototypeOf, Object.isExtensible, Object.isFrozen, Object.isSealed, Object.keys, Object.entries, Object.fromEntries, Object.values, Object.__defineGetter__, Object.__defineSetter__, Object.hasOwnProperty, Object.__lookupGetter__, Object.__lookupSetter__, Object.isPrototypeOf, Object.propertyIsEnumerable, Object.toString, Object.valueOf, Object.__proto__, Object.toLocaleString, Function.apply, Function.bind, Function.call, Function.toString, Boolean.toString, Boolean.valueOf, Symbol.for, Symbol.keyFor, Symbol.asyncIterator, Symbol.hasInstance, Symbol.isConcatSpreadable, Symbol.iterator, Symbol.match, Symbol.matchAll, Symbol.replace, Symbol.search, Symbol.species, Symbol.split, Symbol.toPrimitive, Symbol.toStringTag, Symbol.unscopables, Symbol.toString, Symbol.valueOf, Symbol.description, Error.captureStackTrace, Error.stackTraceLimit, Error.message, Error.toString, Number.isFinite, Number.isInteger, Number.isNaN, Number.isSafeInteger, Number.parseFloat, Number.parseInt, Number.MAX_VALUE, Number.MIN_VALUE, Number.NaN, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.EPSILON, Number.toExponential, Number.toFixed, Number.toPrecision, Number.toString, Number.valueOf, Number.toLocaleString, BigInt.asUintN, BigInt.asIntN, BigInt.toLocaleString, BigInt.toString, BigInt.valueOf, Math.abs, Math.acos, Math.acosh, Math.asin, Math.asinh, Math.atan, Math.atanh, Math.atan2, Math.ceil, Math.cbrt, Math.expm1, Math.clz32, Math.cos, Math.cosh, Math.exp, Math.floor, Math.fround, Math.hypot, Math.imul, Math.log, Math.log1p, Math.log2, Math.log10, Math.max, Math.min, Math.pow, Math.random, Math.round, Math.sign, Math.sin, Math.sinh, Math.sqrt, Math.tan, Math.tanh, Math.trunc, Math.E, Math.LN10, Math.LN2, Math.LOG10E, Math.LOG2E, Math.PI, Math.SQRT1_2, Math.SQRT2, Date.now, Date.parse, Date.UTC, Date.toString, Date.toDateString, Date.toTimeString, Date.toISOString, Date.toUTCString, Date.toGMTString, Date.getDate, Date.setDate, Date.getDay, Date.getFullYear, Date.setFullYear, Date.getHours, Date.setHours, Date.getMilliseconds, Date.setMilliseconds, Date.getMinutes, Date.setMinutes, Date.getMonth, Date.setMonth, Date.getSeconds, Date.setSeconds, Date.getTime, Date.setTime, Date.getTimezoneOffset, Date.getUTCDate, Date.setUTCDate, Date.getUTCDay, Date.getUTCFullYear, Date.setUTCFullYear, Date.getUTCHours, Date.setUTCHours, Date.getUTCMilliseconds, Date.setUTCMilliseconds, Date.getUTCMinutes, Date.setUTCMinutes, Date.getUTCMonth, Date.setUTCMonth, Date.getUTCSeconds, Date.setUTCSeconds, Date.valueOf, Date.getYear, Date.setYear, Date.toJSON, Date.toLocaleString, Date.toLocaleDateString, Date.toLocaleTimeString, String.fromCharCode, String.fromCodePoint, String.raw, String.anchor, String.at, String.big, String.blink, String.bold, String.charAt, String.charCodeAt, String.codePointAt, String.concat, String.endsWith, String.fontcolor, String.fontsize, String.fixed, String.includes, String.indexOf, String.italics, String.lastIndexOf, String.link, String.localeCompare, String.match, String.matchAll, String.normalize, String.padEnd, String.padStart, String.repeat, String.replace, String.replaceAll, String.search, String.slice, String.small, String.split, String.strike, String.sub, String.substr, String.substring, String.sup, String.startsWith, String.toString, String.trim, String.trimStart, String.trimLeft, String.trimEnd, String.trimRight, String.toLocaleLowerCase, String.toLocaleUpperCase, String.toLowerCase, String.toUpperCase, String.valueOf, String.isWellFormed, String.toWellFormed, RegExp.input, RegExp.$_, RegExp.lastMatch, RegExp.$&, RegExp.lastParen, RegExp.$+, RegExp.leftContext, RegExp.$`, RegExp.rightContext, RegExp.$\', RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6, RegExp.$7, RegExp.$8, RegExp.$9, RegExp.exec, RegExp.dotAll, RegExp.flags, RegExp.global, RegExp.hasIndices, RegExp.ignoreCase, RegExp.multiline, RegExp.source, RegExp.sticky, RegExp.unicode, RegExp.compile, RegExp.toString, RegExp.test, RegExp.unicodeSets, Array.isArray, Array.from, Array.of, Array.at, Array.concat, Array.copyWithin, Array.fill, Array.find, Array.findIndex, Array.findLast, Array.findLastIndex, Array.lastIndexOf, Array.pop, Array.push, Array.reverse, Array.shift, Array.unshift, Array.slice, Array.sort, Array.splice, Array.includes, Array.indexOf, Array.join, Array.keys, Array.entries, Array.values, Array.forEach, Array.filter, Array.flat, Array.flatMap, Array.map, Array.every, Array.some, Array.reduce, Array.reduceRight, Array.toLocaleString, Array.toString, Array.toReversed, Array.toSorted, Array.toSpliced, Array.with, Map.get, Map.set, Map.has, Map.delete, Map.clear, Map.entries, Map.forEach, Map.keys, Map.size, Map.values, Set.has, Set.add, Set.delete, Set.clear, Set.entries, Set.forEach, Set.size, Set.values, Set.keys, WeakMap.delete, WeakMap.get, WeakMap.set, WeakMap.has, WeakSet.delete, WeakSet.has, WeakSet.add, Atomics.load, Atomics.store, Atomics.add, Atomics.sub, Atomics.and, Atomics.or, Atomics.xor, Atomics.exchange, Atomics.compareExchange, Atomics.isLockFree, Atomics.wait, Atomics.waitAsync, Atomics.notify, JSON.parse, JSON.stringify, JSON.rawJSON, JSON.isRawJSON, Promise.all, Promise.allSettled, Promise.any, Promise.race, Promise.resolve, Promise.reject, Promise.then, Promise.catch, Promise.finally, Reflect.defineProperty, Reflect.deleteProperty, Reflect.apply, Reflect.construct, Reflect.get, Reflect.getOwnPropertyDescriptor, Reflect.getPrototypeOf, Reflect.has, Reflect.isExtensible, Reflect.ownKeys, Reflect.preventExtensions, Reflect.set, Reflect.setPrototypeOf, Proxy.revocable, Intl.getCanonicalLocales, Intl.supportedValuesOf, Intl.DateTimeFormat, Intl.NumberFormat, Intl.Collator, Intl.v8BreakIterator, Intl.PluralRules, Intl.RelativeTimeFormat, Intl.ListFormat, Intl.Locale, Intl.DisplayNames, Intl.Segmenter, WebAssembly.compile, WebAssembly.validate, WebAssembly.instantiate, WebAssembly.compileStreaming, WebAssembly.instantiateStreaming, WebAssembly.Module, WebAssembly.Instance, WebAssembly.Table, WebAssembly.Memory, WebAssembly.Global, WebAssembly.Tag, WebAssembly.Exception, WebAssembly.CompileError, WebAssembly.LinkError, WebAssembly.RuntimeError, Document.implementation, Document.URL, Document.documentURI, Document.compatMode, Document.characterSet, Document.charset, Document.inputEncoding, Document.contentType, Document.doctype, Document.documentElement, Document.xmlEncoding, Document.xmlVersion, Document.xmlStandalone, Document.domain, Document.referrer, Document.cookie, Document.lastModified, Document.readyState, Document.title, Document.dir, Document.body, Document.head, Document.images, Document.embeds, Document.plugins, Document.links, Document.forms, Document.scripts, Document.currentScript, Document.defaultView, Document.designMode, Document.onreadystatechange, Document.anchors, Document.applets, Document.fgColor, Document.linkColor, Document.vlinkColor, Document.alinkColor, Document.bgColor, Document.all, Document.scrollingElement, Document.onpointerlockchange, Document.onpointerlockerror, Document.hidden, Document.visibilityState, Document.wasDiscarded, Document.prerendering, Document.featurePolicy, Document.webkitVisibilityState, Document.webkitHidden, Document.onbeforecopy, Document.onbeforecut, Document.onbeforepaste, Document.onfreeze, Document.onprerenderingchange, Document.onresume, Document.onsearch, Document.onvisibilitychange, Document.fullscreenEnabled, Document.fullscreen, Document.onfullscreenchange, Document.onfullscreenerror, Document.webkitIsFullScreen, Document.webkitCurrentFullScreenElement, Document.webkitFullscreenEnabled, Document.webkitFullscreenElement, Document.onwebkitfullscreenchange, Document.onwebkitfullscreenerror, Document.rootElement, Document.pictureInPictureEnabled, Document.pictureInPictureElement, Document.onbeforexrselect, Document.onabort, Document.onbeforeinput, Document.onblur, Document.oncancel, Document.oncanplay, Document.oncanplaythrough, Document.onchange, Document.onclick, Document.onclose, Document.oncontextlost, Document.oncontextmenu, Document.oncontextrestored, Document.oncuechange, Document.ondblclick, Document.ondrag, Document.ondragend, Document.ondragenter, Document.ondragleave, Document.ondragover, Document.ondragstart, Document.ondrop, Document.ondurationchange, Document.onemptied, Document.onended, Document.onerror, Document.onfocus, Document.onformdata, Document.oninput, Document.oninvalid, Document.onkeydown, Document.onkeypress, Document.onkeyup, Document.onload, Document.onloadeddata, Document.onloadedmetadata, Document.onloadstart, Document.onmousedown, Document.onmouseenter, Document.onmouseleave, Document.onmousemove, Document.onmouseout, Document.onmouseover, Document.onmouseup, Document.onmousewheel, Document.onpause, Document.onplay, Document.onplaying, Document.onprogress, Document.onratechange, Document.onreset, Document.onresize, Document.onscroll, Document.onsecuritypolicyviolation, Document.onseeked, Document.onseeking, Document.onselect, Document.onslotchange, Document.onstalled, Document.onsubmit, Document.onsuspend, Document.ontimeupdate, Document.ontoggle, Document.onvolumechange, Document.onwaiting, Document.onwebkitanimationend, Document.onwebkitanimationiteration, Document.onwebkitanimationstart, Document.onwebkittransitionend, Document.onwheel, Document.onauxclick, Document.ongotpointercapture, Document.onlostpointercapture, Document.onpointerdown, Document.onpointermove, Document.onpointerrawupdate, Document.onpointerup, Document.onpointercancel, Document.onpointerover, Document.onpointerout, Document.onpointerenter, Document.onpointerleave, Document.onselectstart, Document.onselectionchange, Document.onanimationend, Document.onanimationiteration, Document.onanimationstart, Document.ontransitionrun, Document.ontransitionstart, Document.ontransitionend, Document.ontransitioncancel, Document.oncopy, Document.oncut, Document.onpaste, Document.children, Document.firstElementChild, Document.lastElementChild, Document.childElementCount, Document.activeElement, Document.styleSheets, Document.pointerLockElement, Document.fullscreenElement, Document.adoptedStyleSheets, Document.fonts, Document.adoptNode, Document.append, Document.captureEvents, Document.caretRangeFromPoint, Document.clear, Document.close, Document.createAttribute, Document.createAttributeNS, Document.createCDATASection, Document.createComment, Document.createDocumentFragment, Document.createElement, Document.createElementNS, Document.createEvent, Document.createExpression, Document.createNSResolver, Document.createNodeIterator, Document.createProcessingInstruction, Document.createRange, Document.createTextNode, Document.createTreeWalker, Document.elementFromPoint, Document.elementsFromPoint, Document.evaluate, Document.execCommand, Document.exitFullscreen, Document.exitPictureInPicture, Document.exitPointerLock, Document.getElementById, Document.getElementsByClassName, Document.getElementsByName, Document.getElementsByTagName, Document.getElementsByTagNameNS, Document.getSelection, Document.hasFocus, Document.importNode, Document.open, Document.prepend, Document.queryCommandEnabled, Document.queryCommandIndeterm, Document.queryCommandState, Document.queryCommandSupported, Document.queryCommandValue, Document.querySelector, Document.querySelectorAll, Document.releaseEvents, Document.replaceChildren, Document.webkitCancelFullScreen, Document.webkitExitFullscreen, Document.write, Document.writeln, Document.fragmentDirective, Document.onbeforematch, Document.onbeforetoggle, Document.timeline, Document.oncontentvisibilityautostatechange, Document.onscrollend, Document.getAnimations, Document.startViewTransition, Element.namespaceURI, Element.prefix, Element.localName, Element.tagName, Element.id, Element.className, Element.classList, Element.slot, Element.attributes, Element.shadowRoot, Element.part, Element.assignedSlot, Element.innerHTML, Element.outerHTML, Element.scrollTop, Element.scrollLeft, Element.scrollWidth, Element.scrollHeight, Element.clientTop, Element.clientLeft, Element.clientWidth, Element.clientHeight, Element.onbeforecopy, Element.onbeforecut, Element.onbeforepaste, Element.onsearch, Element.elementTiming, Element.onfullscreenchange, Element.onfullscreenerror, Element.onwebkitfullscreenchange, Element.onwebkitfullscreenerror, Element.role, Element.ariaAtomic, Element.ariaAutoComplete, Element.ariaBusy, Element.ariaBrailleLabel, Element.ariaBrailleRoleDescription, Element.ariaChecked, Element.ariaColCount, Element.ariaColIndex, Element.ariaColSpan, Element.ariaCurrent, Element.ariaDescription, Element.ariaDisabled, Element.ariaExpanded, Element.ariaHasPopup, Element.ariaHidden, Element.ariaInvalid, Element.ariaKeyShortcuts, Element.ariaLabel, Element.ariaLevel, Element.ariaLive, Element.ariaModal, Element.ariaMultiLine, Element.ariaMultiSelectable, Element.ariaOrientation, Element.ariaPlaceholder, Element.ariaPosInSet, Element.ariaPressed, Element.ariaReadOnly, Element.ariaRelevant, Element.ariaRequired, Element.ariaRoleDescription, Element.ariaRowCount, Element.ariaRowIndex, Element.ariaRowSpan, Element.ariaSelected, Element.ariaSetSize, Element.ariaSort, Element.ariaValueMax, Element.ariaValueMin, Element.ariaValueNow, Element.ariaValueText, Element.children, Element.firstElementChild, Element.lastElementChild, Element.childElementCount, Element.previousElementSibling, Element.nextElementSibling, Element.after, Element.animate, Element.append, Element.attachShadow, Element.before, Element.closest, Element.computedStyleMap, Element.getAttribute, Element.getAttributeNS, Element.getAttributeNames, Element.getAttributeNode, Element.getAttributeNodeNS, Element.getBoundingClientRect, Element.getClientRects, Element.getElementsByClassName, Element.getElementsByTagName, Element.getElementsByTagNameNS, Element.getInnerHTML, Element.hasAttribute, Element.hasAttributeNS, Element.hasAttributes, Element.hasPointerCapture, Element.insertAdjacentElement, Element.insertAdjacentHTML, Element.insertAdjacentText, Element.matches, Element.prepend, Element.querySelector, Element.querySelectorAll, Element.releasePointerCapture, Element.remove, Element.removeAttribute, Element.removeAttributeNS, Element.removeAttributeNode, Element.replaceChildren, Element.replaceWith, Element.requestFullscreen, Element.requestPointerLock, Element.scroll, Element.scrollBy, Element.scrollIntoView, Element.scrollIntoViewIfNeeded, Element.scrollTo, Element.setAttribute, Element.setAttributeNS, Element.setAttributeNode, Element.setAttributeNodeNS, Element.setPointerCapture, Element.toggleAttribute, Element.webkitMatchesSelector, Element.webkitRequestFullScreen, Element.webkitRequestFullscreen, Element.checkVisibility, Element.getAnimations, Element.setHTML', }, 'Firefox': { version: 112, windowKeys: `undefined, globalThis, Array, Boolean, JSON, Date, Math, Number, String, RegExp, Error, InternalError, AggregateError, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError, ArrayBuffer, Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array, Uint8ClampedArray, BigInt64Array, BigUint64Array, BigInt, Proxy, WeakMap, Set, DataView, Symbol, Intl, Reflect, WeakSet, Atomics, WebAssembly, FinalizationRegistry, WeakRef, NaN, Infinity, isNaN, isFinite, parseFloat, parseInt, escape, unescape, decodeURI, encodeURI, decodeURIComponent, encodeURIComponent, CryptoKey, FocusEvent, CSSRuleList, MediaStreamTrackAudioSourceNode, SVGGeometryElement, SVGElement, SVGPatternElement, WebSocket, HTMLAllCollection, UIEvent, PageTransitionEvent, AuthenticatorAssertionResponse, ScreenOrientation, MediaCapabilitiesInfo, SVGImageElement, NodeIterator, SVGFEOffsetElement, AnimationPlaybackEvent, MessageChannel, TextDecoderStream, ShadowRoot, SVGAnimatedNumberList, SVGEllipseElement, DOMStringMap, AudioWorkletNode, TextMetrics, SVGPointList, SVGSymbolElement, DocumentType, StorageEvent, SVGAnimatedLength, PerformanceObserver, WebGLSampler, PushSubscription, CustomElementRegistry, SVGUnitTypes, SVGFEMorphologyElement, NodeFilter, File, Geolocation, ProcessingInstruction, AudioScheduledSourceNode, FileReader, IDBObjectStore, SVGStringList, HTMLParagraphElement, IDBDatabase, SVGLinearGradientElement, Animation, HTMLIFrameElement, HTMLTableSectionElement, Worker, TimeRanges, Navigator, InputEvent, GamepadHapticActuator, SVGMatrix, Worklet, SVGFEMergeNodeElement, DOMTokenList, MediaQueryListEvent, HTMLParamElement, MessagePort, SVGLengthList, ResizeObserverSize, TextEncoderStream, PromiseRejectionEvent, SVGTransformList, DOMPoint, SVGTextElement, WebGL2RenderingContext, PluginArray, IDBVersionChangeEvent, FontFaceSetLoadEvent, CSSStyleDeclaration, SVGGraphicsElement, HTMLOListElement, HTMLTextAreaElement, Storage, XPathEvaluator, MouseScrollEvent, HTMLCanvasElement, HTMLBodyElement, HTMLCollection, HTMLHtmlElement, MediaList, HTMLAudioElement, IDBFactory, SVGAnimatedTransformList, MimeType, SVGAnimatedPreserveAspectRatio, HTMLFrameElement, HTMLLegendElement, HTMLMapElement, SVGAnimateMotionElement, HTMLFrameSetElement, CSSGroupingRule, Clipboard, IIRFilterNode, ReadableStreamDefaultController, FileSystemFileHandle, HTMLElement, CSSConditionRule, CSS, SVGFEComponentTransferElement, DOMRectList, AudioWorklet, SourceBuffer, HTMLStyleElement, DocumentTimeline, IDBKeyRange, DOMRequest, PerformanceTiming, GeolocationPosition, SVGTextPositioningElement, OfflineResourceList, IDBOpenDBRequest, SVGFEFuncGElement, MouseEvent, FontFaceSet, OffscreenCanvasRenderingContext2D, OscillatorNode, SpeechSynthesisUtterance, AudioContext, FileSystemEntry, PaintRequest, SVGFEConvolveMatrixElement, ChannelSplitterNode, AudioBufferSourceNode, CaretPosition, AbortSignal, HTMLBRElement, XSLTProcessor, SVGFESpecularLightingElement, mozRTCPeerConnection, XMLSerializer, StorageManager, HTMLImageElement, WebGLQuery, FileSystemHandle, Permissions, BaseAudioContext, MediaStream, History, DOMStringList, StereoPannerNode, ReadableStreamDefaultReader, HTMLDListElement, MutationEvent, SVGComponentTransferFunctionElement, Notification, DynamicsCompressorNode, LockManager, Option, HTMLMeterElement, RTCPeerConnection, CloseEvent, AudioBuffer, ByteLengthQueuingStrategy, SVGFECompositeElement, HTMLTrackElement, ServiceWorkerContainer, MediaStreamTrack, WebGLContextEvent, WritableStreamDefaultController, SVGPathElement, BarProp, PerformanceObserverEntryList, RTCRtpReceiver, StyleSheet, WebGLUniformLocation, HTMLMetaElement, CanvasPattern, OffscreenCanvas, SVGTransform, SVGTextContentElement, PerformanceServerTiming, TrackEvent, XPathExpression, AnimationTimeline, MediaError, HTMLAnchorElement, XMLHttpRequest, SecurityPolicyViolationEvent, CSSMozDocumentRule, CSSImportRule, SVGLength, WaveShaperNode, RTCTrackEvent, RTCRtpSender, CSSAnimation, CSSFontPaletteValuesRule, AnimationEffect, CSSContainerRule, RTCStatsReport, SourceBufferList, HTMLHeadingElement, CanvasCaptureMediaStream, HTMLOptionElement, SVGSVGElement, WebGLActiveInfo, MIDIPort, HTMLUListElement, XPathResult, SVGUseElement, Credential, PublicKeyCredential, DOMQuad, Selection, HTMLDataElement, CSSLayerStatementRule, WebGLShader, Location, MIDIAccess, MediaRecorderErrorEvent, SVGFEGaussianBlurElement, MediaStreamEvent, CSSFontFeatureValuesRule, AbortController, RTCIceCandidate, HTMLLabelElement, PerformanceMeasure, HTMLDirectoryElement, SVGStopElement, PermissionStatus, PerformancePaintTiming, FileSystemFileEntry, SVGMarkerElement, console, SharedWorker, WebGLVertexArrayObject, HTMLOptionsCollection, HTMLTitleElement, TreeWalker, CompositionEvent, IDBCursor, TransformStream, PerformanceNavigation, Blob, SpeechSynthesisErrorEvent, CSSStyleSheet, HTMLUnknownElement, KeyEvent, HTMLOptGroupElement, CanvasGradient, AnalyserNode, Element, AnimationEvent, HTMLFieldSetElement, MediaSession, MutationObserver, SVGAnimateTransformElement, OfflineAudioContext, CacheStorage, MediaKeyStatusMap, GamepadEvent, RTCPeerConnectionIceEvent, AudioParam, AbstractRange, TextEncoder, FileSystemDirectoryHandle, CSSSupportsRule, SVGPreserveAspectRatio, PerformanceEntry, mozRTCSessionDescription, VideoPlaybackQuality, HTMLTableRowElement, HTMLFontElement, MediaKeys, DataTransfer, CSSPageRule, SVGAngle, WebGLFramebuffer, WebGLRenderbuffer, Directory, HTMLAreaElement, MimeTypeArray, NamedNodeMap, CSSKeyframeRule, XMLDocument, HTMLSlotElement, MediaDevices, IDBTransaction, HTMLModElement, MediaKeyError, SVGFEFloodElement, DOMParser, HTMLScriptElement, ReadableStreamBYOBReader, HTMLDataListElement, MediaElementAudioSourceNode, PeriodicWave, DragEvent, SVGStyleElement, SubtleCrypto, TransformStreamDefaultController, MessageEvent, WebGLProgram, SVGSetElement, WebGLShaderPrecisionFormat, ErrorEvent, NodeList, GamepadButton, MediaDeviceInfo, HTMLMediaElement, DeviceMotionEvent, ImageData, Range, PushSubscriptionOptions, OfflineAudioCompletionEvent, DOMPointReadOnly, DocumentFragment, Attr, BroadcastChannel, HTMLLinkElement, DOMMatrixReadOnly, AuthenticatorAttestationResponse, IdleDeadline, ImageBitmapRenderingContext, MediaKeySystemAccess, SVGPoint, SVGFEDropShadowElement, SVGFEDiffuseLightingElement, SVGRadialGradientElement, RTCDataChannelEvent, Headers, FileSystemDirectoryReader, SVGFEDisplacementMapElement, SVGDefsElement, SVGFEDistantLightElement, HTMLFormControlsCollection, WebGLRenderingContext, HTMLTableElement, SVGNumber, SVGAnimatedInteger, VisualViewport, SpeechSynthesisVoice, WheelEvent, SVGAnimatedNumber, GamepadPose, ResizeObserver, TextDecoder, FormDataEvent, FileSystem, IntersectionObserverEntry, SVGPolylineElement, Text, CanvasRenderingContext2D, CSSFontFaceRule, SVGGradientElement, WritableStream, SVGNumberList, SpeechSynthesisEvent, WritableStreamDefaultWriter, SVGFilterElement, URL, SVGRect, SVGFEImageElement, AudioDestinationNode, IDBRequest, MathMLElement, HTMLTimeElement, TextTrackCue, RadioNodeList, SVGTSpanElement, SVGAnimatedLengthList, SVGAnimatedString, HTMLSourceElement, Lock, ProgressEvent, PopupBlockedEvent, ValidityState, WebKitCSSMatrix, AudioListener, HTMLFormElement, PerformanceEventTiming, HTMLProgressElement, Gamepad, MediaKeySession, MediaStreamAudioSourceNode, CSSRule, HTMLPreElement, webkitURL, AuthenticatorResponse, HTMLEmbedElement, HTMLDivElement, MediaStreamAudioDestinationNode, CredentialsContainer, SVGScriptElement, MutationRecord, ConvolverNode, SVGFEPointLightElement, Screen, ClipboardEvent, SVGFETurbulenceElement, SVGFEBlendElement, GeolocationCoordinates, TextTrackList, FontFace, HTMLInputElement, Request, SVGGElement, SVGClipPathElement, TimeEvent, TextTrackCueList, BiquadFilterNode, SVGTitleElement, SVGFETileElement, SVGFEFuncRElement, HTMLButtonElement, Path2D, HTMLTableCellElement, StaticRange, SVGLineElement, CSSCounterStyleRule, HTMLQuoteElement, AudioParamMap, DeviceOrientationEvent, IDBCursorWithValue, MediaKeyMessageEvent, SVGAnimatedEnumeration, MIDIOutput, HTMLHRElement, ImageBitmap, CSSStyleRule, MIDIOutputMap, SVGAElement, ElementInternals, VTTCue, MediaMetadata, PannerNode, SVGForeignObjectElement, SVGSwitchElement, HTMLVideoElement, MediaEncryptedEvent, EventSource, SVGAnimateElement, DOMException, CSSTransition, Image, VTTRegion, CharacterData, SVGFEFuncAElement, SVGDescElement, SubmitEvent, RTCSessionDescription, SVGAnimatedBoolean, StyleSheetList, HTMLTableColElement, HTMLMarqueeElement, CSSKeyframesRule, SVGMetadataElement, MIDIInputMap, PushManager, GainNode, DOMRect, SVGMaskElement, HTMLMenuElement, RTCDTMFToneChangeEvent, IDBIndex, CSSMediaRule, HashChangeEvent, ServiceWorker, SVGFEFuncBElement, BlobEvent, DOMImplementation, GeolocationPositionError, SVGMPathElement, SVGFEColorMatrixElement, KeyboardEvent, HTMLLIElement, RTCCertificate, SpeechSynthesis, ReadableStreamBYOBRequest, DelayNode, XMLHttpRequestEventTarget, PopStateEvent, Cache, ScrollAreaEvent, RTCDtlsTransport, SVGTextPathElement, XMLHttpRequestUpload, HTMLOutputElement, MediaRecorder, PaintRequestList, SVGRectElement, AudioNode, DOMMatrix, MediaSource, FormData, NavigationPreloadManager, HTMLTableCaptionElement, CustomEvent, MediaCapabilities, SVGFEMergeElement, MediaStreamTrackEvent, Audio, CSS2Properties, FileList, SVGAnimatedRect, FileSystemWritableFileStream, ReadableStream, HTMLPictureElement, HTMLSelectElement, AudioProcessingEvent, PerformanceResourceTiming, Plugin, Crypto, CSSLayerBlockRule, ConstantSourceNode, HTMLSpanElement, DataTransferItem, ServiceWorkerRegistration, WebGLTransformFeedback, SVGViewElement, CSSNamespaceRule, URLSearchParams, WebGLBuffer, MediaQueryList, PointerEvent, SVGPolygonElement, KeyframeEffect, RTCDTMFSender, ResizeObserverEntry, SVGCircleElement, SVGAnimationElement, WebGLTexture, DOMRectReadOnly, WebGLSync, IntersectionObserver, MIDIMessageEvent, ReadableByteStreamController, HTMLBaseElement, CountQueuingStrategy, mozRTCIceCandidate, DataTransferItemList, HTMLHeadElement, CDATASection, HTMLDialogElement, HTMLTemplateElement, RTCDataChannel, PerformanceMark, SVGFESpotLightElement, BeforeUnloadEvent, MIDIConnectionEvent, RTCRtpTransceiver, TextTrack, TransitionEvent, HTMLDetailsElement, Comment, HTMLObjectElement, ChannelMergerNode, SVGAnimatedAngle, Response, FileSystemDirectoryEntry, MIDIInput, ScriptProcessorNode, Function, Object, eval, EventTarget, Window, close, stop, focus, blur, open, alert, confirm, prompt, print, postMessage, captureEvents, releaseEvents, getSelection, getComputedStyle, matchMedia, moveTo, moveBy, resizeTo, resizeBy, scroll, scrollTo, scrollBy, getDefaultComputedStyle, scrollByLines, scrollByPages, sizeToContent, updateCommands, find, dump, setResizable, requestIdleCallback, cancelIdleCallback, requestAnimationFrame, cancelAnimationFrame, reportError, btoa, atob, setTimeout, clearTimeout, setInterval, clearInterval, queueMicrotask, createImageBitmap, structuredClone, fetch, self, name, history, customElements, locationbar, menubar, personalbar, scrollbars, statusbar, toolbar, status, closed, event, frames, length, opener, parent, frameElement, navigator, clientInformation, external, applicationCache, screen, innerWidth, innerHeight, scrollX, pageXOffset, scrollY, pageYOffset, screenLeft, screenTop, screenX, screenY, outerWidth, outerHeight, performance, mozInnerScreenX, mozInnerScreenY, devicePixelRatio, scrollMaxX, scrollMaxY, fullScreen, ondevicemotion, ondeviceorientation, ondeviceorientationabsolute, InstallTrigger, visualViewport, crypto, onabort, onblur, onfocus, onauxclick, onbeforeinput, oncanplay, oncanplaythrough, onchange, onclick, onclose, oncontextmenu, oncopy, oncuechange, oncut, ondblclick, ondrag, ondragend, ondragenter, ondragexit, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, onformdata, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onload, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmouseenter, onmouseleave, onmousemove, onmouseout, onmouseover, onmouseup, onwheel, onpaste, onpause, onplay, onplaying, onprogress, onratechange, onreset, onresize, onscroll, onscrollend, onsecuritypolicyviolation, onseeked, onseeking, onselect, onslotchange, onstalled, onsubmit, onsuspend, ontimeupdate, onvolumechange, onwaiting, onselectstart, onselectionchange, ontoggle, onpointercancel, onpointerdown, onpointerup, onpointermove, onpointerout, onpointerover, onpointerenter, onpointerleave, ongotpointercapture, onlostpointercapture, onmozfullscreenchange, onmozfullscreenerror, onanimationcancel, onanimationend, onanimationiteration, onanimationstart, ontransitioncancel, ontransitionend, ontransitionrun, ontransitionstart, onwebkitanimationend, onwebkitanimationiteration, onwebkitanimationstart, onwebkittransitionend, onerror, speechSynthesis, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onlanguagechange, onmessage, onmessageerror, onoffline, ononline, onpagehide, onpageshow, onpopstate, onrejectionhandled, onstorage, onunhandledrejection, onunload, ongamepadconnected, ongamepaddisconnected, localStorage, origin, crossOriginIsolated, isSecureContext, indexedDB, caches, sessionStorage, window, document, location, top, netscape, Node, Document, HTMLDocument, EventCounts, Map, Promise, Event`, cssKeys: `alignContent, align-content, alignItems, align-items, alignSelf, align-self, aspectRatio, aspect-ratio, backfaceVisibility, backface-visibility, borderCollapse, border-collapse, borderImageRepeat, border-image-repeat, boxDecorationBreak, box-decoration-break, boxSizing, box-sizing, breakInside, break-inside, captionSide, caption-side, clear, colorInterpolation, color-interpolation, colorInterpolationFilters, color-interpolation-filters, columnCount, column-count, columnFill, column-fill, columnSpan, column-span, contain, containerType, container-type, direction, display, dominantBaseline, dominant-baseline, emptyCells, empty-cells, flexDirection, flex-direction, flexWrap, flex-wrap, cssFloat, float, fontKerning, font-kerning, fontLanguageOverride, font-language-override, fontOpticalSizing, font-optical-sizing, fontSizeAdjust, font-size-adjust, fontStretch, font-stretch, fontStyle, font-style, fontVariantCaps, font-variant-caps, fontVariantEastAsian, font-variant-east-asian, fontVariantLigatures, font-variant-ligatures, fontVariantNumeric, font-variant-numeric, fontVariantPosition, font-variant-position, fontWeight, font-weight, gridAutoFlow, grid-auto-flow, hyphens, imageOrientation, image-orientation, imageRendering, image-rendering, imeMode, ime-mode, isolation, justifyContent, justify-content, justifyItems, justify-items, justifySelf, justify-self, lineBreak, line-break, listStylePosition, list-style-position, maskType, mask-type, mixBlendMode, mix-blend-mode, MozBoxAlign, -moz-box-align, MozBoxDirection, -moz-box-direction, MozBoxOrient, -moz-box-orient, MozBoxPack, -moz-box-pack, MozFloatEdge, -moz-float-edge, MozOrient, -moz-orient, MozTextSizeAdjust, -moz-text-size-adjust, MozUserFocus, -moz-user-focus, MozUserInput, -moz-user-input, MozUserModify, -moz-user-modify, MozWindowDragging, -moz-window-dragging, objectFit, object-fit, offsetRotate, offset-rotate, outlineStyle, outline-style, overflowAnchor, overflow-anchor, overflowWrap, overflow-wrap, paintOrder, paint-order, pointerEvents, pointer-events, position, printColorAdjust, print-color-adjust, resize, rubyAlign, ruby-align, rubyPosition, ruby-position, scrollBehavior, scroll-behavior, scrollSnapAlign, scroll-snap-align, scrollSnapStop, scroll-snap-stop, scrollSnapType, scroll-snap-type, scrollbarGutter, scrollbar-gutter, scrollbarWidth, scrollbar-width, shapeRendering, shape-rendering, strokeLinecap, stroke-linecap, strokeLinejoin, stroke-linejoin, tableLayout, table-layout, textAlign, text-align, textAlignLast, text-align-last, textAnchor, text-anchor, textCombineUpright, text-combine-upright, textDecorationLine, text-decoration-line, textDecorationSkipInk, text-decoration-skip-ink, textDecorationStyle, text-decoration-style, textEmphasisPosition, text-emphasis-position, textJustify, text-justify, textOrientation, text-orientation, textRendering, text-rendering, textTransform, text-transform, textUnderlinePosition, text-underline-position, touchAction, touch-action, transformBox, transform-box, transformStyle, transform-style, unicodeBidi, unicode-bidi, userSelect, user-select, vectorEffect, vector-effect, visibility, webkitLineClamp, WebkitLineClamp, -webkit-line-clamp, whiteSpace, white-space, wordBreak, word-break, writingMode, writing-mode, zIndex, z-index, appearance, MozForceBrokenImageIcon, -moz-force-broken-image-icon, breakAfter, break-after, breakBefore, break-before, clipRule, clip-rule, fillRule, fill-rule, fillOpacity, fill-opacity, strokeOpacity, stroke-opacity, fontSynthesisSmallCaps, font-synthesis-small-caps, fontSynthesisStyle, font-synthesis-style, fontSynthesisWeight, font-synthesis-weight, MozBoxOrdinalGroup, -moz-box-ordinal-group, order, flexGrow, flex-grow, flexShrink, flex-shrink, MozBoxFlex, -moz-box-flex, strokeMiterlimit, stroke-miterlimit, overflowBlock, overflow-block, overflowInline, overflow-inline, overflowX, overflow-x, overflowY, overflow-y, overscrollBehaviorBlock, overscroll-behavior-block, overscrollBehaviorInline, overscroll-behavior-inline, overscrollBehaviorX, overscroll-behavior-x, overscrollBehaviorY, overscroll-behavior-y, floodOpacity, flood-opacity, opacity, shapeImageThreshold, shape-image-threshold, stopOpacity, stop-opacity, borderBlockEndStyle, border-block-end-style, borderBlockStartStyle, border-block-start-style, borderBottomStyle, border-bottom-style, borderInlineEndStyle, border-inline-end-style, borderInlineStartStyle, border-inline-start-style, borderLeftStyle, border-left-style, borderRightStyle, border-right-style, borderTopStyle, border-top-style, columnRuleStyle, column-rule-style, accentColor, accent-color, animationDelay, animation-delay, animationDirection, animation-direction, animationDuration, animation-duration, animationFillMode, animation-fill-mode, animationIterationCount, animation-iteration-count, animationName, animation-name, animationPlayState, animation-play-state, animationTimingFunction, animation-timing-function, backdropFilter, backdrop-filter, backgroundAttachment, background-attachment, backgroundBlendMode, background-blend-mode, backgroundClip, background-clip, backgroundImage, background-image, backgroundOrigin, background-origin, backgroundPositionX, background-position-x, backgroundPositionY, background-position-y, backgroundRepeat, background-repeat, backgroundSize, background-size, borderImageOutset, border-image-outset, borderImageSlice, border-image-slice, borderImageWidth, border-image-width, borderSpacing, border-spacing, boxShadow, box-shadow, caretColor, caret-color, clip, clipPath, clip-path, color, colorScheme, color-scheme, columnWidth, column-width, containerName, container-name, content, counterIncrement, counter-increment, counterReset, counter-reset, counterSet, counter-set, cursor, d, filter, flexBasis, flex-basis, fontFamily, font-family, fontFeatureSettings, font-feature-settings, fontPalette, font-palette, fontSize, font-size, fontVariantAlternates, font-variant-alternates, fontVariationSettings, font-variation-settings, gridTemplateAreas, grid-template-areas, hyphenateCharacter, hyphenate-character, letterSpacing, letter-spacing, lineHeight, line-height, listStyleType, list-style-type, maskClip, mask-clip, maskComposite, mask-composite, maskImage, mask-image, maskMode, mask-mode, maskOrigin, mask-origin, maskPositionX, mask-position-x, maskPositionY, mask-position-y, maskRepeat, mask-repeat, maskSize, mask-size, offsetPath, offset-path, page, perspective, quotes, rotate, scale, scrollbarColor, scrollbar-color, shapeOutside, shape-outside, strokeDasharray, stroke-dasharray, strokeDashoffset, stroke-dashoffset, strokeWidth, stroke-width, tabSize, tab-size, textDecorationThickness, text-decoration-thickness, textEmphasisStyle, text-emphasis-style, textOverflow, text-overflow, textShadow, text-shadow, transitionDelay, transition-delay, transitionDuration, transition-duration, transitionProperty, transition-property, transitionTimingFunction, transition-timing-function, translate, verticalAlign, vertical-align, willChange, will-change, wordSpacing, word-spacing, objectPosition, object-position, perspectiveOrigin, perspective-origin, offsetAnchor, offset-anchor, fill, stroke, transformOrigin, transform-origin, gridTemplateColumns, grid-template-columns, gridTemplateRows, grid-template-rows, borderImageSource, border-image-source, listStyleImage, list-style-image, gridAutoColumns, grid-auto-columns, gridAutoRows, grid-auto-rows, transform, columnGap, column-gap, rowGap, row-gap, markerEnd, marker-end, markerMid, marker-mid, markerStart, marker-start, containIntrinsicBlockSize, contain-intrinsic-block-size, containIntrinsicHeight, contain-intrinsic-height, containIntrinsicInlineSize, contain-intrinsic-inline-size, containIntrinsicWidth, contain-intrinsic-width, gridColumnEnd, grid-column-end, gridColumnStart, grid-column-start, gridRowEnd, grid-row-end, gridRowStart, grid-row-start, maxBlockSize, max-block-size, maxHeight, max-height, maxInlineSize, max-inline-size, maxWidth, max-width, cx, cy, offsetDistance, offset-distance, textIndent, text-indent, x, y, borderBottomLeftRadius, border-bottom-left-radius, borderBottomRightRadius, border-bottom-right-radius, borderEndEndRadius, border-end-end-radius, borderEndStartRadius, border-end-start-radius, borderStartEndRadius, border-start-end-radius, borderStartStartRadius, border-start-start-radius, borderTopLeftRadius, border-top-left-radius, borderTopRightRadius, border-top-right-radius, blockSize, block-size, height, inlineSize, inline-size, minBlockSize, min-block-size, minHeight, min-height, minInlineSize, min-inline-size, minWidth, min-width, width, paddingBlockEnd, padding-block-end, paddingBlockStart, padding-block-start, paddingBottom, padding-bottom, paddingInlineEnd, padding-inline-end, paddingInlineStart, padding-inline-start, paddingLeft, padding-left, paddingRight, padding-right, paddingTop, padding-top, r, shapeMargin, shape-margin, rx, ry, scrollPaddingBlockEnd, scroll-padding-block-end, scrollPaddingBlockStart, scroll-padding-block-start, scrollPaddingBottom, scroll-padding-bottom, scrollPaddingInlineEnd, scroll-padding-inline-end, scrollPaddingInlineStart, scroll-padding-inline-start, scrollPaddingLeft, scroll-padding-left, scrollPaddingRight, scroll-padding-right, scrollPaddingTop, scroll-padding-top, borderBlockEndWidth, border-block-end-width, borderBlockStartWidth, border-block-start-width, borderBottomWidth, border-bottom-width, borderInlineEndWidth, border-inline-end-width, borderInlineStartWidth, border-inline-start-width, borderLeftWidth, border-left-width, borderRightWidth, border-right-width, borderTopWidth, border-top-width, columnRuleWidth, column-rule-width, outlineWidth, outline-width, webkitTextStrokeWidth, WebkitTextStrokeWidth, -webkit-text-stroke-width, outlineOffset, outline-offset, overflowClipMargin, overflow-clip-margin, scrollMarginBlockEnd, scroll-margin-block-end, scrollMarginBlockStart, scroll-margin-block-start, scrollMarginBottom, scroll-margin-bottom, scrollMarginInlineEnd, scroll-margin-inline-end, scrollMarginInlineStart, scroll-margin-inline-start, scrollMarginLeft, scroll-margin-left, scrollMarginRight, scroll-margin-right, scrollMarginTop, scroll-margin-top, bottom, insetBlockEnd, inset-block-end, insetBlockStart, inset-block-start, insetInlineEnd, inset-inline-end, insetInlineStart, inset-inline-start, left, marginBlockEnd, margin-block-end, marginBlockStart, margin-block-start, marginBottom, margin-bottom, marginInlineEnd, margin-inline-end, marginInlineStart, margin-inline-start, marginLeft, margin-left, marginRight, margin-right, marginTop, margin-top, right, textUnderlineOffset, text-underline-offset, top, backgroundColor, background-color, borderBlockEndColor, border-block-end-color, borderBlockStartColor, border-block-start-color, borderBottomColor, border-bottom-color, borderInlineEndColor, border-inline-end-color, borderInlineStartColor, border-inline-start-color, borderLeftColor, border-left-color, borderRightColor, border-right-color, borderTopColor, border-top-color, columnRuleColor, column-rule-color, floodColor, flood-color, lightingColor, lighting-color, outlineColor, outline-color, stopColor, stop-color, textDecorationColor, text-decoration-color, textEmphasisColor, text-emphasis-color, webkitTextFillColor, WebkitTextFillColor, -webkit-text-fill-color, webkitTextStrokeColor, WebkitTextStrokeColor, -webkit-text-stroke-color, background, backgroundPosition, background-position, borderColor, border-color, borderStyle, border-style, borderWidth, border-width, borderTop, border-top, borderRight, border-right, borderBottom, border-bottom, borderLeft, border-left, borderBlockStart, border-block-start, borderBlockEnd, border-block-end, borderInlineStart, border-inline-start, borderInlineEnd, border-inline-end, border, borderRadius, border-radius, borderImage, border-image, borderBlockWidth, border-block-width, borderBlockStyle, border-block-style, borderBlockColor, border-block-color, borderInlineWidth, border-inline-width, borderInlineStyle, border-inline-style, borderInlineColor, border-inline-color, borderBlock, border-block, borderInline, border-inline, overflow, overscrollBehavior, overscroll-behavior, container, pageBreakBefore, page-break-before, pageBreakAfter, page-break-after, pageBreakInside, page-break-inside, offset, columns, columnRule, column-rule, font, fontVariant, font-variant, fontSynthesis, font-synthesis, marker, textEmphasis, text-emphasis, webkitTextStroke, WebkitTextStroke, -webkit-text-stroke, listStyle, list-style, margin, marginBlock, margin-block, marginInline, margin-inline, scrollMargin, scroll-margin, scrollMarginBlock, scroll-margin-block, scrollMarginInline, scroll-margin-inline, outline, padding, paddingBlock, padding-block, paddingInline, padding-inline, scrollPadding, scroll-padding, scrollPaddingBlock, scroll-padding-block, scrollPaddingInline, scroll-padding-inline, flexFlow, flex-flow, flex, gap, gridRow, grid-row, gridColumn, grid-column, gridArea, grid-area, gridTemplate, grid-template, grid, placeContent, place-content, placeSelf, place-self, placeItems, place-items, inset, insetBlock, inset-block, insetInline, inset-inline, containIntrinsicSize, contain-intrinsic-size, mask, maskPosition, mask-position, textDecoration, text-decoration, transition, animation, all, webkitBackgroundClip, WebkitBackgroundClip, -webkit-background-clip, webkitBackgroundOrigin, WebkitBackgroundOrigin, -webkit-background-origin, webkitBackgroundSize, WebkitBackgroundSize, -webkit-background-size, MozBorderStartColor, -moz-border-start-color, MozBorderStartStyle, -moz-border-start-style, MozBorderStartWidth, -moz-border-start-width, MozBorderEndColor, -moz-border-end-color, MozBorderEndStyle, -moz-border-end-style, MozBorderEndWidth, -moz-border-end-width, webkitBorderTopLeftRadius, WebkitBorderTopLeftRadius, -webkit-border-top-left-radius, webkitBorderTopRightRadius, WebkitBorderTopRightRadius, -webkit-border-top-right-radius, webkitBorderBottomRightRadius, WebkitBorderBottomRightRadius, -webkit-border-bottom-right-radius, webkitBorderBottomLeftRadius, WebkitBorderBottomLeftRadius, -webkit-border-bottom-left-radius, MozTransform, -moz-transform, webkitTransform, WebkitTransform, -webkit-transform, MozPerspective, -moz-perspective, webkitPerspective, WebkitPerspective, -webkit-perspective, MozPerspectiveOrigin, -moz-perspective-origin, webkitPerspectiveOrigin, WebkitPerspectiveOrigin, -webkit-perspective-origin, MozBackfaceVisibility, -moz-backface-visibility, webkitBackfaceVisibility, WebkitBackfaceVisibility, -webkit-backface-visibility, MozTransformStyle, -moz-transform-style, webkitTransformStyle, WebkitTransformStyle, -webkit-transform-style, MozTransformOrigin, -moz-transform-origin, webkitTransformOrigin, WebkitTransformOrigin, -webkit-transform-origin, MozAppearance, -moz-appearance, webkitAppearance, WebkitAppearance, -webkit-appearance, webkitBoxShadow, WebkitBoxShadow, -webkit-box-shadow, webkitFilter, WebkitFilter, -webkit-filter, MozFontFeatureSettings, -moz-font-feature-settings, MozFontLanguageOverride, -moz-font-language-override, colorAdjust, color-adjust, MozHyphens, -moz-hyphens, webkitTextSizeAdjust, WebkitTextSizeAdjust, -webkit-text-size-adjust, wordWrap, word-wrap, MozTabSize, -moz-tab-size, MozMarginStart, -moz-margin-start, MozMarginEnd, -moz-margin-end, MozPaddingStart, -moz-padding-start, MozPaddingEnd, -moz-padding-end, webkitFlexDirection, WebkitFlexDirection, -webkit-flex-direction, webkitFlexWrap, WebkitFlexWrap, -webkit-flex-wrap, webkitJustifyContent, WebkitJustifyContent, -webkit-justify-content, webkitAlignContent, WebkitAlignContent, -webkit-align-content, webkitAlignItems, WebkitAlignItems, -webkit-align-items, webkitFlexGrow, WebkitFlexGrow, -webkit-flex-grow, webkitFlexShrink, WebkitFlexShrink, -webkit-flex-shrink, webkitAlignSelf, WebkitAlignSelf, -webkit-align-self, webkitOrder, WebkitOrder, -webkit-order, webkitFlexBasis, WebkitFlexBasis, -webkit-flex-basis, MozBoxSizing, -moz-box-sizing, webkitBoxSizing, WebkitBoxSizing, -webkit-box-sizing, gridColumnGap, grid-column-gap, gridRowGap, grid-row-gap, webkitClipPath, WebkitClipPath, -webkit-clip-path, webkitMaskRepeat, WebkitMaskRepeat, -webkit-mask-repeat, webkitMaskPositionX, WebkitMaskPositionX, -webkit-mask-position-x, webkitMaskPositionY, WebkitMaskPositionY, -webkit-mask-position-y, webkitMaskClip, WebkitMaskClip, -webkit-mask-clip, webkitMaskOrigin, WebkitMaskOrigin, -webkit-mask-origin, webkitMaskSize, WebkitMaskSize, -webkit-mask-size, webkitMaskComposite, WebkitMaskComposite, -webkit-mask-composite, webkitMaskImage, WebkitMaskImage, -webkit-mask-image, MozUserSelect, -moz-user-select, webkitUserSelect, WebkitUserSelect, -webkit-user-select, MozTransitionDuration, -moz-transition-duration, webkitTransitionDuration, WebkitTransitionDuration, -webkit-transition-duration, MozTransitionTimingFunction, -moz-transition-timing-function, webkitTransitionTimingFunction, WebkitTransitionTimingFunction, -webkit-transition-timing-function, MozTransitionProperty, -moz-transition-property, webkitTransitionProperty, WebkitTransitionProperty, -webkit-transition-property, MozTransitionDelay, -moz-transition-delay, webkitTransitionDelay, WebkitTransitionDelay, -webkit-transition-delay, MozAnimationName, -moz-animation-name, webkitAnimationName, WebkitAnimationName, -webkit-animation-name, MozAnimationDuration, -moz-animation-duration, webkitAnimationDuration, WebkitAnimationDuration, -webkit-animation-duration, MozAnimationTimingFunction, -moz-animation-timing-function, webkitAnimationTimingFunction, WebkitAnimationTimingFunction, -webkit-animation-timing-function, MozAnimationIterationCount, -moz-animation-iteration-count, webkitAnimationIterationCount, WebkitAnimationIterationCount, -webkit-animation-iteration-count, MozAnimationDirection, -moz-animation-direction, webkitAnimationDirection, WebkitAnimationDirection, -webkit-animation-direction, MozAnimationPlayState, -moz-animation-play-state, webkitAnimationPlayState, WebkitAnimationPlayState, -webkit-animation-play-state, MozAnimationFillMode, -moz-animation-fill-mode, webkitAnimationFillMode, WebkitAnimationFillMode, -webkit-animation-fill-mode, MozAnimationDelay, -moz-animation-delay, webkitAnimationDelay, WebkitAnimationDelay, -webkit-animation-delay, webkitBoxAlign, WebkitBoxAlign, -webkit-box-align, webkitBoxDirection, WebkitBoxDirection, -webkit-box-direction, webkitBoxFlex, WebkitBoxFlex, -webkit-box-flex, webkitBoxOrient, WebkitBoxOrient, -webkit-box-orient, webkitBoxPack, WebkitBoxPack, -webkit-box-pack, webkitBoxOrdinalGroup, WebkitBoxOrdinalGroup, -webkit-box-ordinal-group, MozBorderStart, -moz-border-start, MozBorderEnd, -moz-border-end, webkitBorderRadius, WebkitBorderRadius, -webkit-border-radius, MozBorderImage, -moz-border-image, webkitBorderImage, WebkitBorderImage, -webkit-border-image, webkitFlexFlow, WebkitFlexFlow, -webkit-flex-flow, webkitFlex, WebkitFlex, -webkit-flex, gridGap, grid-gap, webkitMask, WebkitMask, -webkit-mask, webkitMaskPosition, WebkitMaskPosition, -webkit-mask-position, MozTransition, -moz-transition, webkitTransition, WebkitTransition, -webkit-transition, MozAnimation, -moz-animation, webkitAnimation, WebkitAnimation, -webkit-animation, constructor`, jsKeys: 'Object.assign, Object.getPrototypeOf, Object.setPrototypeOf, Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptors, Object.keys, Object.values, Object.entries, Object.is, Object.defineProperty, Object.defineProperties, Object.create, Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.isExtensible, Object.preventExtensions, Object.freeze, Object.isFrozen, Object.seal, Object.isSealed, Object.fromEntries, Object.hasOwn, Object.toString, Object.toLocaleString, Object.valueOf, Object.hasOwnProperty, Object.isPrototypeOf, Object.propertyIsEnumerable, Object.__defineGetter__, Object.__defineSetter__, Object.__lookupGetter__, Object.__lookupSetter__, Object.__proto__, Function.toString, Function.apply, Function.call, Function.bind, Boolean.toString, Boolean.valueOf, Symbol.for, Symbol.keyFor, Symbol.isConcatSpreadable, Symbol.iterator, Symbol.match, Symbol.replace, Symbol.search, Symbol.species, Symbol.hasInstance, Symbol.split, Symbol.toPrimitive, Symbol.toStringTag, Symbol.unscopables, Symbol.asyncIterator, Symbol.matchAll, Symbol.toString, Symbol.valueOf, Symbol.description, Error.toString, Error.message, Error.stack, Number.isFinite, Number.isInteger, Number.isNaN, Number.isSafeInteger, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.MAX_VALUE, Number.MIN_VALUE, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.EPSILON, Number.parseInt, Number.parseFloat, Number.NaN, Number.toString, Number.toLocaleString, Number.valueOf, Number.toFixed, Number.toExponential, Number.toPrecision, BigInt.asUintN, BigInt.asIntN, BigInt.valueOf, BigInt.toString, BigInt.toLocaleString, Math.abs, Math.acos, Math.asin, Math.atan, Math.atan2, Math.ceil, Math.clz32, Math.cos, Math.exp, Math.floor, Math.imul, Math.fround, Math.log, Math.max, Math.min, Math.pow, Math.random, Math.round, Math.sin, Math.sqrt, Math.tan, Math.log10, Math.log2, Math.log1p, Math.expm1, Math.cosh, Math.sinh, Math.tanh, Math.acosh, Math.asinh, Math.atanh, Math.hypot, Math.trunc, Math.sign, Math.cbrt, Math.E, Math.LOG2E, Math.LOG10E, Math.LN2, Math.LN10, Math.PI, Math.SQRT2, Math.SQRT1_2, Date.UTC, Date.parse, Date.now, Date.getTime, Date.getTimezoneOffset, Date.getYear, Date.getFullYear, Date.getUTCFullYear, Date.getMonth, Date.getUTCMonth, Date.getDate, Date.getUTCDate, Date.getDay, Date.getUTCDay, Date.getHours, Date.getUTCHours, Date.getMinutes, Date.getUTCMinutes, Date.getSeconds, Date.getUTCSeconds, Date.getMilliseconds, Date.getUTCMilliseconds, Date.setTime, Date.setYear, Date.setFullYear, Date.setUTCFullYear, Date.setMonth, Date.setUTCMonth, Date.setDate, Date.setUTCDate, Date.setHours, Date.setUTCHours, Date.setMinutes, Date.setUTCMinutes, Date.setSeconds, Date.setUTCSeconds, Date.setMilliseconds, Date.setUTCMilliseconds, Date.toUTCString, Date.toLocaleString, Date.toLocaleDateString, Date.toLocaleTimeString, Date.toDateString, Date.toTimeString, Date.toISOString, Date.toJSON, Date.toString, Date.valueOf, Date.toGMTString, String.fromCharCode, String.fromCodePoint, String.raw, String.toString, String.valueOf, String.toLowerCase, String.toUpperCase, String.charAt, String.charCodeAt, String.substring, String.padStart, String.padEnd, String.codePointAt, String.includes, String.indexOf, String.lastIndexOf, String.startsWith, String.endsWith, String.trim, String.trimStart, String.trimEnd, String.toLocaleLowerCase, String.toLocaleUpperCase, String.localeCompare, String.repeat, String.normalize, String.match, String.matchAll, String.search, String.replace, String.replaceAll, String.split, String.substr, String.concat, String.slice, String.at, String.bold, String.italics, String.fixed, String.strike, String.small, String.big, String.blink, String.sup, String.sub, String.anchor, String.link, String.fontcolor, String.fontsize, String.trimLeft, String.trimRight, RegExp.input, RegExp.lastMatch, RegExp.lastParen, RegExp.leftContext, RegExp.rightContext, RegExp.$1, RegExp.$2, RegExp.$3, RegExp.$4, RegExp.$5, RegExp.$6, RegExp.$7, RegExp.$8, RegExp.$9, RegExp.$_, RegExp.$&, RegExp.$+, RegExp.$`, RegExp.$\', RegExp.toString, RegExp.compile, RegExp.exec, RegExp.test, RegExp.flags, RegExp.hasIndices, RegExp.global, RegExp.ignoreCase, RegExp.multiline, RegExp.dotAll, RegExp.source, RegExp.sticky, RegExp.unicode, Array.isArray, Array.from, Array.of, Array.toString, Array.toLocaleString, Array.join, Array.reverse, Array.sort, Array.push, Array.pop, Array.shift, Array.unshift, Array.splice, Array.concat, Array.slice, Array.lastIndexOf, Array.indexOf, Array.forEach, Array.map, Array.filter, Array.reduce, Array.reduceRight, Array.some, Array.every, Array.find, Array.findIndex, Array.copyWithin, Array.fill, Array.entries, Array.keys, Array.values, Array.includes, Array.flatMap, Array.flat, Array.at, Array.findLast, Array.findLastIndex, Map.get, Map.has, Map.set, Map.delete, Map.keys, Map.values, Map.clear, Map.forEach, Map.entries, Map.size, Set.has, Set.add, Set.delete, Set.entries, Set.clear, Set.forEach, Set.values, Set.keys, Set.size, WeakMap.has, WeakMap.get, WeakMap.delete, WeakMap.set, WeakSet.add, WeakSet.delete, WeakSet.has, Atomics.compareExchange, Atomics.load, Atomics.store, Atomics.exchange, Atomics.add, Atomics.sub, Atomics.and, Atomics.or, Atomics.xor, Atomics.isLockFree, Atomics.wait, Atomics.notify, Atomics.wake, JSON.parse, JSON.stringify, Promise.all, Promise.allSettled, Promise.any, Promise.race, Promise.reject, Promise.resolve, Promise.then, Promise.catch, Promise.finally, Reflect.apply, Reflect.construct, Reflect.defineProperty, Reflect.deleteProperty, Reflect.get, Reflect.getOwnPropertyDescriptor, Reflect.getPrototypeOf, Reflect.has, Reflect.isExtensible, Reflect.ownKeys, Reflect.preventExtensions, Reflect.set, Reflect.setPrototypeOf, Proxy.revocable, Intl.getCanonicalLocales, Intl.supportedValuesOf, Intl.Collator, Intl.DateTimeFormat, Intl.DisplayNames, Intl.ListFormat, Intl.Locale, Intl.NumberFormat, Intl.PluralRules, Intl.RelativeTimeFormat, WebAssembly.compile, WebAssembly.instantiate, WebAssembly.validate, WebAssembly.compileStreaming, WebAssembly.instantiateStreaming, WebAssembly.Module, WebAssembly.Instance, WebAssembly.Memory, WebAssembly.Table, WebAssembly.Global, WebAssembly.CompileError, WebAssembly.LinkError, WebAssembly.RuntimeError, WebAssembly.Tag, WebAssembly.Exception, Document.getElementsByTagName, Document.getElementsByTagNameNS, Document.getElementsByClassName, Document.getElementById, Document.createElement, Document.createElementNS, Document.createDocumentFragment, Document.createTextNode, Document.createComment, Document.createProcessingInstruction, Document.importNode, Document.adoptNode, Document.createEvent, Document.createRange, Document.createNodeIterator, Document.createTreeWalker, Document.createCDATASection, Document.createAttribute, Document.createAttributeNS, Document.getElementsByName, Document.open, Document.close, Document.write, Document.writeln, Document.hasFocus, Document.execCommand, Document.queryCommandEnabled, Document.queryCommandIndeterm, Document.queryCommandState, Document.queryCommandSupported, Document.queryCommandValue, Document.releaseCapture, Document.mozSetImageElement, Document.clear, Document.captureEvents, Document.releaseEvents, Document.exitFullscreen, Document.mozCancelFullScreen, Document.exitPointerLock, Document.enableStyleSheetsForSet, Document.caretPositionFromPoint, Document.querySelector, Document.querySelectorAll, Document.getSelection, Document.hasStorageAccess, Document.requestStorageAccess, Document.elementFromPoint, Document.elementsFromPoint, Document.getAnimations, Document.prepend, Document.append, Document.replaceChildren, Document.createExpression, Document.createNSResolver, Document.evaluate, Document.implementation, Document.URL, Document.documentURI, Document.compatMode, Document.characterSet, Document.charset, Document.inputEncoding, Document.contentType, Document.doctype, Document.documentElement, Document.domain, Document.referrer, Document.cookie, Document.lastModified, Document.readyState, Document.title, Document.dir, Document.body, Document.head, Document.images, Document.embeds, Document.plugins, Document.links, Document.forms, Document.scripts, Document.defaultView, Document.designMode, Document.onreadystatechange, Document.onbeforescriptexecute, Document.onafterscriptexecute, Document.currentScript, Document.fgColor, Document.linkColor, Document.vlinkColor, Document.alinkColor, Document.bgColor, Document.anchors, Document.applets, Document.all, Document.fullscreen, Document.mozFullScreen, Document.fullscreenEnabled, Document.mozFullScreenEnabled, Document.onfullscreenchange, Document.onfullscreenerror, Document.onpointerlockchange, Document.onpointerlockerror, Document.hidden, Document.visibilityState, Document.onvisibilitychange, Document.selectedStyleSheetSet, Document.lastStyleSheetSet, Document.preferredStyleSheetSet, Document.styleSheetSets, Document.scrollingElement, Document.timeline, Document.rootElement, Document.activeElement, Document.styleSheets, Document.pointerLockElement, Document.fullscreenElement, Document.mozFullScreenElement, Document.adoptedStyleSheets, Document.fonts, Document.onabort, Document.onblur, Document.onfocus, Document.onauxclick, Document.onbeforeinput, Document.oncanplay, Document.oncanplaythrough, Document.onchange, Document.onclick, Document.onclose, Document.oncontextmenu, Document.oncopy, Document.oncuechange, Document.oncut, Document.ondblclick, Document.ondrag, Document.ondragend, Document.ondragenter, Document.ondragexit, Document.ondragleave, Document.ondragover, Document.ondragstart, Document.ondrop, Document.ondurationchange, Document.onemptied, Document.onended, Document.onformdata, Document.oninput, Document.oninvalid, Document.onkeydown, Document.onkeypress, Document.onkeyup, Document.onload, Document.onloadeddata, Document.onloadedmetadata, Document.onloadstart, Document.onmousedown, Document.onmouseenter, Document.onmouseleave, Document.onmousemove, Document.onmouseout, Document.onmouseover, Document.onmouseup, Document.onwheel, Document.onpaste, Document.onpause, Document.onplay, Document.onplaying, Document.onprogress, Document.onratechange, Document.onreset, Document.onresize, Document.onscroll, Document.onscrollend, Document.onsecuritypolicyviolation, Document.onseeked, Document.onseeking, Document.onselect, Document.onslotchange, Document.onstalled, Document.onsubmit, Document.onsuspend, Document.ontimeupdate, Document.onvolumechange, Document.onwaiting, Document.onselectstart, Document.onselectionchange, Document.ontoggle, Document.onpointercancel, Document.onpointerdown, Document.onpointerup, Document.onpointermove, Document.onpointerout, Document.onpointerover, Document.onpointerenter, Document.onpointerleave, Document.ongotpointercapture, Document.onlostpointercapture, Document.onmozfullscreenchange, Document.onmozfullscreenerror, Document.onanimationcancel, Document.onanimationend, Document.onanimationiteration, Document.onanimationstart, Document.ontransitioncancel, Document.ontransitionend, Document.ontransitionrun, Document.ontransitionstart, Document.onwebkitanimationend, Document.onwebkitanimationiteration, Document.onwebkitanimationstart, Document.onwebkittransitionend, Document.onerror, Document.children, Document.firstElementChild, Document.lastElementChild, Document.childElementCount, Element.getAttributeNames, Element.getAttribute, Element.getAttributeNS, Element.toggleAttribute, Element.setAttribute, Element.setAttributeNS, Element.removeAttribute, Element.removeAttributeNS, Element.hasAttribute, Element.hasAttributeNS, Element.hasAttributes, Element.closest, Element.matches, Element.webkitMatchesSelector, Element.getElementsByTagName, Element.getElementsByTagNameNS, Element.getElementsByClassName, Element.insertAdjacentElement, Element.insertAdjacentText, Element.mozMatchesSelector, Element.setPointerCapture, Element.releasePointerCapture, Element.hasPointerCapture, Element.setCapture, Element.releaseCapture, Element.getAttributeNode, Element.setAttributeNode, Element.removeAttributeNode, Element.getAttributeNodeNS, Element.setAttributeNodeNS, Element.getClientRects, Element.getBoundingClientRect, Element.checkVisibility, Element.scrollIntoView, Element.scroll, Element.scrollTo, Element.scrollBy, Element.insertAdjacentHTML, Element.querySelector, Element.querySelectorAll, Element.attachShadow, Element.requestFullscreen, Element.mozRequestFullScreen, Element.requestPointerLock, Element.animate, Element.getAnimations, Element.before, Element.after, Element.replaceWith, Element.remove, Element.prepend, Element.append, Element.replaceChildren, Element.namespaceURI, Element.prefix, Element.localName, Element.tagName, Element.id, Element.className, Element.classList, Element.part, Element.attributes, Element.scrollTop, Element.scrollLeft, Element.scrollWidth, Element.scrollHeight, Element.clientTop, Element.clientLeft, Element.clientWidth, Element.clientHeight, Element.scrollTopMax, Element.scrollLeftMax, Element.innerHTML, Element.outerHTML, Element.shadowRoot, Element.assignedSlot, Element.slot, Element.onfullscreenchange, Element.onfullscreenerror, Element.previousElementSibling, Element.nextElementSibling, Element.children, Element.firstElementChild, Element.lastElementChild, Element.childElementCount', }, }); // @ts-ignore const getListDiff = ({ oldList, newList, removeCamelCase = false } = {}) => { const oldSet = new Set(oldList); const newSet = new Set(newList); newList.forEach((x) => oldSet.delete(x)); oldList.forEach((x) => newSet.delete(x)); const camelCase = /[a-z][A-Z]/; return { removed: !removeCamelCase ? [...oldSet] : [...oldSet].filter((key) => !camelCase.test(key)), added: !removeCamelCase ? [...newSet] : [...newSet].filter((key) => !camelCase.test(key)), }; }; const BROWSER = (IS_BLINK ? 'Chrome' : IS_GECKO ? 'Firefox' : ''); const getEngineMaps = (browser) => { // Blink const blinkJS = { '76': ['Document.onsecuritypolicyviolation', 'Promise.allSettled'], '77': ['Document.onformdata', 'Document.onpointerrawupdate'], '78': ['Element.elementTiming'], '79': ['Document.onanimationend', 'Document.onanimationiteration', 'Document.onanimationstart', 'Document.ontransitionend'], '80': ['!Document.registerElement', '!Element.createShadowRoot', '!Element.getDestinationInsertionPoints'], '81': ['Document.onwebkitanimationend', 'Document.onwebkitanimationiteration', 'Document.onwebkitanimationstart', 'Document.onwebkittransitionend', 'Element.ariaAtomic', 'Element.ariaAutoComplete', 'Element.ariaBusy', 'Element.ariaChecked', 'Element.ariaColCount', 'Element.ariaColIndex', 'Element.ariaColSpan', 'Element.ariaCurrent', 'Element.ariaDisabled', 'Element.ariaExpanded', 'Element.ariaHasPopup', 'Element.ariaHidden', 'Element.ariaKeyShortcuts', 'Element.ariaLabel', 'Element.ariaLevel', 'Element.ariaLive', 'Element.ariaModal', 'Element.ariaMultiLine', 'Element.ariaMultiSelectable', 'Element.ariaOrientation', 'Element.ariaPlaceholder', 'Element.ariaPosInSet', 'Element.ariaPressed', 'Element.ariaReadOnly', 'Element.ariaRelevant', 'Element.ariaRequired', 'Element.ariaRoleDescription', 'Element.ariaRowCount', 'Element.ariaRowIndex', 'Element.ariaRowSpan', 'Element.ariaSelected', 'Element.ariaSort', 'Element.ariaValueMax', 'Element.ariaValueMin', 'Element.ariaValueNow', 'Element.ariaValueText', 'Intl.DisplayNames'], '83': ['Element.ariaDescription', 'Element.onbeforexrselect'], '84': ['Document.getAnimations', 'Document.timeline', 'Element.ariaSetSize', 'Element.getAnimations'], '85': ['Promise.any', 'String.replaceAll'], '86': ['Document.fragmentDirective', 'Document.replaceChildren', 'Element.replaceChildren', '!Atomics.wake'], '87-89': ['Atomics.waitAsync', 'Document.ontransitioncancel', 'Document.ontransitionrun', 'Document.ontransitionstart', 'Intl.Segmenter'], '90': ['Document.onbeforexrselect', 'RegExp.hasIndices', '!Element.onbeforexrselect'], '91': ['Element.getInnerHTML'], '92': ['Array.at', 'String.at'], '93': ['Error.cause', 'Object.hasOwn'], '94': ['!Error.cause', 'Object.hasOwn'], '95-96': ['WebAssembly.Exception', 'WebAssembly.Tag'], '97-98': ['Array.findLast', 'Array.findLastIndex', 'Document.onslotchange'], '99-101': ['Intl.supportedValuesOf', 'Document.oncontextlost', 'Document.oncontextrestored'], '102': ['Element.ariaInvalid', 'Document.onbeforematch'], '103-106': ['Element.role'], '107-109': ['Element.ariaBrailleLabel', 'Element.ariaBrailleRoleDescription'], '110': ['Array.toReversed', 'Array.toSorted', 'Array.toSpliced', 'Array.with'], '111': ['String.isWellFormed', 'String.toWellFormed', 'Document.startViewTransition'], '112-113': ['RegExp.unicodeSets'], '114-115': ['JSON.rawJSON', 'JSON.isRawJSON'], }; const blinkCSS = { '76': ['backdrop-filter'], '77-80': ['overscroll-behavior-block', 'overscroll-behavior-inline'], '81': ['color-scheme', 'image-orientation'], '83': ['contain-intrinsic-size'], '84': ['appearance', 'ruby-position'], '85-86': ['content-visibility', 'counter-set', 'inherits', 'initial-value', 'page-orientation', 'syntax'], '87': ['ascent-override', 'border-block', 'border-block-color', 'border-block-style', 'border-block-width', 'border-inline', 'border-inline-color', 'border-inline-style', 'border-inline-width', 'descent-override', 'inset', 'inset-block', 'inset-block-end', 'inset-block-start', 'inset-inline', 'inset-inline-end', 'inset-inline-start', 'line-gap-override', 'margin-block', 'margin-inline', 'padding-block', 'padding-inline', 'text-decoration-thickness', 'text-underline-offset'], '88': ['aspect-ratio'], '89': ['border-end-end-radius', 'border-end-start-radius', 'border-start-end-radius', 'border-start-start-radius', 'forced-color-adjust'], '90': ['overflow-clip-margin'], '91': ['additive-symbols', 'fallback', 'negative', 'pad', 'prefix', 'range', 'speak-as', 'suffix', 'symbols', 'system'], '92': ['size-adjust'], '93': ['accent-color'], '94': ['scrollbar-gutter'], '95-96': ['app-region', 'contain-intrinsic-block-size', 'contain-intrinsic-height', 'contain-intrinsic-inline-size', 'contain-intrinsic-width'], '97-98': ['font-synthesis-small-caps', 'font-synthesis-style', 'font-synthesis-weight', 'font-synthesis'], '99-100': ['text-emphasis-color', 'text-emphasis-position', 'text-emphasis-style', 'text-emphasis'], '101-103': ['font-palette', 'base-palette', 'override-colors'], '104': ['object-view-box'], '105': ['container-name', 'container-type', 'container'], '106-107': ['hyphenate-character'], '108': ['hyphenate-character', '!orientation', '!max-zoom', '!min-zoom', '!user-zoom'], '109': ['hyphenate-limit-chars', 'math-depth', 'math-shift', 'math-style'], '110': ['initial-letter'], '111-113': ['baseline-source', 'font-variant-alternates', 'view-transition-name'], '114-115': ['text-wrap', 'white-space-collapse'], }; const blinkWindow = { '80': ['CompressionStream', 'DecompressionStream', 'FeaturePolicy', 'FragmentDirective', 'PeriodicSyncManager', 'VideoPlaybackQuality'], '81': ['SubmitEvent', 'XRHitTestResult', 'XRHitTestSource', 'XRRay', 'XRTransientInputHitTestResult', 'XRTransientInputHitTestSource'], '83': ['BarcodeDetector', 'XRDOMOverlayState', 'XRSystem'], '84': ['AnimationPlaybackEvent', 'AnimationTimeline', 'CSSAnimation', 'CSSTransition', 'DocumentTimeline', 'FinalizationRegistry', 'LayoutShiftAttribution', 'ResizeObserverSize', 'WakeLock', 'WakeLockSentinel', 'WeakRef', 'XRLayer'], '85': ['AggregateError', 'CSSPropertyRule', 'EventCounts', 'XRAnchor', 'XRAnchorSet'], '86': ['RTCEncodedAudioFrame', 'RTCEncodedVideoFrame'], '87': ['CookieChangeEvent', 'CookieStore', 'CookieStoreManager', 'Scheduling'], '88': ['Scheduling', '!BarcodeDetector'], '89': ['ReadableByteStreamController', 'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest', 'ReadableStreamDefaultController', 'XRWebGLBinding'], '90': ['AbstractRange', 'CustomStateSet', 'NavigatorUAData', 'XRCPUDepthInformation', 'XRDepthInformation', 'XRLightEstimate', 'XRLightProbe', 'XRWebGLDepthInformation'], '91': ['CSSCounterStyleRule', 'GravitySensor', 'NavigatorManagedData'], '92': ['CSSCounterStyleRule', '!SharedArrayBuffer'], '93': ['WritableStreamDefaultController'], '94': ['AudioData', 'AudioDecoder', 'AudioEncoder', 'EncodedAudioChunk', 'EncodedVideoChunk', 'IdleDetector', 'ImageDecoder', 'ImageTrack', 'ImageTrackList', 'VideoColorSpace', 'VideoDecoder', 'VideoEncoder', 'VideoFrame', 'MediaStreamTrackGenerator', 'MediaStreamTrackProcessor', 'Profiler', 'VirtualKeyboard', 'DelegatedInkTrailPresenter', 'Ink', 'Scheduler', 'TaskController', 'TaskPriorityChangeEvent', 'TaskSignal', 'VirtualKeyboardGeometryChangeEvent'], '95-96': ['URLPattern'], '97-98': ['WebTransport', 'WebTransportBidirectionalStream', 'WebTransportDatagramDuplexStream', 'WebTransportError'], '99': ['CanvasFilter', 'CSSLayerBlockRule', 'CSSLayerStatementRule'], '100': ['CSSMathClamp'], '101-104': ['CSSFontPaletteValuesRule'], '105-106': ['CSSContainerRule'], '107-108': ['XRCamera'], '109': ['MathMLElement'], '110': ['AudioSinkInfo'], '111-112': ['ViewTransition'], '113-115': ['ViewTransition', '!CanvasFilter'], }; // Gecko const geckoJS = { '71': ['Promise.allSettled'], '72-73': ['Document.onformdata', 'Element.part'], '74': ['!Array.toSource', '!Boolean.toSource', '!Date.toSource', '!Error.toSource', '!Function.toSource', '!Intl.toSource', '!JSON.toSource', '!Math.toSource', '!Number.toSource', '!Object.toSource', '!RegExp.toSource', '!String.toSource', '!WebAssembly.toSource'], '75-76': ['Document.getAnimations', 'Document.timeline', 'Element.getAnimations', 'Intl.Locale'], '77': ['String.replaceAll'], '78': ['Atomics.add', 'Atomics.and', 'Atomics.compareExchange', 'Atomics.exchange', 'Atomics.isLockFree', 'Atomics.load', 'Atomics.notify', 'Atomics.or', 'Atomics.store', 'Atomics.sub', 'Atomics.wait', 'Atomics.wake', 'Atomics.xor', 'Document.replaceChildren', 'Element.replaceChildren', 'Intl.ListFormat', 'RegExp.dotAll'], '79-84': ['Promise.any'], '85': ['!Document.onshow', 'Promise.any'], '86': ['Intl.DisplayNames'], '87': ['Document.onbeforeinput'], '88-89': ['RegExp.hasIndices'], '90-91': ['Array.at', 'String.at'], '92': ['Object.hasOwn'], '93-99': ['Intl.supportedValuesOf', 'Document.onsecuritypolicyviolation', 'Document.onslotchange'], '100': ['WebAssembly.Tag', 'WebAssembly.Exception'], '101-103': ['Document.adoptedStyleSheets'], '104-108': ['Array.findLast', 'Array.findLastIndex'], '109-112': ['Document.onscrollend'], }; // {"added":[],"removed":[]} {"added":["Document.onscrollend"],"removed":[]} {"added":["onscrollend"],"removed":[]} const geckoCSS = { '71': ['-moz-column-span'], '72': ['offset', 'offset-anchor', 'offset-distance', 'offset-path', 'offset-rotate', 'rotate', 'scale', 'translate'], '73': ['overscroll-behavior-block', 'overscroll-behavior-inline'], '74-79': ['!-moz-stack-sizing', 'text-underline-position'], '80-88': ['appearance'], '89-90': ['!-moz-outline-radius', '!-moz-outline-radius-bottomleft', '!-moz-outline-radius-bottomright', '!-moz-outline-radius-topleft', '!-moz-outline-radius-topright', 'aspect-ratio'], '91': ['tab-size'], '92-95': ['accent-color'], '96': ['color-scheme'], '97': ['print-color-adjust', 'scrollbar-gutter', 'd'], '98-101': ['hyphenate-character'], '102': ['overflow-clip-margin'], '103-106': ['scroll-snap-stop'], '107-108': ['backdrop-filter', 'font-palette', 'contain-intrinsic-block-size', 'contain-intrinsic-height', 'contain-intrinsic-inline-size', 'contain-intrinsic-width', 'contain-intrinsic-size'], '109': ['-webkit-clip-path'], '110': ['container-type', 'container-name', 'page', 'container'], '111': ['font-synthesis-small-caps', 'font-synthesis-style', 'font-synthesis-weight'], '112': ['font-synthesis-small-caps', '!-moz-image-region'], }; const geckoWindow = { // disregard: 'reportError','onsecuritypolicyviolation','onslotchange' '71': ['MathMLElement', '!SVGZoomAndPan'], '72-73': ['!BatteryManager', 'FormDataEvent', 'Geolocation', 'GeolocationCoordinates', 'GeolocationPosition', 'GeolocationPositionError', '!mozPaintCount'], '74': ['FormDataEvent', '!uneval'], '75': ['AnimationTimeline', 'CSSAnimation', 'CSSTransition', 'DocumentTimeline', 'SubmitEvent'], '76-77': ['AudioParamMap', 'AudioWorklet', 'AudioWorkletNode', 'Worklet'], '78': ['Atomics'], '79-81': ['AggregateError', 'FinalizationRegistry'], '82': ['MediaMetadata', 'MediaSession', 'Sanitizer'], '83': ['MediaMetadata', 'MediaSession', '!Sanitizer'], '84': ['PerformancePaintTiming'], '85-86': ['PerformancePaintTiming', '!HTMLMenuItemElement', '!onshow'], '87': ['onbeforeinput'], '88': ['onbeforeinput', '!VisualViewport'], '89-92': ['!ondevicelight', '!ondeviceproximity', '!onuserproximity'], '93-95': ['ElementInternals'], '96': ['Lock', 'LockManager'], '97': ['CSSLayerBlockRule', 'CSSLayerStatementRule'], '98': ['HTMLDialogElement'], '99': ['NavigationPreloadManager'], '100-104': ['WritableStream'], '105-106': ['TextDecoderStream', 'OffscreenCanvasRenderingContext2D', 'OffscreenCanvas', 'TextEncoderStream'], '107-109': ['CSSFontPaletteValuesRule'], '110': ['CSSContainerRule'], '111': ['FileSystemFileHandle', 'FileSystemDirectoryHandle'], '112': ['FileSystemFileHandle', '!U2F'], }; const IS_BLINK = browser == 'Chrome'; const IS_GECKO = browser == 'Firefox'; const css = (IS_BLINK ? blinkCSS : IS_GECKO ? geckoCSS : {}); const win = (IS_BLINK ? blinkWindow : IS_GECKO ? geckoWindow : {}); const js = (IS_BLINK ? blinkJS : IS_GECKO ? geckoJS : {}); return { css, win, js, }; }; const getJSCoreFeatures = (win) => { const globalObjects = [ 'Object', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'BigInt', 'Math', 'Date', 'String', 'RegExp', 'Array', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Atomics', 'JSON', 'Promise', 'Reflect', 'Proxy', 'Intl', 'WebAssembly', 'Document', 'Element', ]; try { // @ts-ignore const features = globalObjects.reduce((acc, name) => { const ignore = ['name', 'length', 'constructor', 'prototype', 'arguments', 'caller']; const descriptorKeys = Object.keys(Object.getOwnPropertyDescriptors(win[name] || {})); const descriptorProtoKeys = Object.keys(Object.getOwnPropertyDescriptors((win[name] || {}).prototype || {})); const uniques = [...new Set([...descriptorKeys, ...descriptorProtoKeys].filter((key) => !ignore.includes(key)))]; const keys = uniques.map((key) => `${name}.${key}`); return [...acc, ...keys]; }, []); return features; } catch (error) { console.error(error); return []; } }; // @ts-ignore const versionSort = (x) => x.sort((a, b) => /\d+/.exec(a)[0] - /\d+/.exec(b)[0]).reverse(); const getVersionLie = (vReport, version, forgivenessOffset = 0) => { const stable = getStableFeatures(); const { version: maxVersion } = stable[BROWSER] || {}; const validMetrics = vReport && version; if (!validMetrics) { return {}; } const [vStart, vEnd] = version ? version.split('-') : []; const vMax = (vEnd || vStart); const reportIsTooHigh = +vReport > (+vMax + forgivenessOffset); const reportIsTooLow = +vReport < (+vStart - forgivenessOffset); const reportIsOff = (reportIsTooHigh || reportIsTooLow); const versionIsAboveMax = ((+vMax == maxVersion) && (+vReport > maxVersion)); const liedVersion = !versionIsAboveMax && reportIsOff; const distance = !liedVersion ? 0 : (Math.abs(vReport - (reportIsTooLow ? vStart : vMax))); return { liedVersion, distance }; }; async function getEngineFeatures({ cssComputed, navigatorComputed, windowFeaturesComputed, }) { try { const timer = createTimer(); await queueEvent(timer); const win = PHANTOM_DARKNESS ? PHANTOM_DARKNESS : window; if (!cssComputed || !windowFeaturesComputed) { logTestResult({ test: 'features', passed: false }); return; } const jsFeaturesKeys = getJSCoreFeatures(win); const { keys: computedStyleKeys } = cssComputed.computedStyle || {}; const { keys: windowFeaturesKeys } = windowFeaturesComputed || {}; const { userAgentParsed: decryptedName } = navigatorComputed || {}; const isNative = (win, x) => (/\[native code\]/.test(win[x] + '') && 'prototype' in win[x] && win[x].prototype.constructor.name === x); // @ts-ignore const getFeatures = ({ context, allKeys, engineMap, checkNative = false } = {}) => { const allKeysSet = new Set(allKeys); const features = new Set(); // @ts-ignore const match = Object.keys(engineMap || {}).reduce((acc, key) => { const version = engineMap[key]; const versionLen = version.length; const featureLen = version.filter((prop) => { const removedFromVersion = prop.charAt(0) == '!'; if (removedFromVersion) { const propName = prop.slice(1); return !allKeysSet.has(propName) && features.add(prop); } return (allKeysSet.has(prop) && (checkNative ? isNative(context, prop) : true) && features.add(prop)); }).length; return versionLen == featureLen ? [...acc, key] : acc; }, []); const version = versionSort(match)[0]; return { version, features, }; }; // engine maps const { css: engineMapCSS, win: engineMapWindow, js: engineMapJS, } = getEngineMaps(BROWSER); // css version const { version: cssVersion, features: cssFeatures, } = getFeatures({ context: win, allKeys: computedStyleKeys, engineMap: engineMapCSS, }); // window version const { version: windowVersion, features: windowFeatures, } = getFeatures({ context: win, allKeys: windowFeaturesKeys, engineMap: engineMapWindow, checkNative: true, }); // js version const { version: jsVersion, features: jsFeatures, } = getFeatures({ context: win, allKeys: jsFeaturesKeys, engineMap: engineMapJS, }); // determine version based on 3 factors const getVersionFromRange = (range, versionCollection) => { const exactVersion = versionCollection.find((version) => version && !/-/.test(version)); if (exactVersion) { return exactVersion; } const len = range.length; const first = range[0]; const last = range[len - 1]; return (!len ? '' : len == 1 ? first : `${last}-${first}`); }; const versionSet = new Set([ cssVersion, windowVersion, jsVersion, ]); versionSet.delete(undefined); const versionRange = versionSort([...versionSet].reduce((acc, x) => [...acc, ...x.split('-')], [])); const version = getVersionFromRange(versionRange, [cssVersion, windowVersion, jsVersion]); const vReport = (/\d+/.exec(decryptedName) || [])[0]; const { liedVersion: liedCSS, distance: distanceCSS, } = getVersionLie(vReport, cssVersion); const { liedVersion: liedJS, distance: distanceJS, } = getVersionLie(vReport, jsVersion); const { liedVersion: liedWindow, distance: distanceWindow, } = getVersionLie(vReport, windowVersion); if (liedCSS) { sendToTrash('userAgent', `v${vReport} failed v${cssVersion} CSS features`); if (distanceCSS > 1) { documentLie(`Navigator.userAgent`, `v${vReport} failed CSS features by ${distanceCSS} versions`); } } if (liedJS) { sendToTrash('userAgent', `v${vReport} failed v${jsVersion} JS features`); if (distanceJS > 2) { documentLie(`Navigator.userAgent`, `v${vReport} failed JS features by ${distanceJS} versions`); } } if (liedWindow) { sendToTrash('userAgent', `v${vReport} failed v${windowVersion} Window features`); if (distanceWindow > 3) { documentLie(`Navigator.userAgent`, `v${vReport} failed Window features by ${distanceWindow} versions`); } } logTestResult({ time: timer.stop(), test: 'features', passed: true }); return { versionRange, version, cssVersion, windowVersion, jsVersion, cssFeatures: [...cssFeatures], windowFeatures: [...windowFeatures], jsFeatures: [...jsFeatures], jsFeaturesKeys, }; } catch (error) { logTestResult({ test: 'features', passed: false }); captureError(error); return; } } function featuresHTML(fp) { if (!fp.features) { return `
Features: ${HTMLNote.UNKNOWN}
JS/DOM: ${HTMLNote.UNKNOWN}
CSS: ${HTMLNote.UNKNOWN}
Window: ${HTMLNote.UNKNOWN}
`; } const { versionRange, version, cssVersion, jsVersion, windowVersion, cssFeatures, windowFeatures, jsFeatures, jsFeaturesKeys, } = fp.features || {}; const { keys: windowFeaturesKeys } = fp.windowFeatures || {}; const { keys: computedStyleKeys } = fp.css.computedStyle || {}; const { userAgentVersion } = fp.workerScope || {}; const { css: engineMapCSS, win: engineMapWindow, js: engineMapJS, } = getEngineMaps(BROWSER); // logger const shouldLogFeatures = (browser, version, userAgentVersion) => { const shouldLog = userAgentVersion > version; return shouldLog; }; const log = ({ features, name, diff }) => { console.groupCollapsed(`%c ${name} Features %c-${diff.removed.length} %c+${diff.added.length}`, 'color: #4cc1f9', 'color: Salmon', 'color: MediumAquaMarine'); Object.keys(diff).forEach((key) => { console.log(`%c${key}:`, `color: ${key == 'added' ? 'MediumAquaMarine' : 'Salmon'}`); return console.log(diff[key].join('\n')); }); console.log(features.join(', ')); return console.groupEnd(); }; // modal const report = { computedStyleKeys, windowFeaturesKeys, jsFeaturesKeys }; const getModal = ({ id, engineMap, features, browser, report, userAgentVersion }) => { // capture diffs from stable release const stable = getStableFeatures(); const { windowKeys, cssKeys, jsKeys, version } = stable[browser] || {}; const logger = shouldLogFeatures(browser, version, userAgentVersion); let diff = null; if (id == 'css') { const { computedStyleKeys } = report; if (cssKeys) { diff = getListDiff({ oldList: cssKeys.split(', '), newList: computedStyleKeys, removeCamelCase: true, }); } if (logger) { console.log(`computing ${browser} ${userAgentVersion} diffs from ${browser} ${version}...`); Analysis.featuresCSS = diff; log({ features: computedStyleKeys, name: 'CSS', diff }); } } else if (id == 'window') { const { windowFeaturesKeys } = report; if (windowKeys) { diff = getListDiff({ oldList: windowKeys.split(', '), newList: windowFeaturesKeys, }); } if (logger) { Analysis.featuresWindow = diff; log({ features: windowFeaturesKeys, name: 'Window', diff }); } } else if (id == 'js') { const { jsFeaturesKeys } = report; if (jsKeys) { diff = getListDiff({ oldList: jsKeys.split(', '), newList: jsFeaturesKeys, }); } if (logger) { Analysis.featuresJS = diff; log({ features: jsFeaturesKeys, name: 'JS', diff }); } } const header = !version || !diff || (!diff.added.length && !diff.removed.length) ? '' : ` diffs from ${version}:
${diff && diff.added.length ? diff.added.map((key) => `
${key}
`).join('') : ''} ${diff && diff.removed.length ? diff.removed.map((key) => `
${key}
`).join('') : ''}
`; return modal(`creep-features-${id}`, header + versionSort(Object.keys(engineMap)).map((key) => { return ` ${key}:
${engineMap[key].map((prop) => { return `${prop}`; }).join('
')} `; }).join('
'), hashMini([...features])); }; Analysis.featuresVersion = +userAgentVersion || 0; const cssModal = getModal({ id: 'css', engineMap: engineMapCSS, features: new Set(cssFeatures), browser: BROWSER, report, userAgentVersion, }); const windowModal = getModal({ id: 'window', engineMap: engineMapWindow, features: new Set(windowFeatures), browser: BROWSER, report, userAgentVersion, }); const jsModal = getModal({ id: 'js', engineMap: engineMapJS, features: new Set(jsFeatures), browser: BROWSER, report, userAgentVersion, }); const getIcon = (name) => ``; const browserIcon = (!BROWSER ? '' : /chrome/i.test(BROWSER) ? getIcon('chrome') : /firefox/i.test(BROWSER) ? getIcon('firefox') : ''); return ` ${performanceLogger.getLog().features}
Features: ${versionRange.length ? `${browserIcon}${version}+` : HTMLNote.UNKNOWN}
JS/DOM: ${jsVersion ? `${jsModal} (v${jsVersion})` : HTMLNote.UNKNOWN}
CSS: ${cssVersion ? `${cssModal} (v${cssVersion})` : HTMLNote.UNKNOWN}
Window: ${windowVersion ? `${windowModal} (v${windowVersion})` : HTMLNote.UNKNOWN}
`; } // inspired by Lalit Patel's fontdetect.js // https://www.lalit.org/wordpress/wp-content/uploads/2008/05/fontdetect.js?ver=0.3 const WindowsFonts = { // https://docs.microsoft.com/en-us/typography/fonts/windows_11_font_list '7': [ 'Cambria Math', 'Lucida Console', ], '8': [ 'Aldhabi', 'Gadugi', 'Myanmar Text', 'Nirmala UI', ], '8.1': [ 'Leelawadee UI', 'Javanese Text', 'Segoe UI Emoji', ], '10': [ 'HoloLens MDL2 Assets', // 10 (v1507) + 'Segoe MDL2 Assets', // 10 (v1507) + 'Bahnschrift', // 10 (v1709) +- 'Ink Free', // 10 (v1803) +- ], '11': ['Segoe Fluent Icons'], }; const MacOSFonts = { // Mavericks and below '10.9': [ 'Helvetica Neue', 'Geneva', // mac (not iOS) ], // Yosemite '10.10': [ 'Kohinoor Devanagari Medium', 'Luminari', ], // El Capitan '10.11': [ 'PingFang HK Light', ], // Sierra: https://support.apple.com/en-ie/HT206872 '10.12': [ 'American Typewriter Semibold', 'Futura Bold', 'SignPainter-HouseScript Semibold', ], // High Sierra: https://support.apple.com/en-me/HT207962 // Mojave: https://support.apple.com/en-us/HT208968 '10.13-10.14': [ 'InaiMathi Bold', ], // Catalina: https://support.apple.com/en-us/HT210192 // Big Sur: https://support.apple.com/en-sg/HT211240 '10.15-11': [ 'Galvji', 'MuktaMahee Regular', ], // Monterey: https://support.apple.com/en-us/HT212587 '12': [ 'Noto Sans Gunjala Gondi Regular', 'Noto Sans Masaram Gondi Regular', 'Noto Serif Yezidi Regular' ], // Ventura: https://support.apple.com/en-us/HT213266 '13': [ 'Apple SD Gothic Neo ExtraBold', 'STIX Two Math Regular', 'STIX Two Text Regular', 'Noto Sans Canadian Aboriginal Regular', ], }; const DesktopAppFonts = { // docs.microsoft.com/en-us/typography/font-list/ms-outlook 'Microsoft Outlook': ['MS Outlook'], // https://community.adobe.com/t5/postscript-discussions/zwadobef-font/m-p/3730427#M785 'Adobe Acrobat': ['ZWAdobeF'], // https://wiki.documentfoundation.org/Fonts 'LibreOffice': [ 'Amiri', 'KACSTOffice', 'Liberation Mono', 'Source Code Pro', ], // https://superuser.com/a/611804 'OpenOffice': [ 'DejaVu Sans', 'Gentium Book Basic', 'OpenSymbol', ], }; const APPLE_FONTS = Object.keys(MacOSFonts).map((key) => MacOSFonts[key]).flat(); const WINDOWS_FONTS = Object.keys(WindowsFonts).map((key) => WindowsFonts[key]).flat(); const DESKTOP_APP_FONTS = (Object.keys(DesktopAppFonts).map((key) => DesktopAppFonts[key]).flat()); const LINUX_FONTS = [ 'Arimo', // ubuntu, chrome os 'Chilanka', // ubuntu (not TB) 'Cousine', // ubuntu, chrome os 'Jomolhari', // chrome os 'MONO', // ubuntu, chrome os (not TB) 'Noto Color Emoji', // Linux 'Ubuntu', // ubuntu (not TB) ]; const ANDROID_FONTS = [ 'Dancing Script', // android 'Droid Sans Mono', // Android 'Roboto', // Android, Chrome OS ]; const FONT_LIST = [ ...APPLE_FONTS, ...WINDOWS_FONTS, ...LINUX_FONTS, ...ANDROID_FONTS, ...DESKTOP_APP_FONTS, ].sort(); async function getFonts() { const getPixelEmojis = ({ doc, id, emojis }) => { try { patch(doc.getElementById(id), html `
${emojis.map((emoji) => { return `
${emoji}
`; }).join('')}
`); // get emoji set and system const getEmojiDimensions = (style) => { return { width: style.inlineSize, height: style.blockSize, }; }; const pattern = new Set(); const emojiElems = [...doc.getElementsByClassName('pixel-emoji')]; const emojiSet = emojiElems.reduce((emojiSet, el, i) => { const style = getComputedStyle(el); const emoji = emojis[i]; const { height, width } = getEmojiDimensions(style); const dimensions = `${width},${height}`; if (!pattern.has(dimensions)) { pattern.add(dimensions); emojiSet.add(emoji); } return emojiSet; }, new Set()); const pixelToNumber = (pixels) => +(pixels.replace('px', '')); const pixelSizeSystemSum = 0.00001 * [...pattern].map((x) => { return x.split(',').map((x) => pixelToNumber(x)).reduce((acc, x) => acc += (+x || 0), 0); }).reduce((acc, x) => acc += x, 0); doc.body.removeChild(doc.getElementById('pixel-emoji-container')); return { emojiSet: [...emojiSet], pixelSizeSystemSum, }; } catch (error) { console.error(error); return { emojiSet: [], pixelSizeSystemSum: 0, }; } }; const getFontFaceLoadFonts = async (fontList) => { try { let fontsChecked = []; if (!document.fonts.check(`0px "${getRandomValues()}"`)) { fontsChecked = fontList.reduce((acc, font) => { const found = document.fonts.check(`0px "${font}"`); if (found) acc.push(font); return acc; }, []); } const fontFaceList = fontList.map((font) => new FontFace(font, `local("${font}")`)); const responseCollection = await Promise .allSettled(fontFaceList.map((font) => font.load())); const fontsLoaded = responseCollection.reduce((acc, font) => { if (font.status == 'fulfilled') { acc.push(font.value.family); } return acc; }, []); return [...new Set([...fontsChecked, ...fontsLoaded])].sort(); } catch (error) { console.error(error); return []; } }; const getPlatformVersion = (fonts) => { const getWindows = ({ fonts, fontMap }) => { const fontVersion = { ['11']: fontMap['11'].find((x) => fonts.includes(x)), ['10']: fontMap['10'].find((x) => fonts.includes(x)), ['8.1']: fontMap['8.1'].find((x) => fonts.includes(x)), ['8']: fontMap['8'].find((x) => fonts.includes(x)), // require complete set of Windows 7 fonts ['7']: fontMap['7'].filter((x) => fonts.includes(x)).length == fontMap['7'].length, }; const hash = ('' + Object.keys(fontVersion).sort().filter((key) => !!fontVersion[key])); const hashMap = { '10,11,7,8,8.1': '11', '10,7,8,8.1': '10', '7,8,8.1': '8.1', '11,7,8,8.1': '8.1', // missing 10 '7,8': '8', '10,7,8': '8', // missing 8.1 '10,11,7,8': '8', // missing 8.1 '7': '7', '7,8.1': '7', '10,7,8.1': '7', // missing 8 '10,11,7,8.1': '7', // missing 8 }; const version = hashMap[hash]; return version ? `Windows ${version}` : undefined; }; const getMacOS = ({ fonts, fontMap }) => { const fontVersion = { ['13']: fontMap['13'].find((x) => fonts.includes(x)), ['12']: fontMap['12'].find((x) => fonts.includes(x)), ['10.15-11']: fontMap['10.15-11'].find((x) => fonts.includes(x)), ['10.13-10.14']: fontMap['10.13-10.14'].find((x) => fonts.includes(x)), ['10.12']: fontMap['10.12'].find((x) => fonts.includes(x)), ['10.11']: fontMap['10.11'].find((x) => fonts.includes(x)), ['10.10']: fontMap['10.10'].find((x) => fonts.includes(x)), // require complete set of 10.9 fonts ['10.9']: fontMap['10.9'].filter((x) => fonts.includes(x)).length == fontMap['10.9'].length, }; const hash = ('' + Object.keys(fontVersion).sort().filter((key) => !!fontVersion[key])); const hashMap = { '10.10,10.11,10.12,10.13-10.14,10.15-11,10.9,12,13': 'Ventura', '10.10,10.11,10.12,10.13-10.14,10.15-11,10.9,12': 'Monterey', '10.10,10.11,10.12,10.13-10.14,10.15-11,10.9': '10.15-11', '10.10,10.11,10.12,10.13-10.14,10.9': '10.13-10.14', '10.10,10.11,10.12,10.9': 'Sierra', // 10.12 '10.10,10.11,10.9': 'El Capitan', // 10.11 '10.10,10.9': 'Yosemite', // 10.10 '10.9': 'Mavericks', // 10.9 }; const version = hashMap[hash]; return version ? `macOS ${version}` : undefined; }; return (getWindows({ fonts, fontMap: WindowsFonts }) || getMacOS({ fonts, fontMap: MacOSFonts })); }; const getDesktopApps = (fonts) => { // @ts-ignore const apps = Object.keys(DesktopAppFonts).reduce((acc, key) => { const appFontSet = DesktopAppFonts[key]; const match = appFontSet.filter((x) => fonts.includes(x)).length == appFontSet.length; return match ? [...acc, key] : acc; }, []); return apps; }; try { const timer = createTimer(); await queueEvent(timer); const doc = (PHANTOM_DARKNESS && PHANTOM_DARKNESS.document && PHANTOM_DARKNESS.document.body ? PHANTOM_DARKNESS.document : document); const id = `font-fingerprint`; const div = doc.createElement('div'); div.setAttribute('id', id); doc.body.appendChild(div); const { emojiSet, pixelSizeSystemSum, } = getPixelEmojis({ doc, id, emojis: EMOJIS, }) || {}; const fontList = FONT_LIST; const fontFaceLoadFonts = await getFontFaceLoadFonts(fontList); const platformVersion = getPlatformVersion(fontFaceLoadFonts); const apps = getDesktopApps(fontFaceLoadFonts); // detect lies const lied = (lieProps['FontFace.load'] || lieProps['FontFace.family'] || lieProps['FontFace.status'] || lieProps['String.fromCodePoint'] || lieProps['CSSStyleDeclaration.setProperty'] || lieProps['CSS2Properties.setProperty']) || false; if (isFontOSBad(USER_AGENT_OS, fontFaceLoadFonts)) { LowerEntropy.FONTS = true, Analysis.FontOsIsBad = true; sendToTrash('platform', `${USER_AGENT_OS} system and fonts are uncommon`); } logTestResult({ time: timer.stop(), test: 'fonts', passed: true }); return { fontFaceLoadFonts, platformVersion, apps, emojiSet, pixelSizeSystemSum, lied, }; } catch (error) { logTestResult({ test: 'fonts', passed: false }); captureError(error); return; } } function fontsHTML(fp) { if (!fp.fonts) { return `
Fonts
load (0):
apps:${HTMLNote.BLOCKED}
${HTMLNote.BLOCKED}
${HTMLNote.BLOCKED}
`; } const { fonts: { $hash, fontFaceLoadFonts, platformVersion, apps, emojiSet, pixelSizeSystemSum, lied, }, } = fp; const icon = { 'Linux': '', 'Apple': '', 'Windows': '', 'Android': '', 'CrOS': '', }; const blockHelpTitle = `FontFace.load()\nCSSStyleDeclaration.setProperty()\nblock-size\ninline-size\nhash: ${hashMini(emojiSet)}\n${(emojiSet || []).map((x, i) => i && (i % 6 == 0) ? `${x}\n` : x).join('')}`; return `
${performanceLogger.getLog().fonts} Fonts${hashSlice($hash)}
load (${fontFaceLoadFonts ? count(fontFaceLoadFonts) : '0'}/${'' + FONT_LIST.length}): ${`Like ${platformVersion}` || ((fonts) => { return !(fonts || []).length ? '' : ((('' + fonts).match(/Lucida Console/) || []).length ? `${icon.Windows}Like Windows` : (('' + fonts).match(/Droid Sans Mono|Noto Color Emoji|Roboto/g) || []).length == 3 ? `${icon.Linux}${icon.Android}Like Linux Android` : (('' + fonts).match(/Droid Sans Mono|Roboto/g) || []).length == 2 ? `${icon.Android}Like Android` : (('' + fonts).match(/Noto Color Emoji|Roboto/g) || []).length == 2 ? `${icon.CrOS}Like Chrome OS` : (('' + fonts).match(/Noto Color Emoji/) || []).length ? `${icon.Linux}Like Linux` : (('' + fonts).match(/Arimo/) || []).length ? `${icon.Linux}Like Linux` : (('' + fonts).match(/Helvetica Neue/g) || []).length == 2 ? `${icon.Apple}Like Apple` : `${(fonts || [])[0]}...`); })(fontFaceLoadFonts)}
apps: ${(apps || []).length ? apps.join(', ') : HTMLNote.UNSUPPORTED}
${fontFaceLoadFonts.join(', ') || HTMLNote.UNSUPPORTED}

${pixelSizeSystemSum || HTMLNote.UNSUPPORTED}
${formatEmojiSet(emojiSet)}
`; } function getPlatformEstimate() { if (!IS_BLINK) return []; const v80 = 'getVideoPlaybackQuality' in HTMLVideoElement.prototype; const v81 = CSS.supports('color-scheme: initial'); const v84 = CSS.supports('appearance: initial'); const v86 = 'DisplayNames' in Intl; const v88 = CSS.supports('aspect-ratio: initial'); const v89 = CSS.supports('border-end-end-radius: initial'); const v95 = 'randomUUID' in Crypto.prototype; const hasBarcodeDetector = 'BarcodeDetector' in window; // @ts-expect-error if not supported const hasDownlinkMax = 'downlinkMax' in (window.NetworkInformation?.prototype || {}); const hasContentIndex = 'ContentIndex' in window; const hasContactsManager = 'ContactsManager' in window; const hasEyeDropper = 'EyeDropper' in window; const hasFileSystemWritableFileStream = 'FileSystemWritableFileStream' in window; const hasHid = 'HID' in window && 'HIDDevice' in window; const hasSerialPort = 'SerialPort' in window && 'Serial' in window; const hasSharedWorker = 'SharedWorker' in window; const hasTouch = 'ontouchstart' in Window && 'TouchEvent' in window; const hasAppBadge = 'setAppBadge' in Navigator.prototype; const hasFeature = (version, condition) => { return (version ? [condition] : []); }; const estimate = { ["Android" /* Platform.ANDROID */]: [ ...hasFeature(v88, hasBarcodeDetector), ...hasFeature(v84, hasContentIndex), ...hasFeature(v80, hasContactsManager), hasDownlinkMax, ...hasFeature(v95, !hasEyeDropper), ...hasFeature(v86, !hasFileSystemWritableFileStream), ...hasFeature(v89, !hasHid), ...hasFeature(v89, !hasSerialPort), !hasSharedWorker, hasTouch, ...hasFeature(v81, !hasAppBadge), ], ["Chrome OS" /* Platform.CHROME_OS */]: [ ...hasFeature(v88, hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, hasTouch || !hasTouch, ...hasFeature(v81, !hasAppBadge), ], ["Windows" /* Platform.WINDOWS */]: [ ...hasFeature(v88, !hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), !hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, hasTouch || !hasTouch, ...hasFeature(v81, hasAppBadge), ], ["Mac" /* Platform.MAC */]: [ ...hasFeature(v88, hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), !hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, !hasTouch, ...hasFeature(v81, hasAppBadge), ], ["Linux" /* Platform.LINUX */]: [ ...hasFeature(v88, !hasBarcodeDetector), ...hasFeature(v84, !hasContentIndex), ...hasFeature(v80, !hasContactsManager), !hasDownlinkMax, ...hasFeature(v95, hasEyeDropper), ...hasFeature(v86, hasFileSystemWritableFileStream), ...hasFeature(v89, hasHid), ...hasFeature(v89, hasSerialPort), hasSharedWorker, !hasTouch || !hasTouch, ...hasFeature(v81, !hasAppBadge), ], }; // Chrome only features const headlessEstimate = { noContentIndex: v84 && !hasContentIndex, noContactsManager: v80 && !hasContactsManager, noDownlinkMax: !hasDownlinkMax, }; const scores = Object.keys(estimate).reduce((acc, key) => { const list = estimate[key]; const score = +((list.filter((x) => x).length / list.length).toFixed(2)); acc[key] = score; return acc; }, {}); const platform = Object.keys(scores).reduce((a, b) => scores[a] > scores[b] ? a : b); const highestScore = scores[platform]; return [scores, highestScore, headlessEstimate]; } const SYSTEM_FONTS = [ 'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar', ]; const GeckoFonts = { '-apple-system': "Mac" /* Platform.MAC */, 'Segoe UI': "Windows" /* Platform.WINDOWS */, 'Tahoma': "Windows" /* Platform.WINDOWS */, 'Yu Gothic UI': "Windows" /* Platform.WINDOWS */, 'Microsoft JhengHei UI': "Windows" /* Platform.WINDOWS */, 'Microsoft YaHei UI': "Windows" /* Platform.WINDOWS */, 'Meiryo UI': "Windows" /* Platform.WINDOWS */, 'Cantarell': "Linux" /* Platform.LINUX */, 'Ubuntu': "Linux" /* Platform.LINUX */, 'Sans': "Linux" /* Platform.LINUX */, 'sans-serif': "Linux" /* Platform.LINUX */, 'Fira Sans': "Linux" /* Platform.LINUX */, 'Roboto': "Android" /* Platform.ANDROID */, }; function getSystemFonts() { const { body } = document; const el = document.createElement('div'); body.appendChild(el); try { const systemFonts = String([ ...SYSTEM_FONTS.reduce((acc, font) => { el.setAttribute('style', `font: ${font} !important`); return acc.add(getComputedStyle(el).fontFamily); }, new Set()), ]); const geckoPlatform = GeckoFonts[systemFonts]; return GeckoFonts[systemFonts] ? `${systemFonts}:${geckoPlatform}` : systemFonts; } catch (err) { return ''; } finally { body.removeChild(el); } } /* eslint-disable new-cap */ async function getHeadlessFeatures({ webgl, workerScope, }) { try { const timer = createTimer(); await queueEvent(timer); const mimeTypes = Object.keys({ ...navigator.mimeTypes }); const systemFonts = getSystemFonts(); const [scores, highestScore, headlessEstimate] = getPlatformEstimate(); const data = { chromium: IS_BLINK, likeHeadless: { noChrome: IS_BLINK && !('chrome' in window), hasPermissionsBug: (IS_BLINK && 'permissions' in navigator && await (async () => { const res = await navigator.permissions.query({ name: 'notifications' }); return (res.state == 'prompt' && 'Notification' in window && Notification.permission === 'denied'); })()), noPlugins: IS_BLINK && navigator.plugins.length === 0, noMimeTypes: IS_BLINK && mimeTypes.length === 0, notificationIsDenied: (IS_BLINK && 'Notification' in window && (Notification.permission == 'denied')), hasKnownBgColor: IS_BLINK && (() => { let rendered = PARENT_PHANTOM; if (!PARENT_PHANTOM) { rendered = document.createElement('div'); document.body.appendChild(rendered); } if (!rendered) return false; rendered.setAttribute('style', `background-color: ActiveText`); const { backgroundColor: activeText } = getComputedStyle(rendered) || []; if (!PARENT_PHANTOM) { document.body.removeChild(rendered); } return activeText === 'rgb(255, 0, 0)'; })(), prefersLightColor: matchMedia('(prefers-color-scheme: light)').matches, uaDataIsBlank: ('userAgentData' in navigator && ( // @ts-expect-error if userAgentData is null navigator.userAgentData?.platform === '' || // @ts-expect-error if userAgentData is null await navigator.userAgentData.getHighEntropyValues(['platform']).platform === '')), pdfIsDisabled: ('pdfViewerEnabled' in navigator && navigator.pdfViewerEnabled === false), noTaskbar: (screen.height === screen.availHeight && screen.width === screen.availWidth), hasVvpScreenRes: ((innerWidth === screen.width && outerHeight === screen.height) || ('visualViewport' in window && // @ts-expect-error if unsupported (visualViewport.width === screen.width && visualViewport.height === screen.height))), hasSwiftShader: /SwiftShader/.test(workerScope?.webglRenderer), noWebShare: IS_BLINK && CSS.supports('accent-color: initial') && (!('share' in navigator) || !('canShare' in navigator)), noContentIndex: !!headlessEstimate?.noContentIndex, noContactsManager: !!headlessEstimate?.noContactsManager, noDownlinkMax: !!headlessEstimate?.noDownlinkMax, }, headless: { webDriverIsOn: ((CSS.supports('border-end-end-radius: initial') && navigator.webdriver === undefined) || !!navigator.webdriver || !!lieProps['Navigator.webdriver']), hasHeadlessUA: (/HeadlessChrome/.test(navigator.userAgent) || /HeadlessChrome/.test(navigator.appVersion)), hasHeadlessWorkerUA: !!workerScope && (/HeadlessChrome/.test(workerScope.userAgent)), }, stealth: { hasIframeProxy: (() => { try { const iframe = document.createElement('iframe'); iframe.srcdoc = instanceId; return !!iframe.contentWindow; } catch (err) { return true; } })(), hasHighChromeIndex: (() => { const key = 'chrome'; const highIndexRange = -50; return (Object.keys(window).slice(highIndexRange).includes(key) && Object.getOwnPropertyNames(window).slice(highIndexRange).includes(key)); })(), hasBadChromeRuntime: (() => { // @ts-expect-error if unsupported if (!('chrome' in window && 'runtime' in chrome)) { return false; } try { // @ts-expect-error if unsupported if ('prototype' in chrome.runtime.sendMessage || // @ts-expect-error if unsupported 'prototype' in chrome.runtime.connect) { return true; } // @ts-expect-error if unsupported new chrome.runtime.sendMessage; // @ts-expect-error if unsupported new chrome.runtime.connect; return true; } catch (err) { return err.constructor.name != 'TypeError' ? true : false; } })(), hasToStringProxy: (!!lieProps['Function.toString']), hasBadWebGL: (() => { const { UNMASKED_RENDERER_WEBGL: gpu } = webgl?.parameters || {}; const { webglRenderer: workerGPU } = workerScope || {}; return (gpu && workerGPU && (gpu !== workerGPU)); })(), }, }; const { likeHeadless, headless, stealth } = data; const likeHeadlessKeys = Object.keys(likeHeadless); const headlessKeys = Object.keys(headless); const stealthKeys = Object.keys(stealth); const likeHeadlessRating = +((likeHeadlessKeys.filter((key) => likeHeadless[key]).length / likeHeadlessKeys.length) * 100).toFixed(0); const headlessRating = +((headlessKeys.filter((key) => headless[key]).length / headlessKeys.length) * 100).toFixed(0); const stealthRating = +((stealthKeys.filter((key) => stealth[key]).length / stealthKeys.length) * 100).toFixed(0); logTestResult({ time: timer.stop(), test: 'headless', passed: true }); return { ...data, likeHeadlessRating, headlessRating, stealthRating, systemFonts, platformEstimate: [scores, highestScore], }; } catch (error) { logTestResult({ test: 'headless', passed: false }); captureError(error); return; } } function headlessFeaturesHTML(fp) { if (!fp.headless) { return `
Headless
chromium: ${HTMLNote.BLOCKED}
0% like headless: ${HTMLNote.BLOCKED}
0% headless: ${HTMLNote.BLOCKED}
0% stealth: ${HTMLNote.BLOCKED}
platform hints:
${HTMLNote.BLOCKED}
`; } const { headless: data, } = fp; const { $hash, chromium, likeHeadless, likeHeadlessRating, headless, headlessRating, stealth, stealthRating, systemFonts, platformEstimate, } = data || {}; const [scores, highestScore] = platformEstimate || []; const IconMap = { ["Android" /* Platform.ANDROID */]: ``, ["Chrome OS" /* Platform.CHROME_OS */]: ``, ["Windows" /* Platform.WINDOWS */]: ``, ["Mac" /* Platform.MAC */]: ``, ["Linux" /* Platform.LINUX */]: ``, }; const scoreKeys = Object.keys(scores || {}); const platformTemplate = !scores ? '' : ` ${scoreKeys.map((key) => (scores[key] * 100).toFixed(0)).join(':')}
${scoreKeys.map((key) => { const score = scores[key]; const style = ` filter: opacity(${score == highestScore ? 100 : 15}%); `; return `${IconMap[key]}`; }).join('')} `; return `
${performanceLogger.getLog().headless} Headless${hashSlice($hash)}
chromium: ${'' + chromium}
${'' + likeHeadlessRating}% like headless: ${modal('creep-like-headless', 'Like Headless

' + Object.keys(likeHeadless).map((key) => `${key}: ${'' + likeHeadless[key]}`).join('
'), hashMini(likeHeadless))}
${'' + headlessRating}% headless: ${modal('creep-headless', 'Headless

' + Object.keys(headless).map((key) => `${key}: ${'' + headless[key]}`).join('
'), hashMini(headless))}
${'' + stealthRating}% stealth: ${modal('creep-stealth', 'Stealth

' + Object.keys(stealth).map((key) => `${key}: ${'' + stealth[key]}`).join('
'), hashMini(stealth))}
platform hints:
${systemFonts ? `
${systemFonts}
` : ''} ${platformTemplate ? `
${platformTemplate}
` : ''}
`; } async function getIntl() { const getLocale = (intl) => { const constructors = [ 'Collator', 'DateTimeFormat', 'DisplayNames', 'ListFormat', 'NumberFormat', 'PluralRules', 'RelativeTimeFormat', ]; // @ts-ignore const locale = constructors.reduce((acc, name) => { try { const obj = new intl[name]; if (!obj) { return acc; } const { locale } = obj.resolvedOptions() || {}; return [...acc, locale]; } catch (error) { return acc; } }, []); return [...new Set(locale)]; }; try { const timer = createTimer(); await queueEvent(timer); const lied = (lieProps['Intl.Collator.resolvedOptions'] || lieProps['Intl.DateTimeFormat.resolvedOptions'] || lieProps['Intl.DisplayNames.resolvedOptions'] || lieProps['Intl.ListFormat.resolvedOptions'] || lieProps['Intl.NumberFormat.resolvedOptions'] || lieProps['Intl.PluralRules.resolvedOptions'] || lieProps['Intl.RelativeTimeFormat.resolvedOptions']) || false; const dateTimeFormat = caniuse(() => { return new Intl.DateTimeFormat(undefined, { month: 'long', timeZoneName: 'long', }).format(963644400000); }); const displayNames = caniuse(() => { return new Intl.DisplayNames(undefined, { type: 'language', }).of('en-US'); }); const listFormat = caniuse(() => { // @ts-ignore return new Intl.ListFormat(undefined, { style: 'long', type: 'disjunction', }).format(['0', '1']); }); const numberFormat = caniuse(() => { return new Intl.NumberFormat(undefined, { notation: 'compact', compactDisplay: 'long', }).format(21000000); }); const pluralRules = caniuse(() => { return new Intl.PluralRules().select(1); }); const relativeTimeFormat = caniuse(() => { return new Intl.RelativeTimeFormat(undefined, { localeMatcher: 'best fit', numeric: 'auto', style: 'long', }).format(1, 'year'); }); const locale = getLocale(Intl); logTestResult({ time: timer.stop(), test: 'intl', passed: true }); return { dateTimeFormat, displayNames, listFormat, numberFormat, pluralRules, relativeTimeFormat, locale: '' + locale, lied, }; } catch (error) { logTestResult({ test: 'intl', passed: false }); captureError(error); return; } } function intlHTML(fp) { if (!fp.htmlElementVersion) { return `
Intl
locale: ${HTMLNote.Blocked}
date: ${HTMLNote.Blocked}
display: ${HTMLNote.Blocked}
list: ${HTMLNote.Blocked}
number: ${HTMLNote.Blocked}
plural: ${HTMLNote.Blocked}
relative: ${HTMLNote.Blocked}
`; } const { $hash, dateTimeFormat, displayNames, listFormat, numberFormat, pluralRules, relativeTimeFormat, locale, lied, } = fp.intl || {}; return `
${performanceLogger.getLog().intl} Intl${hashSlice($hash)}
${[ locale, dateTimeFormat, displayNames, numberFormat, relativeTimeFormat, listFormat, pluralRules, ].join('
')}
`; } function getMaths() { try { const timer = createTimer(); timer.start(); // detect failed math equality lie const check = [ 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh', 'atan2', 'cbrt', 'cos', 'cosh', 'expm1', 'exp', 'hypot', 'log', 'log1p', 'log10', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'pow', ]; let lied = false; check.forEach((prop) => { if (!!lieProps[`Math.${prop}`]) { lied = true; } const test = (prop == 'cos' ? [1e308] : prop == 'acos' || prop == 'asin' || prop == 'atanh' ? [0.5] : prop == 'pow' || prop == 'atan2' ? [Math.PI, 2] : [Math.PI]); const res1 = Math[prop](...test); const res2 = Math[prop](...test); const matching = isNaN(res1) && isNaN(res2) ? true : res1 == res2; if (!matching) { lied = true; const mathLie = `expected x and got y`; documentLie(`Math.${prop}`, mathLie); } return; }); const n = 0.123; const bigN = 5.860847362277284e+38; const fns = [ ['acos', [n], `acos(${n})`, 1.4474840516030247, NaN, NaN, 1.4474840516030245], ['acos', [Math.SQRT1_2], 'acos(Math.SQRT1_2)', 0.7853981633974483, NaN, NaN, NaN], ['acosh', [1e308], 'acosh(1e308)', 709.889355822726, NaN, NaN, NaN], ['acosh', [Math.PI], 'acosh(Math.PI)', 1.811526272460853, NaN, NaN, NaN], ['acosh', [Math.SQRT2], 'acosh(Math.SQRT2)', 0.881373587019543, NaN, NaN, 0.8813735870195432], ['asin', [n], `asin(${n})`, 0.12331227519187199, NaN, NaN, NaN], ['asinh', [1e300], 'asinh(1e308)', 691.4686750787736, NaN, NaN, NaN], ['asinh', [Math.PI], 'asinh(Math.PI)', 1.8622957433108482, NaN, NaN, NaN], ['atan', [2], 'atan(2)', 1.1071487177940904, NaN, NaN, 1.1071487177940906], ['atan', [Math.PI], 'atan(Math.PI)', 1.2626272556789115, NaN, NaN, NaN], ['atanh', [0.5], 'atanh(0.5)', 0.5493061443340548, NaN, NaN, 0.5493061443340549], ['atan2', [1e-310, 2], 'atan2(1e-310, 2)', 5e-311, NaN, NaN, NaN], ['atan2', [Math.PI, 2], 'atan2(Math.PI)', 1.0038848218538872, NaN, NaN, NaN], ['cbrt', [100], 'cbrt(100)', 4.641588833612779, NaN, NaN, NaN], ['cbrt', [Math.PI], 'cbrt(Math.PI)', 1.4645918875615231, NaN, NaN, 1.4645918875615234], ['cos', [n], `cos(${n})`, 0.9924450321351935, NaN, NaN, NaN], ['cos', [Math.PI], 'cos(Math.PI)', -1, NaN, NaN, NaN], ['cos', [bigN], `cos(${bigN})`, -0.10868049424995659, NaN, -0.9779661551196617, NaN], ['cos', [-1e308], 'cos(-1e308)', -0.8913089376870335, NaN, 0.99970162388838, NaN], ['cos', [13 * Math.E], 'cos(13*Math.E)', -0.7108118501064331, -0.7108118501064332, NaN, NaN], ['cos', [57 * Math.E], 'cos(57*Math.E)', -0.536911695749024, -0.5369116957490239, NaN, NaN], ['cos', [21 * Math.LN2], 'cos(21*Math.LN2)', -0.4067775970251724, -0.40677759702517235, -0.6534063185820197, NaN], ['cos', [51 * Math.LN2], 'cos(51*Math.LN2)', -0.7017203400855446, -0.7017203400855445, NaN, NaN], ['cos', [21 * Math.LOG2E], 'cos(21*Math.LOG2E)', 0.4362848063618998, 0.43628480636189976, NaN, NaN], ['cos', [25 * Math.SQRT2], 'cos(25*Math.SQRT2)', -0.6982689820462377, -0.6982689820462376, NaN, NaN], ['cos', [50 * Math.SQRT1_2], 'cos(50*Math.SQRT1_2)', -0.6982689820462377, -0.6982689820462376, NaN, NaN], ['cos', [21 * Math.SQRT1_2], 'cos(21*Math.SQRT1_2)', -0.6534063185820198, NaN, NaN, NaN], ['cos', [17 * Math.LOG10E], 'cos(17*Math.LOG10E)', 0.4537557425982784, 0.45375574259827833, NaN, NaN], ['cos', [2 * Math.LOG10E], 'cos(2*Math.LOG10E)', 0.6459044007438142, NaN, 0.6459044007438141, NaN], ['cosh', [1], 'cosh(1)', 1.5430806348152437, NaN, NaN, NaN], ['cosh', [Math.PI], 'cosh(Math.PI)', 11.591953275521519, NaN, NaN, NaN], ['cosh', [492 * Math.LOG2E], 'cosh(492*Math.LOG2E)', 9.199870313877772e+307, 9.199870313877774e+307, NaN, NaN], ['cosh', [502 * Math.SQRT2], 'cosh(502*Math.SQRT2)', 1.0469199669023138e+308, 1.046919966902314e+308, NaN, NaN], ['expm1', [1], 'expm1(1)', 1.718281828459045, NaN, NaN, 1.7182818284590453], ['expm1', [Math.PI], 'expm1(Math.PI)', 22.140692632779267, NaN, NaN, NaN], ['exp', [n], `exp(${n})`, 1.1308844209474893, NaN, NaN, NaN], ['exp', [Math.PI], 'exp(Math.PI)', 23.140692632779267, NaN, NaN, NaN], ['hypot', [1, 2, 3, 4, 5, 6], 'hypot(1, 2, 3, 4, 5, 6)', 9.539392014169456, NaN, NaN, NaN], ['hypot', [bigN, bigN], `hypot(${bigN}, ${bigN})`, 8.288489826731116e+38, 8.288489826731114e+38, NaN, NaN], ['hypot', [2 * Math.E, -100], 'hypot(2*Math.E, -100)', 100.14767208675259, 100.14767208675258, NaN, NaN], ['hypot', [6 * Math.PI, -100], 'hypot(6*Math.PI, -100)', 101.76102278593319, 101.7610227859332, NaN, NaN], ['hypot', [2 * Math.LN2, -100], 'hypot(2*Math.LN2, -100)', 100.0096085986525, 100.00960859865252, NaN, NaN], ['hypot', [Math.LOG2E, -100], 'hypot(Math.LOG2E, -100)', 100.01040630344929, 100.01040630344927, NaN, NaN], ['hypot', [Math.SQRT2, -100], 'hypot(Math.SQRT2, -100)', 100.00999950004999, 100.00999950005, NaN, NaN], ['hypot', [Math.SQRT1_2, -100], 'hypot(Math.SQRT1_2, -100)', 100.0024999687508, 100.00249996875078, NaN, NaN], ['hypot', [2 * Math.LOG10E, -100], 'hypot(2*Math.LOG10E, -100)', 100.00377216279416, 100.00377216279418, NaN, NaN], ['log', [n], `log(${n})`, -2.0955709236097197, NaN, NaN, NaN], ['log', [Math.PI], 'log(Math.PI)', 1.1447298858494002, NaN, NaN, NaN], ['log1p', [n], `log1p(${n})`, 0.11600367575630613, NaN, NaN, NaN], ['log1p', [Math.PI], 'log1p(Math.PI)', 1.4210804127942926, NaN, NaN, NaN], ['log10', [n], `log10(${n})`, -0.9100948885606021, NaN, NaN, NaN], ['log10', [Math.PI], 'log10(Math.PI)', 0.4971498726941338, 0.49714987269413385, NaN, NaN], ['log10', [Math.E], 'log10(Math.E)', 0.4342944819032518, NaN, NaN, NaN], ['log10', [34 * Math.E], 'log10(34*Math.E)', 1.9657733989455068, 1.965773398945507, NaN, NaN], ['log10', [Math.LN2], 'log10(Math.LN2)', -0.1591745389548616, NaN, NaN, NaN], ['log10', [11 * Math.LN2], 'log10(11*Math.LN2)', 0.8822181462033634, 0.8822181462033635, NaN, NaN], ['log10', [Math.LOG2E], 'log10(Math.LOG2E)', 0.15917453895486158, NaN, NaN, NaN], ['log10', [43 * Math.LOG2E], 'log10(43*Math.LOG2E)', 1.792642994534448, 1.7926429945344482, NaN, NaN], ['log10', [Math.LOG10E], 'log10(Math.LOG10E)', -0.36221568869946325, NaN, NaN, NaN], ['log10', [7 * Math.LOG10E], 'log10(7*Math.LOG10E)', 0.4828823513147936, 0.48288235131479357, NaN, NaN], ['log10', [Math.SQRT1_2], 'log10(Math.SQRT1_2)', -0.15051499783199057, NaN, NaN, NaN], ['log10', [2 * Math.SQRT1_2], 'log10(2*Math.SQRT1_2)', 0.1505149978319906, 0.15051499783199063, NaN, NaN], ['log10', [Math.SQRT2], 'log10(Math.SQRT2)', 0.1505149978319906, 0.15051499783199063, NaN, NaN], ['sin', [bigN], `sin(${bigN})`, 0.994076732536068, NaN, -0.20876350121720488, NaN], ['sin', [Math.PI], 'sin(Math.PI)', 1.2246467991473532e-16, NaN, 1.2246063538223773e-16, NaN], ['sin', [39 * Math.E], 'sin(39*Math.E)', -0.7181630308570677, -0.7181630308570678, NaN, NaN], ['sin', [35 * Math.LN2], 'sin(35*Math.LN2)', -0.7659964138980511, -0.765996413898051, NaN, NaN], ['sin', [110 * Math.LOG2E], 'sin(110*Math.LOG2E)', 0.9989410140273756, 0.9989410140273757, NaN, NaN], ['sin', [7 * Math.LOG10E], 'sin(7*Math.LOG10E)', 0.10135692924965616, 0.10135692924965614, NaN, NaN], ['sin', [35 * Math.SQRT1_2], 'sin(35*Math.SQRT1_2)', -0.3746357547858202, -0.37463575478582023, NaN, NaN], ['sin', [21 * Math.SQRT2], 'sin(21*Math.SQRT2)', -0.9892668187780498, -0.9892668187780497, NaN, NaN], ['sinh', [1], 'sinh(1)', 1.1752011936438014, NaN, NaN, NaN], ['sinh', [Math.PI], 'sinh(Math.PI)', 11.548739357257748, NaN, NaN, 11.548739357257746], ['sinh', [Math.E], 'sinh(Math.E)', 7.544137102816975, NaN, NaN, NaN], ['sinh', [Math.LN2], 'sinh(Math.LN2)', 0.75, NaN, NaN, NaN], ['sinh', [Math.LOG2E], 'sinh(Math.LOG2E)', 1.9978980091062795, NaN, NaN, NaN], ['sinh', [492 * Math.LOG2E], 'sinh(492*Math.LOG2E)', 9.199870313877772e+307, 9.199870313877774e+307, NaN, NaN], ['sinh', [Math.LOG10E], 'sinh(Math.LOG10E)', 0.44807597941469024, NaN, NaN, NaN], ['sinh', [Math.SQRT1_2], 'sinh(Math.SQRT1_2)', 0.7675231451261164, NaN, NaN, NaN], ['sinh', [Math.SQRT2], 'sinh(Math.SQRT2)', 1.935066822174357, NaN, NaN, 1.9350668221743568], ['sinh', [502 * Math.SQRT2], 'sinh(502*Math.SQRT2)', 1.0469199669023138e+308, 1.046919966902314e+308, NaN, NaN], ['sqrt', [n], `sqrt(${n})`, 0.3507135583350036, NaN, NaN, NaN], ['sqrt', [Math.PI], 'sqrt(Math.PI)', 1.7724538509055159, NaN, NaN, NaN], ['tan', [-1e308], 'tan(-1e308)', 0.5086861259107568, NaN, NaN, 0.5086861259107567], ['tan', [Math.PI], 'tan(Math.PI)', -1.2246467991473532e-16, NaN, NaN, NaN], ['tan', [6 * Math.E], 'tan(6*Math.E)', 0.6866761546452431, 0.686676154645243, NaN, NaN], ['tan', [6 * Math.LN2], 'tan(6*Math.LN2)', 1.6182817135715877, 1.618281713571588, NaN, 1.6182817135715875], ['tan', [10 * Math.LOG2E], 'tan(10*Math.LOG2E)', -3.3537128705376014, -3.353712870537601, NaN, -3.353712870537602], ['tan', [17 * Math.SQRT2], 'tan(17*Math.SQRT2)', -1.9222955461799982, -1.922295546179998, NaN, NaN], ['tan', [34 * Math.SQRT1_2], 'tan(34*Math.SQRT1_2)', -1.9222955461799982, -1.922295546179998, NaN, NaN], ['tan', [10 * Math.LOG10E], 'tan(10*Math.LOG10E)', 2.5824856130712432, 2.5824856130712437, NaN, NaN], ['tanh', [n], `tanh(${n})`, 0.12238344189440875, NaN, NaN, 0.12238344189440876], ['tanh', [Math.PI], 'tanh(Math.PI)', 0.99627207622075, NaN, NaN, NaN], ['pow', [n, -100], `pow(${n}, -100)`, 1.022089333584519e+91, 1.0220893335845176e+91, NaN, NaN], ['pow', [Math.PI, -100], 'pow(Math.PI, -100)', 1.9275814160560204e-50, 1.9275814160560185e-50, NaN, 1.9275814160560206e-50], ['pow', [Math.E, -100], 'pow(Math.E, -100)', 3.7200759760208555e-44, 3.720075976020851e-44, NaN, NaN], ['pow', [Math.LN2, -100], 'pow(Math.LN2, -100)', 8269017203802394, 8269017203802410, NaN, NaN], ['pow', [Math.LN10, -100], 'pow(Math.LN10, -100)', 6.003867926738829e-37, 6.003867926738811e-37, NaN, NaN], ['pow', [Math.LOG2E, -100], 'pow(Math.LOG2E, -100)', 1.20933355845501e-16, 1.2093335584550061e-16, NaN, NaN], ['pow', [Math.LOG10E, -100], 'pow(Math.LOG10E, -100)', 1.6655929347585958e+36, 1.665592934758592e+36, NaN, 1.6655929347585955e+36], ['pow', [Math.SQRT1_2, -100], 'pow(Math.SQRT1_2, -100)', 1125899906842616.2, 1125899906842611.5, NaN, NaN], ['pow', [Math.SQRT2, -100], 'pow(Math.SQRT2, -100)', 8.881784197001191e-16, 8.881784197001154e-16, NaN, NaN], ['polyfill', [2e-3 ** -100], 'polyfill pow(2e-3, -100)', 7.888609052210102e+269, 7.888609052210126e+269, NaN, NaN], ]; const data = {}; fns.forEach((fn) => { data[fn[2]] = attempt(() => { // @ts-ignore const result = fn[0] != 'polyfill' ? Math[fn[0]](...fn[1]) : fn[1]; const chrome = result == fn[3]; const firefox = fn[4] ? result == fn[4] : false; const torBrowser = fn[5] ? result == fn[5] : false; const safari = fn[6] ? result == fn[6] : false; return { result, chrome, firefox, torBrowser, safari }; }); }); logTestResult({ time: timer.stop(), test: 'math', passed: true }); return { data, lied }; } catch (error) { logTestResult({ test: 'math', passed: false }); captureError(error); return; } } function mathsHTML(fp) { if (!fp.maths) { return `
Math
results: ${HTMLNote.Blocked}
${HTMLNote.Blocked}
`; } const { maths: { data, $hash, lied, }, } = fp; const header = `

C - Chromium
F - Firefox
T - Tor Browser
S - Safari
`; const results = Object.keys(data).map((key) => { const value = data[key]; const { chrome, firefox, torBrowser, safari } = value; return ` ${chrome ? 'C' : '-'}${firefox ? 'F' : '-'}${torBrowser ? 'T' : '-'}${safari ? 'S' : '-'} ${key}`; }); return `
${performanceLogger.getLog().math} Math${hashSlice($hash)}
results: ${!data ? HTMLNote.Blocked : modal('creep-maths', header + results.join('
'))}
`; } // inspired by // - https://privacycheck.sec.lrz.de/active/fp_cpt/fp_can_play_type.html // - https://arkenfox.github.io/TZP const getMimeTypeShortList = () => [ 'audio/ogg; codecs="vorbis"', 'audio/mpeg', 'audio/mpegurl', 'audio/wav; codecs="1"', 'audio/x-m4a', 'audio/aac', 'video/ogg; codecs="theora"', 'video/quicktime', 'video/mp4; codecs="avc1.42E01E"', 'video/webm; codecs="vp8"', 'video/webm; codecs="vp9"', 'video/x-matroska', ].sort(); async function getMedia() { const getMimeTypes = () => { try { const mimeTypes = getMimeTypeShortList(); const videoEl = document.createElement('video'); const audioEl = new Audio(); const isMediaRecorderSupported = 'MediaRecorder' in window; const types = mimeTypes.reduce((acc, type) => { const data = { mimeType: type, audioPlayType: audioEl.canPlayType(type), videoPlayType: videoEl.canPlayType(type), mediaSource: MediaSource.isTypeSupported(type), mediaRecorder: isMediaRecorderSupported ? MediaRecorder.isTypeSupported(type) : false, }; if (!data.audioPlayType && !data.videoPlayType && !data.mediaSource && !data.mediaRecorder) { return acc; } // @ts-ignore acc.push(data); return acc; }, []); return types; } catch (error) { return; } }; try { const timer = createTimer(); timer.start(); const mimeTypes = getMimeTypes(); logTestResult({ time: timer.stop(), test: 'media', passed: true }); return { mimeTypes }; } catch (error) { logTestResult({ test: 'media', passed: false }); captureError(error); return; } } function mediaHTML(fp) { if (!fp.media) { return `
Media
mimes (0): ${HTMLNote.BLOCKED}
`; } const { media: { mimeTypes, $hash, }, } = fp; const header = `

audioPlayType
videoPlayType
mediaSource
mediaRecorder
P (Probably)
M (Maybe)
T (True)
`; const invalidMimeTypes = !mimeTypes || !mimeTypes.length; const mimes = invalidMimeTypes ? undefined : mimeTypes.map((type) => { const { mimeType, audioPlayType, videoPlayType, mediaSource, mediaRecorder } = type; return ` ${audioPlayType == 'probably' ? 'P' : audioPlayType == 'maybe' ? 'M' : '-'}${videoPlayType == 'probably' ? 'P' : videoPlayType == 'maybe' ? 'M' : '-'}${mediaSource ? 'T' : '-'}${mediaRecorder ? 'T' : '-'}: ${mimeType} `; }); const mimesListLen = getMimeTypeShortList().length; return `
${performanceLogger.getLog().media} Media${hashSlice($hash)}
mimes (${count(mimeTypes)}/${mimesListLen}): ${invalidMimeTypes ? HTMLNote.BLOCKED : modal('creep-media-mimeTypes', header + mimes.join('
'), hashMini(mimeTypes))}
`; } // special thanks to https://arh.antoinevastel.com for inspiration async function getNavigator(workerScope) { try { const timer = createTimer(); await queueEvent(timer); let lied = (lieProps['Navigator.appVersion'] || lieProps['Navigator.deviceMemory'] || lieProps['Navigator.doNotTrack'] || lieProps['Navigator.hardwareConcurrency'] || lieProps['Navigator.language'] || lieProps['Navigator.languages'] || lieProps['Navigator.maxTouchPoints'] || lieProps['Navigator.oscpu'] || lieProps['Navigator.platform'] || lieProps['Navigator.userAgent'] || lieProps['Navigator.vendor'] || lieProps['Navigator.plugins'] || lieProps['Navigator.mimeTypes']) || false; const credibleUserAgent = ('chrome' in window ? navigator.userAgent.includes(navigator.appVersion) : true); const data = { platform: attempt(() => { const { platform } = navigator; const systems = ['win', 'linux', 'mac', 'arm', 'pike', 'linux', 'iphone', 'ipad', 'ipod', 'android', 'x11']; const trusted = typeof platform == 'string' && systems.filter((val) => platform.toLowerCase().includes(val))[0]; if (!trusted) { sendToTrash(`platform`, `${platform} is unusual`); } // user agent os lie if (USER_AGENT_OS !== PLATFORM_OS) { lied = true; documentLie(`Navigator.platform`, `${PLATFORM_OS} platform and ${USER_AGENT_OS} user agent do not match`); } if (platform != workerScope.platform) { lied = true; // documented in the worker source } return platform; }), system: attempt(() => getOS(navigator.userAgent), 'userAgent system failed'), userAgentParsed: await attempt(async () => { const reportedUserAgent = caniuse(() => navigator.userAgent); const reportedSystem = getOS(reportedUserAgent); const isBrave = await braveBrowser(); const report = decryptUserAgent({ ua: reportedUserAgent, os: reportedSystem, isBrave, }); return report; }), device: attempt(() => getUserAgentPlatform({ userAgent: navigator.userAgent }), 'userAgent device failed'), userAgent: attempt(() => { const { userAgent } = navigator; if (!credibleUserAgent) { sendToTrash('userAgent', `${userAgent} does not match appVersion`); } if (/\s{2,}|^\s|\s$/g.test(userAgent)) { sendToTrash('userAgent', `extra spaces detected`); } const gibbers = gibberish(userAgent); if (!!gibbers.length) { sendToTrash(`userAgent is gibberish`, userAgent); } if (userAgent != workerScope.userAgent) { lied = true; // documented in the worker source } return userAgent.trim().replace(/\s{2,}/, ' '); }, 'userAgent failed'), uaPostReduction: isUAPostReduction((navigator || {}).userAgent), appVersion: attempt(() => { const { appVersion } = navigator; if (!credibleUserAgent) { sendToTrash('appVersion', `${appVersion} does not match userAgent`); } if ('appVersion' in navigator && !appVersion) { sendToTrash('appVersion', 'Living Standard property returned falsy value'); } if (/\s{2,}|^\s|\s$/g.test(appVersion)) { sendToTrash('appVersion', `extra spaces detected`); } return appVersion.trim().replace(/\s{2,}/, ' '); }, 'appVersion failed'), deviceMemory: attempt(() => { if (!('deviceMemory' in navigator)) { return undefined; } // @ts-ignore const { deviceMemory } = navigator; const trusted = { '0.25': true, '0.5': true, '1': true, '2': true, '4': true, '8': true, '16': true, '32': true, }; if (!trusted[deviceMemory]) { sendToTrash('deviceMemory', `${deviceMemory} is not a valid value [0.25, 0.5, 1, 2, 4, 8, 16, 32]`); } // @ts-expect-error memory is undefined if not supported const memory = performance?.memory?.jsHeapSizeLimit || null; const memoryInGigabytes = memory ? +(memory / 1073741824).toFixed(1) : 0; if (memoryInGigabytes > deviceMemory) { sendToTrash('deviceMemory', `available memory ${memoryInGigabytes}GB is greater than device memory ${deviceMemory}GB`); } if (deviceMemory !== workerScope.deviceMemory) { lied = true; // documented in the worker source } return deviceMemory; }, 'deviceMemory failed'), doNotTrack: attempt(() => { const { doNotTrack } = navigator; const trusted = { '1': !0, 'true': !0, 'yes': !0, '0': !0, 'false': !0, 'no': !0, 'unspecified': !0, 'null': !0, 'undefined': !0, }; if (!trusted[doNotTrack]) { sendToTrash('doNotTrack - unusual result', doNotTrack); } return doNotTrack; }, 'doNotTrack failed'), globalPrivacyControl: attempt(() => { if (!('globalPrivacyControl' in navigator)) { return undefined; } // @ts-ignore const { globalPrivacyControl } = navigator; const trusted = { '1': !0, 'true': !0, 'yes': !0, '0': !0, 'false': !0, 'no': !0, 'unspecified': !0, 'null': !0, 'undefined': !0, }; if (!trusted[globalPrivacyControl]) { sendToTrash('globalPrivacyControl - unusual result', globalPrivacyControl); } return globalPrivacyControl; }, 'globalPrivacyControl failed'), hardwareConcurrency: attempt(() => { if (!('hardwareConcurrency' in navigator)) { return undefined; } const { hardwareConcurrency } = navigator; if (hardwareConcurrency !== workerScope.hardwareConcurrency) { lied = true; // documented in the worker source } return hardwareConcurrency; }, 'hardwareConcurrency failed'), language: attempt(() => { const { language, languages } = navigator; if (language && languages) { // @ts-ignore const lang = /^.{0,2}/g.exec(language)[0]; // @ts-ignore const langs = /^.{0,2}/g.exec(languages[0])[0]; if (langs != lang) { sendToTrash('language/languages', `${[language, languages].join(' ')} mismatch`); } return `${languages.join(', ')} (${language})`; } if (language != workerScope.language) { lied = true; documentLie(`Navigator.language`, `${language} does not match worker scope`); } if (languages !== workerScope.languages) { lied = true; documentLie(`Navigator.languages`, `${languages} does not match worker scope`); } return `${language} ${languages}`; }, 'language(s) failed'), maxTouchPoints: attempt(() => { if (!('maxTouchPoints' in navigator)) { return null; } return navigator.maxTouchPoints; }, 'maxTouchPoints failed'), vendor: attempt(() => navigator.vendor, 'vendor failed'), mimeTypes: attempt(() => { const { mimeTypes } = navigator; return mimeTypes ? [...mimeTypes].map((m) => m.type) : []; }, 'mimeTypes failed'), // @ts-ignore oscpu: attempt(() => navigator.oscpu, 'oscpu failed'), plugins: attempt(() => { // https://html.spec.whatwg.org/multipage/system-state.html#pdf-viewing-support const { plugins } = navigator; if (!(plugins instanceof PluginArray)) { return; } const response = plugins ? [...plugins] .map((p) => ({ name: p.name, description: p.description, filename: p.filename, // @ts-ignore version: p.version, })) : []; const { lies } = getPluginLies(plugins, navigator.mimeTypes); if (lies.length) { lied = true; lies.forEach((lie) => { return documentLie(`Navigator.plugins`, lie); }); } if (response.length) { response.forEach((plugin) => { const { name, description } = plugin; const nameGibbers = gibberish(name); const descriptionGibbers = gibberish(description); if (nameGibbers.length) { sendToTrash(`plugin name is gibberish`, name); } if (descriptionGibbers.length) { sendToTrash(`plugin description is gibberish`, description); } return; }); } return response; }, 'plugins failed'), properties: attempt(() => { const keys = Object.keys(Object.getPrototypeOf(navigator)); return keys; }, 'navigator keys failed'), }; const getUserAgentData = () => attempt(() => { // @ts-ignore if (!navigator.userAgentData || // @ts-ignore !navigator.userAgentData.getHighEntropyValues) { return; } // @ts-ignore return navigator.userAgentData.getHighEntropyValues(['platform', 'platformVersion', 'architecture', 'bitness', 'model', 'uaFullVersion']).then((data) => { // @ts-ignore const { brands, mobile } = navigator.userAgentData || {}; const compressedBrands = (brands, captureVersion = false) => brands .filter((obj) => !/Not/.test(obj.brand)).map((obj) => `${obj.brand}${captureVersion ? ` ${obj.version}` : ''}`); const removeChromium = (brands) => (brands.length > 1 ? brands.filter((brand) => !/Chromium/.test(brand)) : brands); // compress brands if (!data.brands) { data.brands = brands; } data.brandsVersion = compressedBrands(data.brands, true); data.brands = compressedBrands(data.brands); data.brandsVersion = removeChromium(data.brandsVersion); data.brands = removeChromium(data.brands); if (!data.mobile) { data.mobile = mobile; } const dataSorted = Object.keys(data).sort().reduce((acc, key) => { acc[key] = data[key]; return acc; }, {}); return dataSorted; }); }, 'userAgentData failed'); const getBluetoothAvailability = () => attempt(() => { if (!('bluetooth' in navigator) || // @ts-ignore !navigator.bluetooth || // @ts-ignore !navigator.bluetooth.getAvailability) { return undefined; } // @ts-ignore return navigator.bluetooth.getAvailability(); }, 'bluetoothAvailability failed'); const getPermissions = () => attempt(() => { const getPermissionState = (name) => navigator.permissions.query({ name }) .then((res) => ({ name, state: res.state })) .catch((error) => ({ name, state: 'unknown' })); // https://w3c.github.io/permissions/#permission-registry const permissions = !('permissions' in navigator) ? undefined : Promise.all([ getPermissionState('accelerometer'), getPermissionState('ambient-light-sensor'), getPermissionState('background-fetch'), getPermissionState('background-sync'), getPermissionState('bluetooth'), getPermissionState('camera'), getPermissionState('clipboard'), getPermissionState('device-info'), getPermissionState('display-capture'), getPermissionState('gamepad'), getPermissionState('geolocation'), getPermissionState('gyroscope'), getPermissionState('magnetometer'), getPermissionState('microphone'), getPermissionState('midi'), getPermissionState('nfc'), getPermissionState('notifications'), getPermissionState('persistent-storage'), getPermissionState('push'), getPermissionState('screen-wake-lock'), getPermissionState('speaker'), getPermissionState('speaker-selection'), ]).then((permissions) => permissions.reduce((acc, perm) => { const { state, name } = perm || {}; if (acc[state]) { acc[state].push(name); return acc; } acc[state] = [name]; return acc; }, {})).catch((error) => console.error(error)); return permissions; }, 'permissions failed'); const getWebGpu = () => attempt(() => { if (!('gpu' in navigator)) { return; } // @ts-expect-error if unsupported return navigator.gpu.requestAdapter().then((adapter) => { if (!adapter) return; const { limits = {}, features = [] } = adapter || {}; // @ts-expect-error if unsupported const handleInfo = (info) => { const { architecture, description, device, vendor } = info; const adapterInfo = [vendor, architecture, description, device]; const featureValues = [...features.values()]; const limitsData = ((limits) => { const data = {}; // eslint-disable-next-line guard-for-in for (const prop in limits) { data[prop] = limits[prop]; } return data; })(limits); Analysis.webGpuAdapter = adapterInfo; Analysis.webGpuFeatures = featureValues; Analysis.webGpuLimits = hashMini(limitsData); return { adapterInfo, limits: limitsData, }; }; const { info } = adapter; return info ? handleInfo(info) : adapter.requestAdapterInfo().then(handleInfo); }); }, 'webgpu failed'); await queueEvent(timer); return Promise.all([ getUserAgentData(), getBluetoothAvailability(), getPermissions(), getWebGpu(), ]).then(([userAgentData, bluetoothAvailability, permissions, webgpu,]) => { logTestResult({ time: timer.stop(), test: 'navigator', passed: true }); return { ...data, userAgentData, bluetoothAvailability, permissions, webgpu, lied, }; }).catch((error) => { console.error(error); logTestResult({ time: timer.stop(), test: 'navigator', passed: true }); return { ...data, lied, }; }); } catch (error) { logTestResult({ test: 'navigator', passed: false }); captureError(error, 'Navigator failed or blocked by client'); return; } } function navigatorHTML(fp) { if (!fp.navigator) { return `
Navigator
properties (0): ${HTMLNote.BLOCKED}
dnt: ${HTMLNote.BLOCKED}
gpc:${HTMLNote.BLOCKED}
lang: ${HTMLNote.BLOCKED}
mimeTypes (0): ${HTMLNote.BLOCKED}
permissions (0): ${HTMLNote.BLOCKED}
plugins (0): ${HTMLNote.BLOCKED}
vendor: ${HTMLNote.BLOCKED}
webgpu: ${HTMLNote.BLOCKED}
userAgentData:
${HTMLNote.BLOCKED}
device:
${HTMLNote.BLOCKED}
ua parsed: ${HTMLNote.BLOCKED}
userAgent:
${HTMLNote.BLOCKED}
appVersion:
${HTMLNote.BLOCKED}
`; } const { navigator: { $hash, appVersion, deviceMemory, doNotTrack, globalPrivacyControl, hardwareConcurrency, language, maxTouchPoints, mimeTypes, oscpu, permissions, platform, plugins, properties, system, device, userAgent, uaPostReduction, userAgentData, userAgentParsed, vendor, bluetoothAvailability, webgpu, lied, }, } = fp; const id = 'creep-navigator'; const blocked = { ['null']: true, ['undefined']: true, ['']: true, }; const permissionsKeys = Object.keys(permissions || {}); const permissionsGranted = (permissions && permissions.granted ? permissions.granted.length : 0); return ` ${performanceLogger.getLog().navigator}
Navigator${hashSlice($hash)}
properties (${count(properties)}): ${modal(`${id}-properties`, properties.join(', '), hashMini(properties))}
dnt: ${'' + doNotTrack}
gpc: ${'' + globalPrivacyControl == 'undefined' ? HTMLNote.UNSUPPORTED : '' + globalPrivacyControl}
lang: ${!blocked[language] ? language : HTMLNote.BLOCKED}
mimeTypes (${count(mimeTypes)}): ${!blocked['' + mimeTypes] ? modal(`${id}-mimeTypes`, mimeTypes.join('
'), hashMini(mimeTypes)) : HTMLNote.BLOCKED}
permissions (${'' + permissionsGranted}): ${!permissions || !permissionsKeys ? HTMLNote.UNSUPPORTED : modal('creep-permissions', permissionsKeys.map((key) => `
${key}:
${permissions[key].join('
')}
`).join(''), hashMini(permissions))}
plugins (${count(plugins)}): ${!blocked['' + plugins] ? modal(`${id}-plugins`, plugins.map((plugin) => plugin.name).join('
'), hashMini(plugins)) : HTMLNote.BLOCKED}
vendor: ${!blocked[vendor] ? vendor : HTMLNote.BLOCKED}
webgpu: ${!webgpu ? HTMLNote.UNSUPPORTED : modal(`${id}-webgpu`, ((webgpu) => { const { adapterInfo, limits } = webgpu; return `
Adapter
${adapterInfo.filter((x) => x).join('
')}

Limits
${Object.keys(limits).map((x) => `${x}: ${limits[x]}`).join('
')}
`; })(webgpu), hashMini(webgpu))}
userAgentData:
${((userAgentData) => { const { architecture, bitness, brandsVersion, uaFullVersion, mobile, model, platformVersion, platform, } = userAgentData || {}; // @ts-ignore const windowsRelease = computeWindowsRelease({ platform, platformVersion }); return !userAgentData ? HTMLNote.UNSUPPORTED : ` ${(brandsVersion || []).join(',')}${uaFullVersion ? ` (${uaFullVersion})` : ''}
${windowsRelease || `${platform} ${platformVersion}`} ${architecture ? `${architecture}${bitness ? `_${bitness}` : ''}` : ''} ${model ? `
${model}` : ''} ${mobile ? '
mobile' : ''} `; })(userAgentData)}
device:
${oscpu ? oscpu : ''} ${`${oscpu ? '
' : ''}${system}${platform ? ` (${platform})` : ''}`} ${device ? `
${device}` : HTMLNote.BLOCKED}${hardwareConcurrency && deviceMemory ? `
cores: ${hardwareConcurrency}, ram: ${deviceMemory}` : hardwareConcurrency && !deviceMemory ? `
cores: ${hardwareConcurrency}` : !hardwareConcurrency && deviceMemory ? `
ram: ${deviceMemory}` : ''}${typeof maxTouchPoints != 'undefined' ? `, touch: ${'' + maxTouchPoints}` : ''}${bluetoothAvailability ? `, bluetooth` : ''}
ua parsed: ${userAgentParsed || HTMLNote.BLOCKED}
userAgent:${!uaPostReduction ? '' : `ua reduction`}
${userAgent || HTMLNote.BLOCKED}
appVersion:
${appVersion || HTMLNote.BLOCKED}
`; } async function getResistance() { try { const timer = createTimer(); await queueEvent(timer); const data = { privacy: undefined, security: undefined, mode: undefined, extension: undefined, engine: (IS_BLINK ? 'Blink' : IS_GECKO ? 'Gecko' : ''), }; // Firefox/Tor Browser const regex = (n) => new RegExp(`${n}+$`); const delay = (ms, baseNumber, baseDate) => new Promise((resolve) => setTimeout(() => { const date = baseDate ? baseDate : +new Date(); // @ts-ignore const value = regex(baseNumber).test(date) ? regex(baseNumber).exec(date)[0] : date; return resolve(value); }, ms)); const getTimerPrecision = async () => { const baseDate = +new Date(); const baseNumber = +('' + baseDate).slice(-1); const a = await delay(0, baseNumber, baseDate); const b = await delay(1, baseNumber); const c = await delay(2, baseNumber); const d = await delay(3, baseNumber); const e = await delay(4, baseNumber); const f = await delay(5, baseNumber); const g = await delay(6, baseNumber); const h = await delay(7, baseNumber); const i = await delay(8, baseNumber); const j = await delay(9, baseNumber); const lastCharA = ('' + a).slice(-1); const lastCharB = ('' + b).slice(-1); const lastCharC = ('' + c).slice(-1); const lastCharD = ('' + d).slice(-1); const lastCharE = ('' + e).slice(-1); const lastCharF = ('' + f).slice(-1); const lastCharG = ('' + g).slice(-1); const lastCharH = ('' + h).slice(-1); const lastCharI = ('' + i).slice(-1); const lastCharJ = ('' + j).slice(-1); const protection = (lastCharA == lastCharB && lastCharA == lastCharC && lastCharA == lastCharD && lastCharA == lastCharE && lastCharA == lastCharF && lastCharA == lastCharG && lastCharA == lastCharH && lastCharA == lastCharI && lastCharA == lastCharJ); const baseLen = ('' + a).length; const collection = [a, b, c, d, e, f, g, h, i, j]; return { protection, delays: collection.map((n) => ('' + n).length > baseLen ? ('' + n).slice(-baseLen) : n), precision: protection ? Math.min(...collection.map((val) => ('' + val).length)) : undefined, precisionValue: protection ? lastCharA : undefined, }; }; const [isBrave, timerPrecision,] = await Promise.all([ braveBrowser(), IS_BLINK ? undefined : getTimerPrecision(), ]); if (isBrave) { const braveMode = getBraveMode(); data.privacy = 'Brave'; // @ts-ignore data.security = { 'FileSystemWritableFileStream': 'FileSystemWritableFileStream' in window, 'Serial': 'Serial' in window, 'ReportingObserver': 'ReportingObserver' in window, }; data.mode = (braveMode.allow ? 'allow' : braveMode.standard ? 'standard' : braveMode.strict ? 'strict' : ''); } const { protection } = timerPrecision || {}; if (IS_GECKO && protection) { const features = { 'OfflineAudioContext': 'OfflineAudioContext' in window, // dom.webaudio.enabled 'WebGL2RenderingContext': 'WebGL2RenderingContext' in window, // webgl.enable-webgl2 'WebAssembly': 'WebAssembly' in window, // javascript.options.wasm 'maxTouchPoints': 'maxTouchPoints' in navigator, 'RTCRtpTransceiver': 'RTCRtpTransceiver' in window, 'MediaDevices': 'MediaDevices' in window, 'Credential': 'Credential' in window, }; const featureKeys = Object.keys(features); const targetSet = new Set([ 'RTCRtpTransceiver', 'MediaDevices', 'Credential', ]); const torBrowser = featureKeys.filter((key) => targetSet.has(key) && !features[key]).length == targetSet.size; const safer = !features.WebAssembly; data.privacy = torBrowser ? 'Tor Browser' : 'Firefox'; // @ts-ignore data.security = { 'reduceTimerPrecision': true, ...features, }; data.mode = (!torBrowser ? 'resistFingerprinting' : safer ? 'safer' : 'standard'); } // extension // - this technique gets a small sample of known lie patterns // - patterns vary based on extensions settings, version, browser const prototypeLiesLen = Object.keys(prototypeLies).length; // patterns based on settings const disabled = 'c767712b'; const pattern = { noscript: { contentDocumentHash: ['0b637a33', '37e2f32e', '318390d1'], contentWindowHash: ['0b637a33', '37e2f32e', '318390d1'], getContextHash: ['0b637a33', '081d6d1b', disabled], }, trace: { contentDocumentHash: ['ca9d9c2f'], contentWindowHash: ['ca9d9c2f'], createElementHash: ['77dea834'], getElementByIdHash: ['77dea834'], getImageDataHash: ['77dea834'], toBlobHash: ['77dea834', disabled], toDataURLHash: ['77dea834', disabled], }, cydec: { // [FF, FF Anti OFF, Chrome, Chrome Anti Off, no iframe Chrome, no iframe Chrome Anti Off] contentDocumentHash: ['945b0c78', '15771efa', '403a1a21', '55e9b959'], contentWindowHash: ['945b0c78', '15771efa', '403a1a21', '55e9b959'], createElementHash: ['3dd86d6f', 'cc7cb598', '4237b44c', '1466aaf0', '0cb0c682', '73c662d9', '72b1ee2b', 'ae3d02c9'], getElementByIdHash: ['3dd86d6f', 'cc7cb598', '4237b44c', '1466aaf0', '0cb0c682', '73c662d9', '72b1ee2b', 'ae3d02c9'], getImageDataHash: ['044f14c2', 'db60d7f9', '15771efa', 'db60d7f9', '55e9b959'], toBlobHash: ['044f14c2', '15771efa', 'afec348d', '55e9b959', '0dbbf456'], toDataURLHash: ['ecb498d9', '15771efa', '6b838fb6', 'd19104ec', '6985d315', '55e9b959', 'fe88259f'], }, canvasblocker: { contentDocumentHash: ['98ec858e', 'dbbaf31f'], contentWindowHash: ['98ec858e', 'dbbaf31f'], appendHash: ['98ec858e', 'dbbaf31f'], getImageDataHash: ['98ec858e', 'a2971888', 'dbbaf31f', disabled], toBlobHash: ['9f1c3dfe', 'a2971888', 'dbbaf31f', disabled], toDataURLHash: ['98ec858e', 'a2971888', 'dbbaf31f', disabled], }, chameleon: { appendHash: ['77dea834'], insertAdjacentElementHash: ['77dea834'], insertAdjacentHTMLHash: ['77dea834'], insertAdjacentTextHash: ['77dea834'], prependHash: ['77dea834'], replaceWithHash: ['77dea834'], appendChildHash: ['77dea834'], insertBeforeHash: ['77dea834'], replaceChildHash: ['77dea834'], }, duckduckgo: { toDataURLHash: ['fd00bf5d', '8ee7df22', disabled], toBlobHash: ['fd00bf5d', '8ee7df22', disabled], getImageDataHash: ['fd00bf5d', '8ee7df22', disabled], getByteFrequencyDataHash: ['fd00bf5d', '8ee7df22', disabled], getByteTimeDomainDataHash: ['fd00bf5d', '8ee7df22', disabled], getFloatFrequencyDataHash: ['fd00bf5d', '8ee7df22', disabled], getFloatTimeDomainDataHash: ['fd00bf5d', '8ee7df22', disabled], copyFromChannelHash: ['fd00bf5d', '8ee7df22', disabled], getChannelDataHash: ['fd00bf5d', '8ee7df22', disabled], hardwareConcurrencyHash: ['dfd41ab4'], availHeightHash: ['dfd41ab4'], availLeftHash: ['dfd41ab4'], availTopHash: ['dfd41ab4'], availWidthHash: ['dfd41ab4'], colorDepthHash: ['dfd41ab4'], pixelDepthHash: ['dfd41ab4'], }, // mode: Learn to block new trackers from your browsing privacybadger: { getImageDataHash: ['0cb0c682'], toDataURLHash: ['0cb0c682'], }, privacypossum: { hardwareConcurrencyHash: ['452924d5'], availWidthHash: ['452924d5'], colorDepthHash: ['452924d5'], }, jshelter: { contentDocumentHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], contentWindowHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], appendHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], insertAdjacentElementHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], insertAdjacentHTMLHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], prependHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], replaceWithHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], appendChildHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], insertBeforeHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], replaceChildHash: ['0007ab4e', '0b637a33', '866fa7e7', '318390d1'], hardwareConcurrencyHash: ['dfd41ab4'], }, puppeteerExtra: { contentDocumentHash: ['55e9b959'], contentWindowHash: [ '55e9b959', '50a281b5', // @2.10.0 ], createElementHash: ['55e9b959'], getElementByIdHash: ['55e9b959'], appendHash: ['55e9b959'], insertAdjacentElementHash: ['55e9b959'], insertAdjacentHTMLHash: ['55e9b959'], insertAdjacentTextHash: ['55e9b959'], prependHash: ['55e9b959'], replaceWithHash: ['55e9b959'], appendChildHash: ['55e9b959'], insertBeforeHash: ['55e9b959'], replaceChildHash: ['55e9b959'], getContextHash: ['55e9b959', disabled], toDataURLHash: ['55e9b959', disabled], toBlobHash: ['55e9b959', disabled], getImageDataHash: ['55e9b959'], hardwareConcurrencyHash: ['efbd4cf9', 'a63491fb', 'b011fd1c', '194ecf17', '55e9b959'], }, fakeBrowser: { appendChildHash: ['8dfec2ec', 'f43e6134'], getContextHash: ['83b825ab', 'a63491fb'], toDataURLHash: ['83b825ab', 'a63491fb'], toBlobHash: ['83b825ab', 'a63491fb'], getImageDataHash: ['83b825ab', 'a63491fb'], hardwareConcurrencyHash: ['83b825ab', 'a63491fb'], availHeightHash: ['83b825ab', 'a63491fb'], availLeftHash: ['83b825ab', 'a63491fb'], availTopHash: ['83b825ab', 'a63491fb'], availWidthHash: ['83b825ab', 'a63491fb'], colorDepthHash: ['83b825ab', 'a63491fb'], pixelDepthHash: ['83b825ab', 'a63491fb'], }, }; /* Random User-Agent User Agent Switcher and Manager ScriptSafe Windscribe */ await queueEvent(timer); const hash = { // iframes contentDocumentHash: hashMini(prototypeLies['HTMLIFrameElement.contentDocument']), contentWindowHash: hashMini(prototypeLies['HTMLIFrameElement.contentWindow']), createElementHash: hashMini(prototypeLies['Document.createElement']), getElementByIdHash: hashMini(prototypeLies['Document.getElementById']), appendHash: hashMini(prototypeLies['Element.append']), insertAdjacentElementHash: hashMini(prototypeLies['Element.insertAdjacentElement']), insertAdjacentHTMLHash: hashMini(prototypeLies['Element.insertAdjacentHTML']), insertAdjacentTextHash: hashMini(prototypeLies['Element.insertAdjacentText']), prependHash: hashMini(prototypeLies['Element.prepend']), replaceWithHash: hashMini(prototypeLies['Element.replaceWith']), appendChildHash: hashMini(prototypeLies['Node.appendChild']), insertBeforeHash: hashMini(prototypeLies['Node.insertBefore']), replaceChildHash: hashMini(prototypeLies['Node.replaceChild']), // canvas getContextHash: hashMini(prototypeLies['HTMLCanvasElement.getContext']), toDataURLHash: hashMini(prototypeLies['HTMLCanvasElement.toDataURL']), toBlobHash: hashMini(prototypeLies['HTMLCanvasElement.toBlob']), getImageDataHash: hashMini(prototypeLies['CanvasRenderingContext2D.getImageData']), // Audio getByteFrequencyDataHash: hashMini(prototypeLies['AnalyserNode.getByteFrequencyData']), getByteTimeDomainDataHash: hashMini(prototypeLies['AnalyserNode.getByteTimeDomainData']), getFloatFrequencyDataHash: hashMini(prototypeLies['AnalyserNode.getFloatFrequencyData']), getFloatTimeDomainDataHash: hashMini(prototypeLies['AnalyserNode.getFloatTimeDomainData']), copyFromChannelHash: hashMini(prototypeLies['AudioBuffer.copyFromChannel']), getChannelDataHash: hashMini(prototypeLies['AudioBuffer.getChannelData']), // Hardware hardwareConcurrencyHash: hashMini(prototypeLies['Navigator.hardwareConcurrency']), // Screen availHeightHash: hashMini(prototypeLies['Screen.availHeight']), availLeftHash: hashMini(prototypeLies['Screen.availLeft']), availTopHash: hashMini(prototypeLies['Screen.availTop']), availWidthHash: hashMini(prototypeLies['Screen.availWidth']), colorDepthHash: hashMini(prototypeLies['Screen.colorDepth']), pixelDepthHash: hashMini(prototypeLies['Screen.pixelDepth']), }; data.extensionHashPattern = Object.keys(hash).reduce((acc, key) => { const val = hash[key]; if (val == disabled) { return acc; } acc[key.replace('Hash', '')] = val; return acc; }, {}); const getExtension = ({ pattern, hash, prototypeLiesLen }) => { const { noscript, trace, cydec, canvasblocker, chameleon, duckduckgo, privacybadger, privacypossum, jshelter, puppeteerExtra, fakeBrowser, } = pattern; const disabled = 'c767712b'; if (prototypeLiesLen) { if (prototypeLiesLen >= 7 && trace.contentDocumentHash.includes(hash.contentDocumentHash) && trace.contentWindowHash.includes(hash.contentWindowHash) && trace.createElementHash.includes(hash.createElementHash) && trace.getElementByIdHash.includes(hash.getElementByIdHash) && trace.toDataURLHash.includes(hash.toDataURLHash) && trace.toBlobHash.includes(hash.toBlobHash) && trace.getImageDataHash.includes(hash.getImageDataHash)) { return 'Trace'; } if (prototypeLiesLen >= 7 && cydec.contentDocumentHash.includes(hash.contentDocumentHash) && cydec.contentWindowHash.includes(hash.contentWindowHash) && cydec.createElementHash.includes(hash.createElementHash) && cydec.getElementByIdHash.includes(hash.getElementByIdHash) && cydec.toDataURLHash.includes(hash.toDataURLHash) && cydec.toBlobHash.includes(hash.toBlobHash) && cydec.getImageDataHash.includes(hash.getImageDataHash)) { return 'CyDec'; } if (prototypeLiesLen >= 6 && canvasblocker.contentDocumentHash.includes(hash.contentDocumentHash) && canvasblocker.contentWindowHash.includes(hash.contentWindowHash) && canvasblocker.appendHash.includes(hash.appendHash) && canvasblocker.toDataURLHash.includes(hash.toDataURLHash) && canvasblocker.toBlobHash.includes(hash.toBlobHash) && canvasblocker.getImageDataHash.includes(hash.getImageDataHash)) { return 'CanvasBlocker'; } if (prototypeLiesLen >= 9 && chameleon.appendHash.includes(hash.appendHash) && chameleon.insertAdjacentElementHash.includes(hash.insertAdjacentElementHash) && chameleon.insertAdjacentHTMLHash.includes(hash.insertAdjacentHTMLHash) && chameleon.insertAdjacentTextHash.includes(hash.insertAdjacentTextHash) && chameleon.prependHash.includes(hash.prependHash) && chameleon.replaceWithHash.includes(hash.replaceWithHash) && chameleon.appendChildHash.includes(hash.appendChildHash) && chameleon.insertBeforeHash.includes(hash.insertBeforeHash) && chameleon.replaceChildHash.includes(hash.replaceChildHash)) { return 'Chameleon'; } if (prototypeLiesLen >= 7 && duckduckgo.toDataURLHash.includes(hash.toDataURLHash) && duckduckgo.toBlobHash.includes(hash.toBlobHash) && duckduckgo.getImageDataHash.includes(hash.getImageDataHash) && duckduckgo.getByteFrequencyDataHash.includes(hash.getByteFrequencyDataHash) && duckduckgo.getByteTimeDomainDataHash.includes(hash.getByteTimeDomainDataHash) && duckduckgo.getFloatFrequencyDataHash.includes(hash.getFloatFrequencyDataHash) && duckduckgo.getFloatTimeDomainDataHash.includes(hash.getFloatTimeDomainDataHash) && duckduckgo.copyFromChannelHash.includes(hash.copyFromChannelHash) && duckduckgo.getChannelDataHash.includes(hash.getChannelDataHash) && duckduckgo.hardwareConcurrencyHash.includes(hash.hardwareConcurrencyHash) && duckduckgo.availHeightHash.includes(hash.availHeightHash) && duckduckgo.availLeftHash.includes(hash.availLeftHash) && duckduckgo.availTopHash.includes(hash.availTopHash) && duckduckgo.availWidthHash.includes(hash.availWidthHash) && duckduckgo.colorDepthHash.includes(hash.colorDepthHash) && duckduckgo.pixelDepthHash.includes(hash.pixelDepthHash)) { return 'DuckDuckGo'; } if (prototypeLiesLen >= 2 && privacybadger.getImageDataHash.includes(hash.getImageDataHash) && privacybadger.toDataURLHash.includes(hash.toDataURLHash)) { return 'Privacy Badger'; } if (prototypeLiesLen >= 3 && privacypossum.hardwareConcurrencyHash.includes(hash.hardwareConcurrencyHash) && privacypossum.availWidthHash.includes(hash.availWidthHash) && privacypossum.colorDepthHash.includes(hash.colorDepthHash)) { return 'Privacy Possum'; } if (prototypeLiesLen >= 2 && noscript.contentDocumentHash.includes(hash.contentDocumentHash) && noscript.contentWindowHash.includes(hash.contentDocumentHash) && noscript.getContextHash.includes(hash.getContextHash) && // distinguish NoScript from JShelter hash.hardwareConcurrencyHash == disabled) { return 'NoScript'; } if (prototypeLiesLen >= 14 && jshelter.contentDocumentHash.includes(hash.contentDocumentHash) && jshelter.contentWindowHash.includes(hash.contentDocumentHash) && jshelter.appendHash.includes(hash.appendHash) && jshelter.insertAdjacentElementHash.includes(hash.insertAdjacentElementHash) && jshelter.insertAdjacentHTMLHash.includes(hash.insertAdjacentHTMLHash) && jshelter.prependHash.includes(hash.prependHash) && jshelter.replaceWithHash.includes(hash.replaceWithHash) && jshelter.appendChildHash.includes(hash.appendChildHash) && jshelter.insertBeforeHash.includes(hash.insertBeforeHash) && jshelter.replaceChildHash.includes(hash.replaceChildHash) && jshelter.hardwareConcurrencyHash.includes(hash.hardwareConcurrencyHash)) { return 'JShelter'; } if (prototypeLiesLen >= 13 && puppeteerExtra.contentDocumentHash.includes(hash.contentDocumentHash) && puppeteerExtra.contentWindowHash.includes(hash.contentWindowHash) && puppeteerExtra.createElementHash.includes(hash.createElementHash) && puppeteerExtra.getElementByIdHash.includes(hash.getElementByIdHash) && puppeteerExtra.appendHash.includes(hash.appendHash) && puppeteerExtra.insertAdjacentElementHash.includes(hash.insertAdjacentElementHash) && puppeteerExtra.insertAdjacentHTMLHash.includes(hash.insertAdjacentHTMLHash) && puppeteerExtra.insertAdjacentTextHash.includes(hash.insertAdjacentTextHash) && puppeteerExtra.prependHash.includes(hash.prependHash) && puppeteerExtra.replaceWithHash.includes(hash.replaceWithHash) && puppeteerExtra.appendChildHash.includes(hash.appendChildHash) && puppeteerExtra.insertBeforeHash.includes(hash.insertBeforeHash) && puppeteerExtra.contentDocumentHash.includes(hash.contentDocumentHash) && puppeteerExtra.replaceChildHash.includes(hash.replaceChildHash) && puppeteerExtra.getContextHash.includes(hash.getContextHash) && puppeteerExtra.toDataURLHash.includes(hash.toDataURLHash) && puppeteerExtra.toBlobHash.includes(hash.toBlobHash) && puppeteerExtra.getImageDataHash.includes(hash.getImageDataHash) && puppeteerExtra.hardwareConcurrencyHash.includes(hash.hardwareConcurrencyHash)) { return 'puppeteer-extra'; } if (prototypeLiesLen >= 12 && fakeBrowser.appendChildHash.includes(hash.appendChildHash) && fakeBrowser.getContextHash.includes(hash.getContextHash) && fakeBrowser.toDataURLHash.includes(hash.toDataURLHash) && fakeBrowser.toBlobHash.includes(hash.toBlobHash) && fakeBrowser.getImageDataHash.includes(hash.getImageDataHash) && fakeBrowser.hardwareConcurrencyHash.includes(hash.hardwareConcurrencyHash) && fakeBrowser.availHeightHash.includes(hash.availHeightHash) && fakeBrowser.availLeftHash.includes(hash.availLeftHash) && fakeBrowser.availTopHash.includes(hash.availTopHash) && fakeBrowser.availWidthHash.includes(hash.availWidthHash) && fakeBrowser.colorDepthHash.includes(hash.colorDepthHash) && fakeBrowser.pixelDepthHash.includes(hash.pixelDepthHash)) { return 'FakeBrowser'; } return; } return; }; // @ts-ignore data.extension = getExtension({ pattern, hash, prototypeLiesLen }); logTestResult({ time: timer.stop(), test: 'resistance', passed: true }); return data; } catch (error) { logTestResult({ test: 'resistance', passed: false }); captureError(error); return; } } function resistanceHTML(fp) { if (!fp.resistance) { return `
Resistance
privacy: ${HTMLNote.BLOCKED}
security: ${HTMLNote.BLOCKED}
mode: ${HTMLNote.BLOCKED}
extension: ${HTMLNote.BLOCKED}
`; } const { resistance: data, } = fp; const { $hash, privacy, security, mode, extension, extensionHashPattern, engine, } = data || {}; const securitySettings = !security || Object.keys(security).reduce((acc, curr) => { if (security[curr]) { acc[curr] = 'enabled'; return acc; } acc[curr] = 'disabled'; return acc; }, {}); const browserIcon = (/brave/i.test(privacy) ? '' : /tor/i.test(privacy) ? '' : /firefox/i.test(privacy) ? '' : ''); const extensionIcon = (/blink/i.test(engine) ? '' : /gecko/i.test(engine) ? '' : ''); return `
${performanceLogger.getLog().resistance} Resistance${hashSlice($hash)}
privacy: ${privacy ? `${browserIcon}${privacy}` : HTMLNote.UNKNOWN}
security: ${!security ? HTMLNote.UNKNOWN : modal('creep-resistance', 'Security

' + Object.keys(securitySettings).map((key) => `${key}: ${'' + securitySettings[key]}`).join('
'), hashMini(security))}
mode: ${mode || HTMLNote.UNKNOWN}
extension: ${!Object.keys(extensionHashPattern || {}).length ? HTMLNote.UNKNOWN : modal('creep-extension', 'Pattern

' + Object.keys(extensionHashPattern).map((key) => `${key}: ${'' + extensionHashPattern[key]}`).join('
'), (extension ? `${extensionIcon}${extension}` : hashMini(extensionHashPattern)))}
`; } function hasTouch() { try { return 'ontouchstart' in window && !!document.createEvent('TouchEvent'); } catch (err) { return false; } } async function getScreen(log = true) { try { const timer = createTimer(); timer.start(); let lied = (lieProps['Screen.width'] || lieProps['Screen.height'] || lieProps['Screen.availWidth'] || lieProps['Screen.availHeight'] || lieProps['Screen.colorDepth'] || lieProps['Screen.pixelDepth']) || false; const s = (window.screen || {}); const { width, height, availWidth, availHeight, colorDepth, pixelDepth, } = s; const dpr = window.devicePixelRatio || 0; const firefoxWithHighDPR = IS_GECKO && (dpr != 1); if (!firefoxWithHighDPR) { // firefox with high dpr requires floating point precision dimensions const matchMediaLie = !matchMedia(`(device-width: ${width}px) and (device-height: ${height}px)`).matches; if (matchMediaLie) { lied = true; documentLie('Screen', 'failed matchMedia'); } } const hasLiedDPR = !matchMedia(`(resolution: ${dpr}dppx)`).matches; if (!IS_WEBKIT && hasLiedDPR) { lied = true; documentLie('Window.devicePixelRatio', 'lied dpr'); } const noTaskbar = !(width - availWidth || height - availHeight); if (width > 800 && noTaskbar) { LowerEntropy.SCREEN = true; } const data = { width, height, availWidth, availHeight, colorDepth, pixelDepth, touch: hasTouch(), lied, }; log && logTestResult({ time: timer.stop(), test: 'screen', passed: true }); return data; } catch (error) { log && logTestResult({ test: 'screen', passed: false }); captureError(error); return; } } function screenHTML(fp) { if (!fp.screen) { return `
Screen
...screen: ${HTMLNote.BLOCKED}
....avail: ${HTMLNote.BLOCKED}
touch: ${HTMLNote.BLOCKED}
depth: ${HTMLNote.BLOCKED}
viewport: ${HTMLNote.BLOCKED}
`; } const { screen: data, } = fp; const { $hash } = data || {}; const perf = performanceLogger.getLog().screen; const paintScreen = (event) => { const el = document.getElementById('creep-resize'); if (!el) { return; } removeEventListener('resize', paintScreen); return getScreen(false).then((data) => { requestAnimationFrame(() => patch(el, html `${resizeHTML(({ data, $hash, perf, paintScreen }))}`)); }); }; const resizeHTML = ({ data, $hash, perf, paintScreen }) => { const { width, height, availWidth, availHeight, colorDepth, pixelDepth, touch, lied, } = data; addEventListener('resize', paintScreen); const s = (window.screen || {}); const { orientation } = s; const { type: orientationType } = orientation || {}; const dpr = window.devicePixelRatio || undefined; const { width: vVWidth, height: vVHeight } = (window.visualViewport || {}); const mediaOrientation = !window.matchMedia ? undefined : (matchMedia('(orientation: landscape)').matches ? 'landscape' : matchMedia('(orientation: portrait)').matches ? 'portrait' : undefined); const displayMode = !window.matchMedia ? undefined : (matchMedia('(display-mode: fullscreen)').matches ? 'fullscreen' : matchMedia('(display-mode: standalone)').matches ? 'standalone' : matchMedia('(display-mode: minimal-ui)').matches ? 'minimal-ui' : matchMedia('(display-mode: browser)').matches ? 'browser' : undefined); const getDeviceDimensions = (width, height, diameter = 180) => { const aspectRatio = width / height; const isPortrait = height > width; const deviceWidth = isPortrait ? diameter * aspectRatio : diameter; const deviceHeight = isPortrait ? diameter : diameter / aspectRatio; return { deviceWidth, deviceHeight }; }; // const { deviceWidth, deviceHeight } = getDeviceDimensions(width, height) const { deviceWidth: deviceInnerWidth, deviceHeight: deviceInnerHeight } = getDeviceDimensions(innerWidth, innerHeight); const toFix = (n, nFix) => { const d = +(1 + [...Array(nFix)].map((x) => 0).join('')); return Math.round(n * d) / d; }; const viewportTitle = `Window.outerWidth\nWindow.outerHeight\nWindow.innerWidth\nWindow.innerHeight\nVisualViewport.width\nVisualViewport.height\nWindow.matchMedia()\nScreenOrientation.type\nWindow.devicePixelRatio`; return `
${perf} Screen${hashSlice($hash)}
...screen: ${width} x ${height}
....avail: ${availWidth} x ${availHeight}
touch: ${touch}
depth: ${colorDepth}|${pixelDepth}
viewport:
${outerWidth} ${innerWidth} ${toFix(vVWidth, 6)} ${outerHeight} ${innerHeight} ${toFix(vVHeight, 6)} ${displayMode} ${mediaOrientation} ${orientationType} ${dpr}
`; }; return ` ${resizeHTML({ data, $hash, perf, paintScreen })} `; } async function getVoices() { // Don't run voice immediately. This is unstable // wait a bit for services to load await new Promise((resolve) => setTimeout(() => resolve(undefined), 50)); return new Promise(async (resolve) => { try { const timer = createTimer(); await queueEvent(timer); // use window since iframe is unstable in FF const supported = 'speechSynthesis' in window; supported && speechSynthesis.getVoices(); // warm up if (!supported) { logTestResult({ test: 'speech', passed: false }); return resolve(null); } const voicesLie = !!lieProps['SpeechSynthesis.getVoices']; const giveUpOnVoices = setTimeout(() => { logTestResult({ test: 'speech', passed: false }); return resolve(null); }, 300); const getVoices = () => { const data = speechSynthesis.getVoices(); const localServiceDidLoad = (data || []).find((x) => x.localService); if (!data || !data.length || (IS_BLINK && !localServiceDidLoad)) { return; } clearTimeout(giveUpOnVoices); // filter first occurrence of unique voiceURI data const getUniques = (data, voiceURISet) => data .filter((x) => { const { voiceURI } = x; if (!voiceURISet.has(voiceURI)) { voiceURISet.add(voiceURI); return true; } return false; }); const dataUnique = getUniques(data, new Set()); // https://wicg.github.io/speech-api/#speechsynthesisvoice-attributes const local = dataUnique.filter((x) => x.localService).map((x) => x.name); const remote = dataUnique.filter((x) => !x.localService).map((x) => x.name); const languages = [...new Set(dataUnique.map((x) => x.lang))]; const defaultLocalVoices = dataUnique.filter((x) => x.default && x.localService); let defaultVoiceName = ''; let defaultVoiceLang = ''; if (defaultLocalVoices.length === 1) { const { name, lang } = defaultLocalVoices[0]; defaultVoiceName = name; defaultVoiceLang = (lang || '').replace(/_/, '-'); } // eslint-disable-next-line new-cap const { locale: localeLang } = Intl.DateTimeFormat().resolvedOptions(); if (defaultVoiceLang && defaultVoiceLang.split('-')[0] !== localeLang.split('-')[0]) { // this is not trash Analysis.voiceLangMismatch = true; LowerEntropy.TIME_ZONE = true; } logTestResult({ time: timer.stop(), test: 'speech', passed: true }); return resolve({ local, remote, languages, defaultVoiceName, defaultVoiceLang, lied: voicesLie, }); }; getVoices(); if (speechSynthesis.addEventListener) { return speechSynthesis.addEventListener('voiceschanged', getVoices); } speechSynthesis.onvoiceschanged = getVoices; } catch (error) { logTestResult({ test: 'speech', passed: false }); captureError(error); return resolve(null); } }); } function voicesHTML(fp) { if (!fp.voices) { return `
Speech
local (0): ${HTMLNote.BLOCKED}
remote (0): ${HTMLNote.BLOCKED}
lang (0): ${HTMLNote.BLOCKED}
default:
${HTMLNote.BLOCKED}
`; } const { voices: { $hash, local, remote, languages, defaultVoiceName, defaultVoiceLang, lied, }, } = fp; const icon = { 'Linux': '', 'Apple': '', 'Windows': '', 'Android': '', 'CrOS': '', }; const system = { 'Chrome OS': icon.CrOS, 'Maged': icon.Apple, 'Microsoft': icon.Windows, 'English United States': icon.Android, 'English (United States)': icon.Android, }; const systemVoice = Object.keys(system).find((key) => local.find((voice) => voice.includes(key))) || ''; return `
${performanceLogger.getLog().speech} Speech${hashSlice($hash)}
local (${count(local)}): ${!local || !local.length ? HTMLNote.UNSUPPORTED : modal('creep-voices-local', local.join('
'), `${system[systemVoice] || ''}${hashMini(local)}`)}
remote (${count(remote)}): ${!remote || !remote.length ? HTMLNote.UNSUPPORTED : modal('creep-voices-remote', remote.join('
'), hashMini(remote))}
lang (${count(languages)}): ${!languages || !languages.length ? HTMLNote.BLOCKED : languages.length == 1 ? languages[0] : modal('creep-voices-languages', languages.join('
'), hashMini(languages))}
default:
${!defaultVoiceName ? HTMLNote.UNSUPPORTED : `${defaultVoiceName}${defaultVoiceLang ? ` [${defaultVoiceLang}]` : ''}`}
`; } const GIGABYTE = 1073741824; // bytes function getMaxCallStackSize() { const fn = () => { try { return 1 + fn(); } catch (err) { return 1; } }; [...Array(10)].forEach(() => fn()); // stabilize return fn(); } // based on and inspired by // https://github.com/Joe12387/OP-Fingerprinting-Script/blob/main/opfs.js#L443 function getTimingResolution() { const maxRuns = 5000; let valA = 1; let valB = 1; let res; for (let i = 0; i < maxRuns; i++) { const a = performance.now(); const b = performance.now(); if (a < b) { res = b - a; if (res > valA && res < valB) { valB = res; } else if (res < valA) { valB = valA; valA = res; } } } return [valA, valB]; } function getClientLitter() { try { const iframe = document.createElement('iframe'); document.body.appendChild(iframe); const iframeWindow = iframe.contentWindow; const windowKeys = Object.getOwnPropertyNames(window); const iframeKeys = Object.getOwnPropertyNames(iframeWindow); document.body.removeChild(iframe); const clientKeys = windowKeys.filter((x) => !iframeKeys.includes(x)); return clientKeys; } catch (err) { return []; } } function getClientCode() { const names = Object.getOwnPropertyNames(window).slice(-50); const [p1, p2] = (1).constructor.toString().split((1).constructor.name); const isEngine = (fn) => { return (typeof fn === 'function' && ('' + fn === p1 + fn.name + p2 || '' + fn === p1 + (fn.name || '').replace('get ', '') + p2)); }; const isClient = (key) => { if (/_$/.test(key)) return true; const d = Object.getOwnPropertyDescriptor(window, key); if (!d) return true; return key === 'chrome' ? names.includes(key) : !isEngine(d.get || d.value); }; return Object.keys(window) .slice(-50) .filter((x) => isClient(x)); } async function getBattery() { if (!('getBattery' in navigator)) return null; // @ts-expect-error if not supported return navigator.getBattery(); } async function getStorage() { if (!navigator?.storage?.estimate) return null; return Promise.all([ navigator.storage.estimate().then(({ quota }) => quota), new Promise((resolve) => { // @ts-expect-error if not supported navigator.webkitTemporaryStorage.queryUsageAndQuota((_, quota) => { resolve(quota); }); }).catch(() => null), ]).then(([quota1, quota2]) => (quota2 || quota1)); } async function getScriptSize() { let url = null; try { // @ts-expect-error if unsupported url = document?.currentScript?.src || (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('creep.js', document.baseURI).href); } catch (err) { } if (!url) return null; return fetch(url) .then((res) => res.blob()) .then((blob) => blob.size) .catch(() => null); } async function getStatus() { const [batteryInfo, quotaA, quotaB, scriptSize, stackSize, timingRes, clientLitter,] = await Promise.all([ getBattery(), getStorage(), getStorage(), getScriptSize(), getMaxCallStackSize(), getTimingResolution(), [...new Set([...getClientLitter(), ...getClientCode()])].sort().slice(0, 50), ]); // BatteryManager const { charging, chargingTime, dischargingTime, level, } = batteryInfo || {}; // MemoryInfo // @ts-expect-error if not supported const memory = performance?.memory?.jsHeapSizeLimit || null; const memoryInGigabytes = memory ? +(memory / GIGABYTE).toFixed(2) : null; // StorageManager const quotaInGigabytes = quotaA ? +(+(quotaA) / GIGABYTE).toFixed(2) : null; // Network Info const { downlink, effectiveType, rtt, saveData, downlinkMax, type, // @ts-expect-error if not supported } = navigator?.connection || {}; const scripts = [ ...document.querySelectorAll('script'), ].map((x) => x.src.replace(/^https?:\/\//, '')).slice(0, 10); return { charging, chargingTime, dischargingTime, level, memory, memoryInGigabytes, quota: quotaA, quotaIsInsecure: quotaA !== quotaB, quotaInGigabytes, downlink, effectiveType, rtt, saveData, downlinkMax, type, stackSize, timingRes, clientLitter, scripts, scriptSize, }; } function statusHTML(status) { if (!status) { return `
Status
network:
${HTMLNote.BLOCKED}
battery:
${HTMLNote.BLOCKED}
available:
${HTMLNote.BLOCKED}
`; } const { charging, chargingTime, dischargingTime, level, memory, memoryInGigabytes, quota, quotaInGigabytes, downlink, effectiveType, rtt, saveData, downlinkMax, type, stackSize, timingRes, } = status; const statusHash = hashMini({ memoryInGigabytes, quotaInGigabytes, timingRes, rtt: rtt === 0 ? 0 : -1, type, }); return `
Status${statusHash}
network:
${isNaN(Number(rtt)) ? HTMLNote.UNSUPPORTED : `
rtt: ${rtt}, downlink: ${downlink}${downlinkMax ? `, max: ${downlinkMax}` : ''}
effectiveType: ${effectiveType}
saveData: ${saveData}${type ? `, type: ${type}` : ''}
`}
battery:
${!level || isNaN(Number(level)) ? HTMLNote.UNSUPPORTED : `
level: ${level * 100}%
charging: ${charging}
charge time: ${chargingTime === Infinity ? 'discharging' : chargingTime === 0 ? 'fully charged' : `${+(chargingTime / 60).toFixed(1)} min.`}
discharge time: ${dischargingTime === Infinity ? 'charging' : `${+(dischargingTime / 60).toFixed(1)} min.`}
`}
available:
${quota ? `
storage: ${quotaInGigabytes}GB
[${quota}]
` : ''} ${memory ? `
memory: ${memoryInGigabytes}GB
[${memory}]
` : ''} ${timingRes ? `
timing res:
${timingRes.join('
')}
` : ''}
stack: ${stackSize || HTMLNote.BLOCKED}
`; } async function getSVG() { try { const timer = createTimer(); await queueEvent(timer); let lied = (lieProps['SVGRect.height'] || lieProps['SVGRect.width'] || lieProps['SVGRect.x'] || lieProps['SVGRect.y'] || lieProps['String.fromCodePoint'] || lieProps['SVGRectElement.getBBox'] || lieProps['SVGTextContentElement.getExtentOfChar'] || lieProps['SVGTextContentElement.getSubStringLength'] || lieProps['SVGTextContentElement.getComputedTextLength']) || false; const doc = (PHANTOM_DARKNESS && PHANTOM_DARKNESS.document && PHANTOM_DARKNESS.document.body ? PHANTOM_DARKNESS.document : document); const divElement = document.createElement('div'); doc.body.appendChild(divElement); // patch div patch(divElement, html `
${EMOJIS.map((emoji) => { return `${emoji}`; }).join('')}
`); // SVG const reduceToObject = (nativeObj) => { const keys = Object.keys(nativeObj.__proto__); return keys.reduce((acc, key) => { const val = nativeObj[key]; const isMethod = typeof val == 'function'; return isMethod ? acc : { ...acc, [key]: val }; }, {}); }; const reduceToSum = (nativeObj) => { const keys = Object.keys(nativeObj.__proto__); return keys.reduce((acc, key) => { const val = nativeObj[key]; return isNaN(val) ? acc : (acc += val); }, 0); }; const getObjectSum = (obj) => !obj ? 0 : Object.keys(obj).reduce((acc, key) => acc += Math.abs(obj[key]), 0); // SVGRect const svgBox = doc.getElementById('svgBox'); const bBox = reduceToObject(svgBox.getBBox()); // compute SVGRect emojis const pattern = new Set(); const svgElems = [...svgBox.getElementsByClassName('svgrect-emoji')]; await queueEvent(timer); const emojiSet = svgElems.reduce((emojiSet, el, i) => { const emoji = EMOJIS[i]; const dimensions = '' + el.getComputedTextLength(); if (!pattern.has(dimensions)) { pattern.add(dimensions); emojiSet.add(emoji); } return emojiSet; }, new Set()); // svgRect System Sum const svgrectSystemSum = 0.00001 * [...pattern].map((x) => { return x.split(',').reduce((acc, x) => acc += (+x || 0), 0); }).reduce((acc, x) => acc += x, 0); // detect failed shift calculation const svgEmojiEl = svgElems[0]; const initial = svgEmojiEl.getComputedTextLength(); svgEmojiEl.classList.add('shift-svg'); const shifted = svgEmojiEl.getComputedTextLength(); svgEmojiEl.classList.remove('shift-svg'); const unshifted = svgEmojiEl.getComputedTextLength(); if ((initial - shifted) != (unshifted - shifted)) { lied = true; documentLie('SVGTextContentElement.getComputedTextLength', 'failed unshift calculation'); } const data = { bBox: getObjectSum(bBox), extentOfChar: reduceToSum(svgElems[0].getExtentOfChar(EMOJIS[0])), subStringLength: svgElems[0].getSubStringLength(0, 10), computedTextLength: svgElems[0].getComputedTextLength(), emojiSet: [...emojiSet], svgrectSystemSum, lied, }; doc.body.removeChild(doc.getElementById('svg-container')); logTestResult({ time: timer.stop(), test: 'svg', passed: true }); return data; } catch (error) { logTestResult({ test: 'svg', passed: false }); captureError(error); return; } } function svgHTML(fp) { if (!fp.svg) { return `
SVGRect
bBox: ${HTMLNote.BLOCKED}
char: ${HTMLNote.BLOCKED}
subs: ${HTMLNote.BLOCKED}
text: ${HTMLNote.BLOCKED}
${HTMLNote.BLOCKED}
`; } const { svg: { $hash, bBox, subStringLength, extentOfChar, computedTextLength, emojiSet, svgrectSystemSum, lied, }, } = fp; const divisor = 10000; const helpTitle = `SVGTextContentElement.getComputedTextLength()\nhash: ${hashMini(emojiSet)}\n${emojiSet.map((x, i) => i && (i % 6 == 0) ? `${x}\n` : x).join('')}`; return `
${performanceLogger.getLog().svg} SVGRect${hashSlice($hash)}
bBox: ${bBox ? (bBox / divisor) : HTMLNote.BLOCKED}
char: ${extentOfChar ? (extentOfChar / divisor) : HTMLNote.BLOCKED}
subs: ${subStringLength ? (subStringLength / divisor) : HTMLNote.BLOCKED}
text: ${computedTextLength ? (computedTextLength / divisor) : HTMLNote.BLOCKED}
${svgrectSystemSum || HTMLNote.UNSUPPORTED} ${formatEmojiSet(emojiSet)}
`; } function getTimezone() { // inspired by https://arkenfox.github.io/TZP // https://github.com/vvo/tzdb/blob/master/time-zones-names.json const cities = [ 'UTC', 'GMT', 'Etc/GMT+0', 'Etc/GMT+1', 'Etc/GMT+10', 'Etc/GMT+11', 'Etc/GMT+12', 'Etc/GMT+2', 'Etc/GMT+3', 'Etc/GMT+4', 'Etc/GMT+5', 'Etc/GMT+6', 'Etc/GMT+7', 'Etc/GMT+8', 'Etc/GMT+9', 'Etc/GMT-1', 'Etc/GMT-10', 'Etc/GMT-11', 'Etc/GMT-12', 'Etc/GMT-13', 'Etc/GMT-14', 'Etc/GMT-2', 'Etc/GMT-3', 'Etc/GMT-4', 'Etc/GMT-5', 'Etc/GMT-6', 'Etc/GMT-7', 'Etc/GMT-8', 'Etc/GMT-9', 'Etc/GMT', 'Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', 'Africa/Algiers', 'Africa/Asmara', 'Africa/Bamako', 'Africa/Bangui', 'Africa/Banjul', 'Africa/Bissau', 'Africa/Blantyre', 'Africa/Brazzaville', 'Africa/Bujumbura', 'Africa/Cairo', 'Africa/Casablanca', 'Africa/Ceuta', 'Africa/Conakry', 'Africa/Dakar', 'Africa/Dar_es_Salaam', 'Africa/Djibouti', 'Africa/Douala', 'Africa/El_Aaiun', 'Africa/Freetown', 'Africa/Gaborone', 'Africa/Harare', 'Africa/Johannesburg', 'Africa/Juba', 'Africa/Kampala', 'Africa/Khartoum', 'Africa/Kigali', 'Africa/Kinshasa', 'Africa/Lagos', 'Africa/Libreville', 'Africa/Lome', 'Africa/Luanda', 'Africa/Lubumbashi', 'Africa/Lusaka', 'Africa/Malabo', 'Africa/Maputo', 'Africa/Maseru', 'Africa/Mbabane', 'Africa/Mogadishu', 'Africa/Monrovia', 'Africa/Nairobi', 'Africa/Ndjamena', 'Africa/Niamey', 'Africa/Nouakchott', 'Africa/Ouagadougou', 'Africa/Porto-Novo', 'Africa/Sao_Tome', 'Africa/Tripoli', 'Africa/Tunis', 'Africa/Windhoek', 'America/Adak', 'America/Anchorage', 'America/Anguilla', 'America/Antigua', 'America/Araguaina', 'America/Argentina/Buenos_Aires', 'America/Argentina/Catamarca', 'America/Argentina/Cordoba', 'America/Argentina/Jujuy', 'America/Argentina/La_Rioja', 'America/Argentina/Mendoza', 'America/Argentina/Rio_Gallegos', 'America/Argentina/Salta', 'America/Argentina/San_Juan', 'America/Argentina/San_Luis', 'America/Argentina/Tucuman', 'America/Argentina/Ushuaia', 'America/Aruba', 'America/Asuncion', 'America/Atikokan', 'America/Bahia', 'America/Bahia_Banderas', 'America/Barbados', 'America/Belem', 'America/Belize', 'America/Blanc-Sablon', 'America/Boa_Vista', 'America/Bogota', 'America/Boise', 'America/Cambridge_Bay', 'America/Campo_Grande', 'America/Cancun', 'America/Caracas', 'America/Cayenne', 'America/Cayman', 'America/Chicago', 'America/Chihuahua', 'America/Costa_Rica', 'America/Creston', 'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', 'America/Dawson', 'America/Dawson_Creek', 'America/Denver', 'America/Detroit', 'America/Dominica', 'America/Edmonton', 'America/Eirunepe', 'America/El_Salvador', 'America/Fort_Nelson', 'America/Fortaleza', 'America/Glace_Bay', 'America/Godthab', 'America/Goose_Bay', 'America/Grand_Turk', 'America/Grenada', 'America/Guadeloupe', 'America/Guatemala', 'America/Guayaquil', 'America/Guyana', 'America/Halifax', 'America/Havana', 'America/Hermosillo', 'America/Indiana/Indianapolis', 'America/Indiana/Knox', 'America/Indiana/Marengo', 'America/Indiana/Petersburg', 'America/Indiana/Tell_City', 'America/Indiana/Vevay', 'America/Indiana/Vincennes', 'America/Indiana/Winamac', 'America/Inuvik', 'America/Iqaluit', 'America/Jamaica', 'America/Juneau', 'America/Kentucky/Louisville', 'America/Kentucky/Monticello', 'America/Kralendijk', 'America/La_Paz', 'America/Lima', 'America/Los_Angeles', 'America/Lower_Princes', 'America/Maceio', 'America/Managua', 'America/Manaus', 'America/Marigot', 'America/Martinique', 'America/Matamoros', 'America/Mazatlan', 'America/Menominee', 'America/Merida', 'America/Metlakatla', 'America/Mexico_City', 'America/Miquelon', 'America/Moncton', 'America/Monterrey', 'America/Montevideo', 'America/Montserrat', 'America/Nassau', 'America/New_York', 'America/Nipigon', 'America/Nome', 'America/Noronha', 'America/North_Dakota/Beulah', 'America/North_Dakota/Center', 'America/North_Dakota/New_Salem', 'America/Ojinaga', 'America/Panama', 'America/Pangnirtung', 'America/Paramaribo', 'America/Phoenix', 'America/Port-au-Prince', 'America/Port_of_Spain', 'America/Porto_Velho', 'America/Puerto_Rico', 'America/Punta_Arenas', 'America/Rainy_River', 'America/Rankin_Inlet', 'America/Recife', 'America/Regina', 'America/Resolute', 'America/Rio_Branco', 'America/Santarem', 'America/Santiago', 'America/Santo_Domingo', 'America/Sao_Paulo', 'America/Scoresbysund', 'America/Sitka', 'America/St_Barthelemy', 'America/St_Johns', 'America/St_Kitts', 'America/St_Lucia', 'America/St_Thomas', 'America/St_Vincent', 'America/Swift_Current', 'America/Tegucigalpa', 'America/Thule', 'America/Thunder_Bay', 'America/Tijuana', 'America/Toronto', 'America/Tortola', 'America/Vancouver', 'America/Whitehorse', 'America/Winnipeg', 'America/Yakutat', 'America/Yellowknife', 'Antarctica/Casey', 'Antarctica/Davis', 'Antarctica/DumontDUrville', 'Antarctica/Macquarie', 'Antarctica/Mawson', 'Antarctica/McMurdo', 'Antarctica/Palmer', 'Antarctica/Rothera', 'Antarctica/Syowa', 'Antarctica/Troll', 'Antarctica/Vostok', 'Arctic/Longyearbyen', 'Asia/Aden', 'Asia/Almaty', 'Asia/Amman', 'Asia/Anadyr', 'Asia/Aqtau', 'Asia/Aqtobe', 'Asia/Ashgabat', 'Asia/Atyrau', 'Asia/Baghdad', 'Asia/Bahrain', 'Asia/Baku', 'Asia/Bangkok', 'Asia/Barnaul', 'Asia/Beirut', 'Asia/Bishkek', 'Asia/Brunei', 'Asia/Calcutta', 'Asia/Chita', 'Asia/Choibalsan', 'Asia/Colombo', 'Asia/Damascus', 'Asia/Dhaka', 'Asia/Dili', 'Asia/Dubai', 'Asia/Dushanbe', 'Asia/Famagusta', 'Asia/Gaza', 'Asia/Hebron', 'Asia/Ho_Chi_Minh', 'Asia/Hong_Kong', 'Asia/Hovd', 'Asia/Irkutsk', 'Asia/Jakarta', 'Asia/Jayapura', 'Asia/Jerusalem', 'Asia/Kabul', 'Asia/Kamchatka', 'Asia/Karachi', 'Asia/Kathmandu', 'Asia/Khandyga', 'Asia/Kolkata', 'Asia/Krasnoyarsk', 'Asia/Kuala_Lumpur', 'Asia/Kuching', 'Asia/Kuwait', 'Asia/Macau', 'Asia/Magadan', 'Asia/Makassar', 'Asia/Manila', 'Asia/Muscat', 'Asia/Nicosia', 'Asia/Novokuznetsk', 'Asia/Novosibirsk', 'Asia/Omsk', 'Asia/Oral', 'Asia/Phnom_Penh', 'Asia/Pontianak', 'Asia/Pyongyang', 'Asia/Qatar', 'Asia/Qostanay', 'Asia/Qyzylorda', 'Asia/Riyadh', 'Asia/Sakhalin', 'Asia/Samarkand', 'Asia/Seoul', 'Asia/Shanghai', 'Asia/Singapore', 'Asia/Srednekolymsk', 'Asia/Taipei', 'Asia/Tashkent', 'Asia/Tbilisi', 'Asia/Tehran', 'Asia/Thimphu', 'Asia/Tokyo', 'Asia/Tomsk', 'Asia/Ulaanbaatar', 'Asia/Urumqi', 'Asia/Ust-Nera', 'Asia/Vientiane', 'Asia/Vladivostok', 'Asia/Yakutsk', 'Asia/Yangon', 'Asia/Yekaterinburg', 'Asia/Yerevan', 'Atlantic/Azores', 'Atlantic/Bermuda', 'Atlantic/Canary', 'Atlantic/Cape_Verde', 'Atlantic/Faroe', 'Atlantic/Madeira', 'Atlantic/Reykjavik', 'Atlantic/South_Georgia', 'Atlantic/St_Helena', 'Atlantic/Stanley', 'Australia/Adelaide', 'Australia/Brisbane', 'Australia/Broken_Hill', 'Australia/Currie', 'Australia/Darwin', 'Australia/Eucla', 'Australia/Hobart', 'Australia/Lindeman', 'Australia/Lord_Howe', 'Australia/Melbourne', 'Australia/Perth', 'Australia/Sydney', 'Europe/Amsterdam', 'Europe/Andorra', 'Europe/Astrakhan', 'Europe/Athens', 'Europe/Belgrade', 'Europe/Berlin', 'Europe/Bratislava', 'Europe/Brussels', 'Europe/Bucharest', 'Europe/Budapest', 'Europe/Busingen', 'Europe/Chisinau', 'Europe/Copenhagen', 'Europe/Dublin', 'Europe/Gibraltar', 'Europe/Guernsey', 'Europe/Helsinki', 'Europe/Isle_of_Man', 'Europe/Istanbul', 'Europe/Jersey', 'Europe/Kaliningrad', 'Europe/Kiev', 'Europe/Kirov', 'Europe/Lisbon', 'Europe/Ljubljana', 'Europe/London', 'Europe/Luxembourg', 'Europe/Madrid', 'Europe/Malta', 'Europe/Mariehamn', 'Europe/Minsk', 'Europe/Monaco', 'Europe/Moscow', 'Europe/Oslo', 'Europe/Paris', 'Europe/Podgorica', 'Europe/Prague', 'Europe/Riga', 'Europe/Rome', 'Europe/Samara', 'Europe/San_Marino', 'Europe/Sarajevo', 'Europe/Saratov', 'Europe/Simferopol', 'Europe/Skopje', 'Europe/Sofia', 'Europe/Stockholm', 'Europe/Tallinn', 'Europe/Tirane', 'Europe/Ulyanovsk', 'Europe/Uzhgorod', 'Europe/Vaduz', 'Europe/Vatican', 'Europe/Vienna', 'Europe/Vilnius', 'Europe/Volgograd', 'Europe/Warsaw', 'Europe/Zagreb', 'Europe/Zaporozhye', 'Europe/Zurich', 'Indian/Antananarivo', 'Indian/Chagos', 'Indian/Christmas', 'Indian/Cocos', 'Indian/Comoro', 'Indian/Kerguelen', 'Indian/Mahe', 'Indian/Maldives', 'Indian/Mauritius', 'Indian/Mayotte', 'Indian/Reunion', 'Pacific/Apia', 'Pacific/Auckland', 'Pacific/Bougainville', 'Pacific/Chatham', 'Pacific/Chuuk', 'Pacific/Easter', 'Pacific/Efate', 'Pacific/Enderbury', 'Pacific/Fakaofo', 'Pacific/Fiji', 'Pacific/Funafuti', 'Pacific/Galapagos', 'Pacific/Gambier', 'Pacific/Guadalcanal', 'Pacific/Guam', 'Pacific/Honolulu', 'Pacific/Kiritimati', 'Pacific/Kosrae', 'Pacific/Kwajalein', 'Pacific/Majuro', 'Pacific/Marquesas', 'Pacific/Midway', 'Pacific/Nauru', 'Pacific/Niue', 'Pacific/Norfolk', 'Pacific/Noumea', 'Pacific/Pago_Pago', 'Pacific/Palau', 'Pacific/Pitcairn', 'Pacific/Pohnpei', 'Pacific/Port_Moresby', 'Pacific/Rarotonga', 'Pacific/Saipan', 'Pacific/Tahiti', 'Pacific/Tarawa', 'Pacific/Tongatapu', 'Pacific/Wake', 'Pacific/Wallis', ]; const getTimezoneOffset = () => { const [year, month, day] = JSON.stringify(new Date()) .slice(1, 11) .split('-'); const dateString = `${month}/${day}/${year}`; const dateStringUTC = `${year}-${month}-${day}`; const now = +new Date(dateString); const utc = +new Date(dateStringUTC); const offset = +((now - utc) / 60000); return ~~offset; }; const getTimezoneOffsetHistory = ({ year, city = null }) => { const format = { timeZone: '', year: 'numeric', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', }; const minute = 60000; let formatter; let summer; if (city) { const options = { ...format, timeZone: city, }; // @ts-ignore formatter = new Intl.DateTimeFormat('en', options); summer = +new Date(formatter.format(new Date(`7/1/${year}`))); } else { summer = +new Date(`7/1/${year}`); } const summerUTCTime = +new Date(`${year}-07-01`); const offset = (summer - summerUTCTime) / minute; return offset; }; const binarySearch = (list, fn) => { const end = list.length; const middle = Math.floor(end / 2); const [left, right] = [list.slice(0, middle), list.slice(middle, end)]; const found = fn(left); return end == 1 || found.length ? found : binarySearch(right, fn); }; const decryptLocation = ({ year, timeZone }) => { const system = getTimezoneOffsetHistory({ year }); const resolvedOptions = getTimezoneOffsetHistory({ year, city: timeZone }); const filter = (cities) => cities .filter((city) => system == getTimezoneOffsetHistory({ year, city })); // get city region set const decryption = (system == resolvedOptions ? [timeZone] : binarySearch(cities, filter)); // reduce set to one city const decrypted = (decryption.length == 1 && decryption[0] == timeZone ? timeZone : hashMini(decryption)); return decrypted; }; const formatLocation = (x) => { try { return x.replace(/_/, ' ').split('/').join(', '); } catch (error) { } return x; }; try { const timer = createTimer(); timer.start(); const lied = (lieProps['Date.getTimezoneOffset'] || lieProps['Intl.DateTimeFormat.resolvedOptions'] || lieProps['Intl.RelativeTimeFormat.resolvedOptions']) || false; const year = 1113; // eslint-disable-next-line new-cap const { timeZone } = Intl.DateTimeFormat().resolvedOptions(); const decrypted = decryptLocation({ year, timeZone }); const locationEpoch = +new Date(new Date(`7/1/${year}`)); const notWithinParentheses = /.*\(|\).*/g; const data = { zone: ('' + new Date()).replace(notWithinParentheses, ''), location: formatLocation(timeZone), locationMeasured: formatLocation(decrypted), locationEpoch, offset: new Date().getTimezoneOffset(), offsetComputed: getTimezoneOffset(), lied, }; logTestResult({ time: timer.stop(), test: 'timezone', passed: true }); return { ...data }; } catch (error) { logTestResult({ test: 'timezone', passed: false }); captureError(error); return; } } function timezoneHTML(fp) { if (!fp.timezone) { return `
Timezone
${HTMLNote.BLOCKED}
`; } const { timezone: { $hash, zone, location, locationMeasured, locationEpoch, offset, offsetComputed, lied, }, } = fp; return `
${performanceLogger.getLog().timezone} Timezone${hashSlice($hash)}
${zone ? zone : ''}
${location != locationMeasured ? locationMeasured : location}
${locationEpoch}
${offset != offsetComputed ? offsetComputed : offset}
`; } async function getCanvasWebgl() { // use short list to improve performance const getParamNames = () => [ // 'BLEND_EQUATION', // 'BLEND_EQUATION_RGB', // 'BLEND_EQUATION_ALPHA', // 'BLEND_DST_RGB', // 'BLEND_SRC_RGB', // 'BLEND_DST_ALPHA', // 'BLEND_SRC_ALPHA', // 'BLEND_COLOR', // 'CULL_FACE', // 'BLEND', // 'DITHER', // 'STENCIL_TEST', // 'DEPTH_TEST', // 'SCISSOR_TEST', // 'POLYGON_OFFSET_FILL', // 'SAMPLE_ALPHA_TO_COVERAGE', // 'SAMPLE_COVERAGE', // 'LINE_WIDTH', 'ALIASED_POINT_SIZE_RANGE', 'ALIASED_LINE_WIDTH_RANGE', // 'CULL_FACE_MODE', // 'FRONT_FACE', // 'DEPTH_RANGE', // 'DEPTH_WRITEMASK', // 'DEPTH_CLEAR_VALUE', // 'DEPTH_FUNC', // 'STENCIL_CLEAR_VALUE', // 'STENCIL_FUNC', // 'STENCIL_FAIL', // 'STENCIL_PASS_DEPTH_FAIL', // 'STENCIL_PASS_DEPTH_PASS', // 'STENCIL_REF', 'STENCIL_VALUE_MASK', 'STENCIL_WRITEMASK', // 'STENCIL_BACK_FUNC', // 'STENCIL_BACK_FAIL', // 'STENCIL_BACK_PASS_DEPTH_FAIL', // 'STENCIL_BACK_PASS_DEPTH_PASS', // 'STENCIL_BACK_REF', 'STENCIL_BACK_VALUE_MASK', 'STENCIL_BACK_WRITEMASK', // 'VIEWPORT', // 'SCISSOR_BOX', // 'COLOR_CLEAR_VALUE', // 'COLOR_WRITEMASK', // 'UNPACK_ALIGNMENT', // 'PACK_ALIGNMENT', 'MAX_TEXTURE_SIZE', 'MAX_VIEWPORT_DIMS', 'SUBPIXEL_BITS', // 'RED_BITS', // 'GREEN_BITS', // 'BLUE_BITS', // 'ALPHA_BITS', // 'DEPTH_BITS', // 'STENCIL_BITS', // 'POLYGON_OFFSET_UNITS', // 'POLYGON_OFFSET_FACTOR', // 'SAMPLE_BUFFERS', // 'SAMPLES', // 'SAMPLE_COVERAGE_VALUE', // 'SAMPLE_COVERAGE_INVERT', // 'COMPRESSED_TEXTURE_FORMATS', // 'GENERATE_MIPMAP_HINT', 'MAX_VERTEX_ATTRIBS', 'MAX_VERTEX_UNIFORM_VECTORS', 'MAX_VARYING_VECTORS', 'MAX_COMBINED_TEXTURE_IMAGE_UNITS', 'MAX_VERTEX_TEXTURE_IMAGE_UNITS', 'MAX_TEXTURE_IMAGE_UNITS', 'MAX_FRAGMENT_UNIFORM_VECTORS', 'SHADING_LANGUAGE_VERSION', 'VENDOR', 'RENDERER', 'VERSION', 'MAX_CUBE_MAP_TEXTURE_SIZE', // 'ACTIVE_TEXTURE', // 'IMPLEMENTATION_COLOR_READ_TYPE', // 'IMPLEMENTATION_COLOR_READ_FORMAT', 'MAX_RENDERBUFFER_SIZE', // 'UNPACK_FLIP_Y_WEBGL', // 'UNPACK_PREMULTIPLY_ALPHA_WEBGL', // 'UNPACK_COLORSPACE_CONVERSION_WEBGL', // 'READ_BUFFER', // 'UNPACK_ROW_LENGTH', // 'UNPACK_SKIP_ROWS', // 'UNPACK_SKIP_PIXELS', // 'PACK_ROW_LENGTH', // 'PACK_SKIP_ROWS', // 'PACK_SKIP_PIXELS', // 'UNPACK_SKIP_IMAGES', // 'UNPACK_IMAGE_HEIGHT', 'MAX_3D_TEXTURE_SIZE', 'MAX_ELEMENTS_VERTICES', 'MAX_ELEMENTS_INDICES', 'MAX_TEXTURE_LOD_BIAS', 'MAX_DRAW_BUFFERS', // 'DRAW_BUFFER0', // 'DRAW_BUFFER1', // 'DRAW_BUFFER2', // 'DRAW_BUFFER3', // 'DRAW_BUFFER4', // 'DRAW_BUFFER5', // 'DRAW_BUFFER6', // 'DRAW_BUFFER7', 'MAX_FRAGMENT_UNIFORM_COMPONENTS', 'MAX_VERTEX_UNIFORM_COMPONENTS', // 'FRAGMENT_SHADER_DERIVATIVE_HINT', 'MAX_ARRAY_TEXTURE_LAYERS', // 'MIN_PROGRAM_TEXEL_OFFSET', 'MAX_PROGRAM_TEXEL_OFFSET', 'MAX_VARYING_COMPONENTS', 'MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS', // 'RASTERIZER_DISCARD', 'MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS', 'MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS', 'MAX_COLOR_ATTACHMENTS', 'MAX_SAMPLES', 'MAX_VERTEX_UNIFORM_BLOCKS', 'MAX_FRAGMENT_UNIFORM_BLOCKS', 'MAX_COMBINED_UNIFORM_BLOCKS', 'MAX_UNIFORM_BUFFER_BINDINGS', 'MAX_UNIFORM_BLOCK_SIZE', 'MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS', 'MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS', // 'UNIFORM_BUFFER_OFFSET_ALIGNMENT', 'MAX_VERTEX_OUTPUT_COMPONENTS', 'MAX_FRAGMENT_INPUT_COMPONENTS', 'MAX_SERVER_WAIT_TIMEOUT', // 'TRANSFORM_FEEDBACK_PAUSED', // 'TRANSFORM_FEEDBACK_ACTIVE', 'MAX_ELEMENT_INDEX', 'MAX_CLIENT_WAIT_TIMEOUT_WEBGL', ].sort(); const draw = (gl) => { const isSafari15AndAbove = ('BigInt64Array' in window && IS_WEBKIT && !/(Cr|Fx)iOS/.test(navigator.userAgent)); if (!gl || isSafari15AndAbove) { return; } // gl.clearColor(0.47, 0.7, 0.78, 1) gl.clear(gl.COLOR_BUFFER_BIT); // based on https://github.com/Valve/fingerprintjs2/blob/master/fingerprint2.js const vertexPosBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); const vertices = new Float32Array([-0.9, -0.7, 0, 0.8, -0.7, 0, 0, 0.5, 0]); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // create program const program = gl.createProgram(); // compile and attach vertex shader const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, ` attribute vec2 attrVertex; varying vec2 varyinTexCoordinate; uniform vec2 uniformOffset; void main(){ varyinTexCoordinate = attrVertex + uniformOffset; gl_Position = vec4(attrVertex, 0, 1); } `); gl.compileShader(vertexShader); gl.attachShader(program, vertexShader); // compile and attach fragment shader const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, ` precision mediump float; varying vec2 varyinTexCoordinate; void main() { gl_FragColor = vec4(varyinTexCoordinate, 1, 1); } `); gl.compileShader(fragmentShader); gl.attachShader(program, fragmentShader); // use program const componentSize = 3; gl.linkProgram(program); gl.useProgram(program); program.vertexPosAttrib = gl.getAttribLocation(program, 'attrVertex'); program.offsetUniform = gl.getUniformLocation(program, 'uniformOffset'); gl.enableVertexAttribArray(program.vertexPosArray); gl.vertexAttribPointer(program.vertexPosAttrib, componentSize, gl.FLOAT, false, 0, 0); gl.uniform2f(program.offsetUniform, 1, 1); // draw const numOfIndices = 3; gl.drawArrays(gl.LINE_LOOP, 0, numOfIndices); return gl; }; try { const timer = createTimer(); await queueEvent(timer); // detect lies const dataLie = lieProps['HTMLCanvasElement.toDataURL']; const contextLie = lieProps['HTMLCanvasElement.getContext']; const parameterOrExtensionLie = (lieProps['WebGLRenderingContext.getParameter'] || lieProps['WebGL2RenderingContext.getParameter'] || lieProps['WebGLRenderingContext.getExtension'] || lieProps['WebGL2RenderingContext.getExtension']); const lied = (dataLie || contextLie || parameterOrExtensionLie || lieProps['WebGLRenderingContext.getSupportedExtensions'] || lieProps['WebGL2RenderingContext.getSupportedExtensions']) || false; // create canvas context let win = window; if (!LIKE_BRAVE && PHANTOM_DARKNESS) { win = PHANTOM_DARKNESS; } const doc = win.document; let canvas; let canvas2; if ('OffscreenCanvas' in window) { // @ts-ignore OffscreenCanvas canvas = new win.OffscreenCanvas(256, 256); // @ts-ignore OffscreenCanvas canvas2 = new win.OffscreenCanvas(256, 256); } else { canvas = doc.createElement('canvas'); canvas2 = doc.createElement('canvas'); } const getContext = (canvas, contextType) => { try { if (contextType == 'webgl2') { return (canvas.getContext('webgl2') || canvas.getContext('experimental-webgl2')); } return (canvas.getContext('webgl') || canvas.getContext('experimental-webgl') || canvas.getContext('moz-webgl') || canvas.getContext('webkit-3d')); } catch (error) { return; } }; const gl = getContext(canvas, 'webgl'); const gl2 = getContext(canvas2, 'webgl2'); if (!gl) { logTestResult({ test: 'webgl', passed: false }); return; } // helpers const getShaderPrecisionFormat = (gl, shaderType) => { if (!gl) { return; } const LOW_FLOAT = attempt(() => gl.getShaderPrecisionFormat(gl[shaderType], gl.LOW_FLOAT)); const MEDIUM_FLOAT = attempt(() => gl.getShaderPrecisionFormat(gl[shaderType], gl.MEDIUM_FLOAT)); const HIGH_FLOAT = attempt(() => gl.getShaderPrecisionFormat(gl[shaderType], gl.HIGH_FLOAT)); const HIGH_INT = attempt(() => gl.getShaderPrecisionFormat(gl[shaderType], gl.HIGH_INT)); return { LOW_FLOAT, MEDIUM_FLOAT, HIGH_FLOAT, HIGH_INT, }; }; const getShaderData = (name, shader) => { const data = {}; // eslint-disable-next-line guard-for-in for (const prop in shader) { const obj = shader[prop]; data[name + '.' + prop + '.precision'] = obj ? attempt(() => obj.precision) : undefined; data[name + '.' + prop + '.rangeMax'] = obj ? attempt(() => obj.rangeMax) : undefined; data[name + '.' + prop + '.rangeMin'] = obj ? attempt(() => obj.rangeMin) : undefined; } return data; }; const getMaxAnisotropy = (gl) => { if (!gl) { return; } const ext = (gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic')); return ext ? gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT) : undefined; }; const getParams = (gl) => { if (!gl) { return {}; } const pnamesShortList = new Set(getParamNames()); const pnames = Object.getOwnPropertyNames(Object.getPrototypeOf(gl)) // .filter(prop => prop.toUpperCase() == prop) // global test .filter((name) => pnamesShortList.has(name)); return pnames.reduce((acc, name) => { const val = gl.getParameter(gl[name]); if (!!val && 'buffer' in Object.getPrototypeOf(val)) { acc[name] = [...val]; } else { acc[name] = val; } return acc; }, {}); }; const getUnmasked = (gl) => { const ext = !!gl ? gl.getExtension('WEBGL_debug_renderer_info') : null; return !ext ? {} : { UNMASKED_VENDOR_WEBGL: gl.getParameter(ext.UNMASKED_VENDOR_WEBGL), UNMASKED_RENDERER_WEBGL: gl.getParameter(ext.UNMASKED_RENDERER_WEBGL), }; }; const getSupportedExtensions = (gl) => { if (!gl) { return []; } const ext = attempt(() => gl.getSupportedExtensions()); if (!ext) { return []; } return ext; }; const getWebGLData = (gl, contextType) => { if (!gl) { return { dataURI: undefined, pixels: undefined, }; } try { draw(gl); const { drawingBufferWidth, drawingBufferHeight } = gl; let dataURI = ''; if (gl.canvas.constructor.name === 'OffscreenCanvas') { const canvas = document.createElement('canvas'); draw(getContext(canvas, contextType)); dataURI = canvas.toDataURL(); } else { dataURI = gl.canvas.toDataURL(); } // reduce excessive reads to improve performance const width = drawingBufferWidth / 15; const height = drawingBufferHeight / 6; const pixels = new Uint8Array(width * height * 4); try { gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); } catch (error) { return { dataURI, pixels: undefined, }; } // console.log([...pixels].filter(x => !!x)) // test read return { dataURI, pixels: [...pixels], }; } catch (error) { return captureError(error); } }; // get data await queueEvent(timer); const params = { ...getParams(gl), ...getUnmasked(gl) }; const params2 = { ...getParams(gl2), ...getUnmasked(gl2) }; const VersionParam = { ALIASED_LINE_WIDTH_RANGE: true, SHADING_LANGUAGE_VERSION: true, VERSION: true, }; const mismatch = Object.keys(params2) .filter((key) => !!params[key] && !VersionParam[key] && ('' + params[key] != '' + params2[key])); if (mismatch.length) { sendToTrash('webgl/webgl2 mirrored params mismatch', mismatch.toString()); } await queueEvent(timer); const { dataURI, pixels } = getWebGLData(gl, 'webgl') || {}; const { dataURI: dataURI2, pixels: pixels2 } = getWebGLData(gl2, 'webgl2') || {}; const data = { extensions: [...getSupportedExtensions(gl), ...getSupportedExtensions(gl2)], pixels, pixels2, dataURI, dataURI2, parameters: { ...{ ...params, ...params2 }, ...{ antialias: gl.getContextAttributes() ? gl.getContextAttributes().antialias : undefined, MAX_VIEWPORT_DIMS: attempt(() => [...gl.getParameter(gl.MAX_VIEWPORT_DIMS)]), MAX_TEXTURE_MAX_ANISOTROPY_EXT: getMaxAnisotropy(gl), ...getShaderData('VERTEX_SHADER', getShaderPrecisionFormat(gl, 'VERTEX_SHADER')), ...getShaderData('FRAGMENT_SHADER', getShaderPrecisionFormat(gl, 'FRAGMENT_SHADER')), MAX_DRAW_BUFFERS_WEBGL: attempt(() => { const buffers = gl.getExtension('WEBGL_draw_buffers'); return buffers ? gl.getParameter(buffers.MAX_DRAW_BUFFERS_WEBGL) : undefined; }), }, }, parameterOrExtensionLie, lied, }; // Firewall const brandCapabilities = ['00b72507', '00c1b42d', '00fe1ec9', '02b3eea3', '0461d3de', '0463627d', '057857ac', '0586e20b', '0639a81a', '087d5759', '08847ba5', '0b2d4333', '0cdb985d', '0e058699', '0eb2fc19', '0f39d057', '0f840379', '0fc123c7', '101e0582', '12e92e62', '12f8ac14', '1453d59a', '149a1efa', '166dc7c8', '16c481a6', '171831c5', '177cc258', '18579e83', '19594666', '1b251fd7', '1bfd326c', '1e8a9a79', '1ff7c7e7', '2048bc5a', '2259b706', '22d0f2cf', '230d6a0d', '23d1ce20', '2402c3d2', '24306836', '258789d0', '25a760b8', '25f9385d', '27938830', '27db292c', '2b80fd96', '2bb488da', '2c04c2eb', '2d15287f', '2f014c41', '2f582ed9', '300ee927', '33bc5492', '34270469', '3660b71f', '3740c4c7', '3999a5e1', '39ead506', '3a91d0d6', '3b724916', '3bf321b8', '3c546144', '3f9ef44c', '3fea1100', '3ff82303', '4027d193', '402e1064', '4065cd69', '43038e3d', '4503e771', '461f97e1', '464d51ac', '467b99a5', '482c81b2', '48af038f', '4962ada1', '49bf7358', '4c9e8f5d', '502c402c', '508d1625', '52e348ba', '534002ab', '5582debe', '55d3aa56', '55e821f7', '581f3282', '5831d5fd', '58871380', '58fdc720', '5a5658f1', '5a90a5f8', '5aea1af1', '5b6a17aa', '5bef9a39', '5ca55292', '5d786cef', '5ddb9237', '5ee41456', '61178f2a', '61ca8e23', '61d9464e', '61eecaae', '623c3bfd', '6248d9e3', '6294d84e', '62bf7ef1', '6346cf49', '6357365c', '66628310', '668f0f93', '66d992e8', '67995996', '6843ebbf', '6864dcb0', '6951838b', '696e1548', '698c5c2e', '6a75ae3b', '6aa1ff7e', '6b07d4f8', '6b290cd4', '6c168801', '6dfae3cb', '6e806ffc', '6edf1720', '6f81cbe7', '70859bdb', '70a095b1', '7238c5dd', '7360ebd1', '741688e4', '74daf866', '78640859', '79284c47', '794f8929', '795e5c95', '79a57aa9', '7aa13573', '7b2e5242', '7b811cdd', '7ec0ea6b', '801d73af', '802e2547', '81b9cd29', '8219e1a4', '82a9a2f1', '8428fc8e', '849ccb64', '8541aa4c', '85479b99', '8bd0b91b', '8d371161', '903c8847', '917871e7', '98aeaba9', '99b1a1c6', '99ef2c3b', '9b67b7dc', '9c6df98c', '9c814c1b', '9e2b5e94', '9fd76352', 'a1c808d5', 'a22788f8', 'a2383001', 'a26e9aa9', 'a397a568', 'a3f9ee34', 'a4b988da', 'a4d34176', 'a581f55e', 'a5a477ae', 'a9640880', 'a97d3858', 'aa73f3a4', 'ab40bece', 'ac4d4ba8', 'ad01a422', 'ade75c4f', 'ae2c4777', 'afa583bc', 'b10c2a85', 'b224cc7c', 'b2d6fc98', 'b362c2f5', 'b467620a', 'b4d40dcc', 'b504662d', 'b50edd99', 'b5494027', 'b62321c3', 'b8961d15', 'b8ea6e7f', 'bb77a469', 'bc0f9686', 'bcf7315f', 'be2dfaea', 'beffda26', 'bf06317e', 'bf610cdb', 'bfe1c212', 'c00582e9', 'c026469d', 'c04889b1', 'c04b0635', 'c04e374a', 'c05f7596', 'c07307c6', 'c092fdf8', 'c25dd065', 'c2bce496', 'c5e9a883', 'c79634c2', 'c7e37ca0', 'c93b5366', 'c9bc4ffd', 'cba1878b', 'cbeade8c', 'ce2e3d16', 'cefb72ca', 'cf9643e6', 'cfd20274', 'd05a66eb', 'd09c1c07', 'd1e76c89', 'd2172943', 'd2dc2474', 'd498797d', 'd6bf35ad', 'd734ea08', 'd860ff42', 'd8bd9e5a', 'd913dafa', 'd970d345', 'dbdbe7a4', 'dc271c35', 'dcd9a29e', 'dd67b076', 'de793ead', 'ded74044', 'df9daeb6', 'e10339b3', 'e142d1f9', 'e155c47e', 'e15afab0', 'e16bb1bb', 'e316e4c0', 'e3eff92a', 'e4569a5b', 'e574bef6', 'e5962ba3', 'e6464c9f', 'e68b5c4e', 'e796b84e', 'e8694547', 'e965d180', 'e965d541', 'e9bdc904', 'e9dbb8d5', 'ea54d525', 'ea59b343', 'ea7f90ea', 'ea8f5ad0', 'eaa13804', 'eb799d34', 'ec050bb6', 'ec928655', 'eed2e5e1', 'ef8f5db1', 'f0d5a3c7', 'f1077334', 'f221fef5', 'f2293447', 'f33d918e', 'f3c6ea11', 'f51056a1', 'f51cab9a', 'f573bb34', 'f5d19934', 'f7451c92', 'f8e65486', 'f9714b3d', 'fa994f33', 'fafa14c0', 'fc37fe1f', 'fca66520', 'fe0997b6']; const capabilities = [-1056897629, -1056946782, -1073719331, -1147160399, -1147160553, -1147168724, -1147419751, -1147419753, -1147419775, -1147427826, -1147451883, -1147451901, -1147464169, -1147464177, -1147488144, -1147602934, -1147643759, -1147643872, -1147765274, -1148326739, -1148335070, -1148572354, -1148678631, -1148680509, -1148713259, -1164279890, -1164800191, -1164800478, -1332029332, -133757475, -1342154787, -134823971, -16746546, -1878102921, -1878111124, -1962893370, -1962919974, -1962928178, -2130164162, -2130164382, -2130164388, -2130164546, -2130172573, -2130659912, -2145933648, -2145941977, -2145958228, -2145966414, -2145966441, -2145966529, -2145966535, -2145966545, -2145970658, -2145974343, -2145974380, -2145974489, -2145974596, -2145974598, -2145974612, -2145974637, -2145974657, -2145974729, -2146187766, -2146232338, -2146232480, -2146232503, -2146232590, -2146232723, -2146232724, -2146236588, -2146236703, -2146237020, -2146251619, -2146251641, -2146251681, -2146253671, -2146253693, -2146277218, -2146286438, -2146286463, -2146286583, -2146319268, -2146376065, -2146379955, -2146384003, -2146384011, -2146384027, -2146384034, -2146384120, -2146384281, -2146398568, -2146400384, -2146400556, -2146400620, -2146401928, -2146417027, -2146526795, -2146526934, -2147125544, -2147128275, -2147133747, -2147133749, -2147133760, -2147134974, -2147136328, -2147142429, -2147287810, -2147287811, -2147287820, -2147287834, -2147287835, -2147287854, -2147291718, -2147291820, -2147293058, -2147295768, -2147295822, -2147295823, -2147295849, -2147295857, -2147300019, -2147304193, -2147304219, -2147306321, -2147316382, -2147316383, -2147333118, -2147336998, -2147337003, -2147337012, -2147337022, -2147344686, -2147346747, -2147361652, -2147361731, -2147361769, -2147361774, -2147361775, -2147361778, -2147361792, -2147362760, -2147365698, -2147365730, -2147365759, -2147365760, -2147365827, -2147365863, -2147373914, -2147373984, -2147374032, -2147374080, -2147378041, -2147378146, -2147382130, -2147382221, -2147382251, -2147382270, -2147382272, -2147383246, -2147385825, -2147385849, -2147386292, -2147386326, -2147387335, -2147387364, -2147389930, -2147389937, -2147389951, -2147390461, -2147394188, -2147394251, -2147394484, -2147400057, -2147406798, -2147407643, -2147407821, -2147410938, -2147410941, -2147414733, -2147414956, -2147414987, -2147415037, -2147429201, -2147429223, -2147439020, -2147440422, -2147447111, -2147447122, -2147447126, -2147447137, -2147447149, -2147447157, -2147447161, -2147447163, -2147447873, -2147447892, -2147447896, -2147447928, -2147448592, -2147453701, -2147453767, -2147453768, -2147459031, -2147461169, -2147466956, -2147466972, -2147467172, -2147470173, -2147475351, -2147475352, -638494755, -671082546, -677558160, -999987216, 1099536, 1099644, 1147714426, 1197075, 1229835, 1508998, 1509050, 1610618841, 184555483, 2146590728, 2147305224, 2147361749, 2147440438, 2147475085, 2147479181, 21667, 349912, 351513, 83625, 998804992, 998911268, 999148597, 999156922]; const webglParams = !data.parameters ? undefined : [ ...new Set(Object.values(data.parameters) .filter((val) => val && typeof val != 'string') .flat() .map((val) => Number(val))), ].sort((a, b) => (a - b)); const gpuBrand = getGpuBrand(data.parameters?.UNMASKED_RENDERER_WEBGL); const webglParamsStr = '' + webglParams; const webglBrandCapabilities = !gpuBrand || !webglParamsStr ? undefined : hashMini([gpuBrand, webglParamsStr]); const webglCapabilities = !webglParams ? undefined : webglParams.reduce((acc, val, i) => acc ^ (+val + i), 0); Analysis.webglParams = webglParamsStr; Analysis.webglBrandCapabilities = webglBrandCapabilities; Analysis.webglCapabilities = webglCapabilities; const hasSusGpu = webglBrandCapabilities && !brandCapabilities.includes(webglBrandCapabilities); const hasSusCapabilities = webglCapabilities && !capabilities.includes(webglCapabilities); if (hasSusGpu) { LowerEntropy.WEBGL = true; sendToTrash('WebGLRenderingContext.getParameter', 'suspicious gpu'); } if (hasSusCapabilities) { LowerEntropy.WEBGL = true; sendToTrash('WebGLRenderingContext.getParameter', 'suspicious capabilities'); } logTestResult({ time: timer.stop(), test: 'webgl', passed: true }); return { ...data, gpu: { ...(getWebGLRendererConfidence((data.parameters || {}).UNMASKED_RENDERER_WEBGL) || {}), compressedGPU: compressWebGLRenderer((data.parameters || {}).UNMASKED_RENDERER_WEBGL), }, }; } catch (error) { logTestResult({ test: 'webgl', passed: false }); captureError(error); return; } } function webglHTML(fp) { if (!fp.canvasWebgl) { return `
WebGL
images: ${HTMLNote.BLOCKED}
pixels: ${HTMLNote.BLOCKED}
params (0): ${HTMLNote.BLOCKED}
exts (0): ${HTMLNote.BLOCKED}
gpu:
${HTMLNote.BLOCKED}
`; } const { canvasWebgl: data } = fp; const id = 'creep-canvas-webgl'; const { $hash, dataURI, dataURI2, pixels, pixels2, lied, extensions, parameters, gpu, } = data || {}; const { parts, warnings, gibbers, confidence, grade: confidenceGrade, compressedGPU, } = gpu || {}; const paramKeys = parameters ? Object.keys(parameters).sort() : []; return `
${performanceLogger.getLog().webgl} WebGL${hashSlice($hash)}
images:${!dataURI ? ' ' + HTMLNote.BLOCKED : `${hashMini(dataURI)}${!dataURI2 || dataURI == dataURI2 ? '' : `${hashMini(dataURI2)}`}`}
pixels:${!pixels ? ' ' + HTMLNote.BLOCKED : `${hashSlice(pixels)}${!pixels2 || pixels == pixels2 ? '' : `${hashSlice(pixels2)}`}`}
params (${count(paramKeys)}): ${!paramKeys.length ? HTMLNote.BLOCKED : modal(`${id}-parameters`, paramKeys.map((key) => `${key}: ${parameters[key]}`).join('
'), hashMini(parameters))}
exts (${count(extensions)}): ${!extensions.length ? HTMLNote.BLOCKED : modal(`${id}-extensions`, extensions.sort().join('
'), hashMini(extensions))}
gpu:${confidence ? `confidence: ${confidence}` : ''}
${parameters.UNMASKED_VENDOR_WEBGL ? parameters.UNMASKED_VENDOR_WEBGL : ''} ${!parameters.UNMASKED_RENDERER_WEBGL ? HTMLNote.BLOCKED : `
${parameters.UNMASKED_RENDERER_WEBGL}`}
${!dataURI ? '
' : ``}
`; } async function getWebRTCDevices() { if (!navigator?.mediaDevices?.enumerateDevices) return null; return navigator.mediaDevices.enumerateDevices().then((devices) => { return devices.map((device) => device.kind).sort(); }); } const getExtensions = (sdp) => { const extensions = (('' + sdp).match(/extmap:\d+ [^\n|\r]+/g) || []) .map((x) => x.replace(/extmap:[^\s]+ /, '')); return [...new Set(extensions)].sort(); }; const createCounter = () => { let counter = 0; return { increment: () => counter += 1, getValue: () => counter, }; }; // https://webrtchacks.com/sdp-anatomy/ // https://tools.ietf.org/id/draft-ietf-rtcweb-sdp-08.html const constructDescriptions = ({ mediaType, sdp, sdpDescriptors, rtxCounter }) => { if (!('' + sdpDescriptors)) { return; } return sdpDescriptors.reduce((descriptionAcc, descriptor) => { const matcher = `(rtpmap|fmtp|rtcp-fb):${descriptor} (.+)`; const formats = (sdp.match(new RegExp(matcher, 'g')) || []); if (!('' + formats)) { return descriptionAcc; } const isRtxCodec = ('' + formats).includes(' rtx/'); if (isRtxCodec) { if (rtxCounter.getValue()) { return descriptionAcc; } rtxCounter.increment(); } const getLineData = (x) => x.replace(/[^\s]+ /, ''); const description = formats.reduce((acc, x) => { const rawData = getLineData(x); const data = rawData.split('/'); const codec = data[0]; const description = {}; if (x.includes('rtpmap')) { if (mediaType == 'audio') { description.channels = (+data[2]) || 1; } description.mimeType = `${mediaType}/${codec}`; description.clockRates = [+data[1]]; return { ...acc, ...description, }; } else if (x.includes('rtcp-fb')) { return { ...acc, feedbackSupport: [...(acc.feedbackSupport || []), rawData], }; } else if (isRtxCodec) { return acc; // no sdpFmtpLine } return { ...acc, sdpFmtpLine: [...rawData.split(';')] }; }, {}); let shouldMerge = false; const mergerAcc = descriptionAcc.map((x) => { shouldMerge = x.mimeType == description.mimeType; if (shouldMerge) { if (x.feedbackSupport) { x.feedbackSupport = [ ...new Set([...x.feedbackSupport, ...description.feedbackSupport]), ]; } if (x.sdpFmtpLine) { x.sdpFmtpLine = [ ...new Set([...x.sdpFmtpLine, ...description.sdpFmtpLine]), ]; } return { ...x, clockRates: [ ...new Set([...x.clockRates, ...description.clockRates]), ], }; } return x; }); if (shouldMerge) { return mergerAcc; } return [...descriptionAcc, description]; }, []); }; const getCapabilities = (sdp) => { const videoDescriptors = ((/m=video [^\s]+ [^\s]+ ([^\n|\r]+)/.exec(sdp) || [])[1] || '').split(' '); const audioDescriptors = ((/m=audio [^\s]+ [^\s]+ ([^\n|\r]+)/.exec(sdp) || [])[1] || '').split(' '); const rtxCounter = createCounter(); return { audio: constructDescriptions({ mediaType: 'audio', sdp, sdpDescriptors: audioDescriptors, rtxCounter, }), video: constructDescriptions({ mediaType: 'video', sdp, sdpDescriptors: videoDescriptors, rtxCounter, }), }; }; const getIPAddress = (sdp) => { const blocked = '0.0.0.0'; const candidateEncoding = /((udp|tcp)\s)((\d|\w)+\s)((\d|\w|(\.|\:))+)(?=\s)/ig; const connectionLineEncoding = /(c=IN\s)(.+)\s/ig; const connectionLineIpAddress = ((sdp.match(connectionLineEncoding) || [])[0] || '').trim().split(' ')[2]; if (connectionLineIpAddress && (connectionLineIpAddress != blocked)) { return connectionLineIpAddress; } const candidateIpAddress = ((sdp.match(candidateEncoding) || [])[0] || '').split(' ')[2]; return candidateIpAddress && (candidateIpAddress != blocked) ? candidateIpAddress : undefined; }; async function getWebRTCData() { return new Promise(async (resolve) => { if (!window.RTCPeerConnection) { return resolve(null); } const config = { iceCandidatePoolSize: 1, iceServers: [ { urls: [ 'stun:stun4.l.google.com:19302', 'stun:stun3.l.google.com:19302', // 'stun:stun2.l.google.com:19302', // 'stun:stun1.l.google.com:19302', // 'stun:stun.l.google.com:19302', ], }, ], }; const connection = new RTCPeerConnection(config); connection.createDataChannel(''); const options = { offerToReceiveAudio: 1, offerToReceiveVideo: 1 }; const offer = await connection.createOffer(options); connection.setLocalDescription(offer); const { sdp } = offer || {}; const extensions = getExtensions(sdp); const codecsSdp = getCapabilities(sdp); let iceCandidate = ''; let foundation = ''; const giveUpOnIPAddress = setTimeout(() => { connection.removeEventListener('icecandidate', computeCandidate); connection.close(); if (sdp) { return resolve({ codecsSdp, extensions, foundation, iceCandidate, }); } return resolve(null); }, 3000); const computeCandidate = (event) => { const { candidate, foundation: foundationProp } = event.candidate || {}; if (!candidate) { return; } if (!iceCandidate) { iceCandidate = candidate; foundation = (/^candidate:([\w]+)/.exec(candidate) || [])[1] || ''; } const { sdp } = connection.localDescription || {}; const address = getIPAddress(sdp); if (!address) { return; } const knownInterface = { 842163049: 'public interface', 2268587630: 'WireGuard', }; connection.removeEventListener('icecandidate', computeCandidate); clearTimeout(giveUpOnIPAddress); connection.close(); return resolve({ codecsSdp, extensions, foundation: knownInterface[foundation] || foundation, foundationProp, iceCandidate, address, stunConnection: candidate, }); }; connection.addEventListener('icecandidate', computeCandidate); }); } function webrtcHTML(webRTC, mediaDevices) { if (!webRTC && !mediaDevices) { return `
WebRTC
host connection:
${HTMLNote.BLOCKED}
foundation/ip:
${HTMLNote.BLOCKED}
sdp capabilities: ${HTMLNote.BLOCKED}
stun connection:
${HTMLNote.BLOCKED}
devices (0): ${HTMLNote.BLOCKED}
${HTMLNote.BLOCKED}
`; } const { codecsSdp, extensions, foundation, foundationProp, iceCandidate, address, stunConnection, } = webRTC || {}; const { audio, video } = codecsSdp || {}; const id = 'creep-webrtc'; const webRTCHash = hashMini({ codecsSdp, extensions, foundation, foundationProp, address, mediaDevices, }); const deviceMap = { 'audioinput': 'mic', 'audiooutput': 'audio', 'videoinput': 'webcam', }; const feedbackId = { 'ccm fir': 'Codec Control Message Full Intra Request (ccm fir)', 'goog-remb': 'Google\'s Receiver Estimated Maximum Bitrate (goog-remb)', 'nack': 'Negative ACKs (nack)', 'nack pli': 'Picture loss Indication and NACK (nack pli)', 'transport-cc': 'Transport Wide Congestion Control (transport-cc)', }; const replaceIndex = ({ list, index, replacement }) => [ ...list.slice(0, index), replacement, ...list.slice(index + 1), ]; const mediaDevicesByType = (mediaDevices || []).reduce((acc, x) => { const deviceType = deviceMap[x] || x; if (!acc.includes(deviceType)) { return (acc = [...acc, deviceType]); } else if (!deviceType.includes('dual') && (acc.filter((x) => x == deviceType) || []).length == 1) { return (acc = replaceIndex({ list: acc, index: acc.indexOf(deviceType), replacement: `dual ${deviceType}`, })); } return (acc = [...acc, deviceType]); }, []); const getModalTemplate = (list) => (list || []).map((x) => { return ` ${x.mimeType}
Clock Rates: ${x.clockRates.sort((a, b) => b - a).join(', ')} ${x.channels > 1 ? `
Channels: ${x.channels}` : ''} ${x.sdpFmtpLine ? `
Format Specific Parameters:
- ${x.sdpFmtpLine.sort().map((x) => x.replace('=', ': ')).join('
- ')}` : ''} ${x.feedbackSupport ? `
Feedback Support:
- ${x.feedbackSupport.map((x) => { return feedbackId[x] || x; }).sort().join('
- ')}` : ''} `; }).join('

'); return `
WebRTC${webRTCHash}
host connection:
${iceCandidate || HTMLNote.BLOCKED}
foundation/ip:
${foundation ? `type & base ip: ${foundation}` : HTMLNote.UNSUPPORTED}
${address ? `ip: ${address}` : HTMLNote.BLOCKED}
sdp capabilities: ${!codecsSdp ? HTMLNote.BLOCKED : modal(`${id}-sdp-capabilities`, getModalTemplate(audio) + '

' + getModalTemplate(video) + '

extensions
' + extensions.join('
'), hashMini({ audio, video, extensions }))}
stun connection:
${stunConnection || HTMLNote.BLOCKED}
devices (${count(mediaDevices)}):
${!mediaDevices || !mediaDevices.length ? HTMLNote.BLOCKED : mediaDevicesByType.join(', ')}
`; } function getWindowFeatures() { try { const timer = createTimer(); timer.start(); const win = PHANTOM_DARKNESS || window; let keys = Object.getOwnPropertyNames(win) .filter((key) => !/_|\d{3,}/.test(key)); // clear out known ddg noise // if Firefox, remove the 'Event' key and push to end for consistent order // and disregard keys known to be missing in RFP mode const firefoxKeyMovedByInspect = 'Event'; const varyingKeysMissingInRFP = ['PerformanceNavigationTiming', 'Performance']; if (IS_GECKO) { const index = keys.indexOf(firefoxKeyMovedByInspect); if (index != -1) { keys = keys.slice(0, index).concat(keys.slice(index + 1)); keys = [...keys, firefoxKeyMovedByInspect]; } varyingKeysMissingInRFP.forEach((key) => { const index = keys.indexOf(key); if (index != -1) { keys = keys.slice(0, index).concat(keys.slice(index + 1)); } return keys; }); } const moz = keys.filter((key) => (/moz/i).test(key)).length; const webkit = keys.filter((key) => (/webkit/i).test(key)).length; const apple = keys.filter((key) => (/apple/i).test(key)).length; const data = { keys, apple, moz, webkit }; logTestResult({ time: timer.stop(), test: 'window', passed: true }); return { ...data }; } catch (error) { logTestResult({ test: 'window', passed: false }); captureError(error); return; } } function windowFeaturesHTML(fp) { if (!fp.windowFeatures) { return `
Window
keys (0): ${HTMLNote.BLOCKED}
`; } const { windowFeatures: { $hash, keys, }, } = fp; return `
${performanceLogger.getLog().window} Window${hashSlice($hash)}
keys (${count(keys)}): ${keys && keys.length ? modal('creep-iframe-content-window-version', keys.join(', ')) : HTMLNote.BLOCKED}
`; } !async function () { const scope = await spawnWorker(); if (scope == 0 /* Scope.WORKER */) { return; } const isBrave = IS_BLINK ? await braveBrowser() : false; const braveMode = isBrave ? getBraveMode() : {}; const braveFingerprintingBlocking = isBrave && (braveMode.standard || braveMode.strict); const fingerprint = async () => { const timeStart = timer(); const fingerprintTimeStart = timer(); // @ts-ignore const [workerScopeComputed, voicesComputed, offlineAudioContextComputed, canvasWebglComputed, canvas2dComputed, windowFeaturesComputed, htmlElementVersionComputed, cssComputed, cssMediaComputed, screenComputed, mathsComputed, consoleErrorsComputed, timezoneComputed, clientRectsComputed, fontsComputed, mediaComputed, svgComputed, resistanceComputed, intlComputed,] = await Promise.all([ getBestWorkerScope(), getVoices(), getOfflineAudioContext(), getCanvasWebgl(), getCanvas2d(), getWindowFeatures(), getHTMLElementVersion(), getCSS(), getCSSMedia(), getScreen(), getMaths(), getConsoleErrors(), getTimezone(), getClientRects(), getFonts(), getMedia(), getSVG(), getResistance(), getIntl(), ]).catch((error) => console.error(error.message)); const navigatorComputed = await getNavigator(workerScopeComputed) .catch((error) => console.error(error.message)); // @ts-ignore const [headlessComputed, featuresComputed,] = await Promise.all([ getHeadlessFeatures({ webgl: canvasWebglComputed, workerScope: workerScopeComputed, }), getEngineFeatures({ cssComputed, navigatorComputed, windowFeaturesComputed, }), ]).catch((error) => console.error(error.message)); // @ts-ignore const [liesComputed, trashComputed, capturedErrorsComputed,] = await Promise.all([ getLies(), getTrash(), getCapturedErrors(), ]).catch((error) => console.error(error.message)); const fingerprintTimeEnd = fingerprintTimeStart(); console.log(`Fingerprinting complete in ${(fingerprintTimeEnd).toFixed(2)}ms`); // GPU Prediction const { parameters: gpuParameter } = canvasWebglComputed || {}; const reducedGPUParameters = { ...(braveFingerprintingBlocking ? getBraveUnprotectedParameters(gpuParameter) : gpuParameter), RENDERER: undefined, SHADING_LANGUAGE_VERSION: undefined, UNMASKED_RENDERER_WEBGL: undefined, UNMASKED_VENDOR_WEBGL: undefined, VERSION: undefined, VENDOR: undefined, }; // Hashing const hashStartTime = timer(); // @ts-ignore const [windowHash, headlessHash, htmlHash, cssMediaHash, cssHash, styleHash, styleSystemHash, screenHash, voicesHash, canvas2dHash, canvas2dImageHash, canvas2dPaintHash, canvas2dTextHash, canvas2dEmojiHash, canvasWebglHash, canvasWebglImageHash, canvasWebglParametersHash, pixelsHash, pixels2Hash, mathsHash, consoleErrorsHash, timezoneHash, rectsHash, domRectHash, audioHash, fontsHash, workerHash, mediaHash, mimeTypesHash, navigatorHash, liesHash, trashHash, errorsHash, svgHash, resistanceHash, intlHash, featuresHash, deviceOfTimezoneHash,] = await Promise.all([ hashify(windowFeaturesComputed), hashify(headlessComputed), hashify((htmlElementVersionComputed || {}).keys), hashify(cssMediaComputed), hashify(cssComputed), hashify((cssComputed || {}).computedStyle), hashify((cssComputed || {}).system), hashify(screenComputed), hashify(voicesComputed), hashify(canvas2dComputed), hashify((canvas2dComputed || {}).dataURI), hashify((canvas2dComputed || {}).paintURI), hashify((canvas2dComputed || {}).textURI), hashify((canvas2dComputed || {}).emojiURI), hashify(canvasWebglComputed), hashify((canvasWebglComputed || {}).dataURI), hashify(reducedGPUParameters), ((canvasWebglComputed || {}).pixels || []).length ? hashify(canvasWebglComputed.pixels) : undefined, ((canvasWebglComputed || {}).pixels2 || []).length ? hashify(canvasWebglComputed.pixels2) : undefined, hashify((mathsComputed || {}).data), hashify((consoleErrorsComputed || {}).errors), hashify(timezoneComputed), hashify(clientRectsComputed), hashify([ (clientRectsComputed || {}).elementBoundingClientRect, (clientRectsComputed || {}).elementClientRects, (clientRectsComputed || {}).rangeBoundingClientRect, (clientRectsComputed || {}).rangeClientRects, ]), hashify(offlineAudioContextComputed), hashify(fontsComputed), hashify(workerScopeComputed), hashify(mediaComputed), hashify((mediaComputed || {}).mimeTypes), hashify(navigatorComputed), hashify(liesComputed), hashify(trashComputed), hashify(capturedErrorsComputed), hashify(svgComputed), hashify(resistanceComputed), hashify(intlComputed), hashify(featuresComputed), hashify((() => { const { bluetoothAvailability, device, deviceMemory, hardwareConcurrency, maxTouchPoints, oscpu, platform, system, userAgentData, } = navigatorComputed || {}; const { architecture, bitness, mobile, model, platform: uaPlatform, platformVersion, } = userAgentData || {}; const { 'any-pointer': anyPointer } = cssMediaComputed?.mediaCSS || {}; const { colorDepth, pixelDepth, height, width } = screenComputed || {}; const { location, locationEpoch, zone } = timezoneComputed || {}; const { deviceMemory: deviceMemoryWorker, hardwareConcurrency: hardwareConcurrencyWorker, gpu, platform: platformWorker, system: systemWorker, timezoneLocation: locationWorker, userAgentData: userAgentDataWorker, } = workerScopeComputed || {}; const { compressedGPU, confidence } = gpu || {}; const { architecture: architectureWorker, bitness: bitnessWorker, mobile: mobileWorker, model: modelWorker, platform: uaPlatformWorker, platformVersion: platformVersionWorker, } = userAgentDataWorker || {}; return [ anyPointer, architecture, architectureWorker, bitness, bitnessWorker, bluetoothAvailability, colorDepth, ...(compressedGPU && confidence != 'low' ? [compressedGPU] : []), device, deviceMemory, deviceMemoryWorker, hardwareConcurrency, hardwareConcurrencyWorker, height, location, locationWorker, locationEpoch, maxTouchPoints, mobile, mobileWorker, model, modelWorker, oscpu, pixelDepth, platform, platformWorker, platformVersion, platformVersionWorker, system, systemWorker, uaPlatform, uaPlatformWorker, width, zone, ]; })()), ]).catch((error) => console.error(error.message)); // console.log(performance.now()-start) const hashTimeEnd = hashStartTime(); const timeEnd = timeStart(); console.log(`Hashing complete in ${(hashTimeEnd).toFixed(2)}ms`); if (PARENT_PHANTOM) { // @ts-ignore PARENT_PHANTOM.parentNode.removeChild(PARENT_PHANTOM); } const fingerprint = { workerScope: !workerScopeComputed ? undefined : { ...workerScopeComputed, $hash: workerHash }, navigator: !navigatorComputed ? undefined : { ...navigatorComputed, $hash: navigatorHash }, windowFeatures: !windowFeaturesComputed ? undefined : { ...windowFeaturesComputed, $hash: windowHash }, headless: !headlessComputed ? undefined : { ...headlessComputed, $hash: headlessHash }, htmlElementVersion: !htmlElementVersionComputed ? undefined : { ...htmlElementVersionComputed, $hash: htmlHash }, cssMedia: !cssMediaComputed ? undefined : { ...cssMediaComputed, $hash: cssMediaHash }, css: !cssComputed ? undefined : { ...cssComputed, $hash: cssHash }, screen: !screenComputed ? undefined : { ...screenComputed, $hash: screenHash }, voices: !voicesComputed ? undefined : { ...voicesComputed, $hash: voicesHash }, media: !mediaComputed ? undefined : { ...mediaComputed, $hash: mediaHash }, canvas2d: !canvas2dComputed ? undefined : { ...canvas2dComputed, $hash: canvas2dHash }, canvasWebgl: !canvasWebglComputed ? undefined : { ...canvasWebglComputed, pixels: pixelsHash, pixels2: pixels2Hash, $hash: canvasWebglHash }, maths: !mathsComputed ? undefined : { ...mathsComputed, $hash: mathsHash }, consoleErrors: !consoleErrorsComputed ? undefined : { ...consoleErrorsComputed, $hash: consoleErrorsHash }, timezone: !timezoneComputed ? undefined : { ...timezoneComputed, $hash: timezoneHash }, clientRects: !clientRectsComputed ? undefined : { ...clientRectsComputed, $hash: rectsHash }, offlineAudioContext: !offlineAudioContextComputed ? undefined : { ...offlineAudioContextComputed, $hash: audioHash }, fonts: !fontsComputed ? undefined : { ...fontsComputed, $hash: fontsHash }, lies: !liesComputed ? undefined : { ...liesComputed, $hash: liesHash }, trash: !trashComputed ? undefined : { ...trashComputed, $hash: trashHash }, capturedErrors: !capturedErrorsComputed ? undefined : { ...capturedErrorsComputed, $hash: errorsHash }, svg: !svgComputed ? undefined : { ...svgComputed, $hash: svgHash }, resistance: !resistanceComputed ? undefined : { ...resistanceComputed, $hash: resistanceHash }, intl: !intlComputed ? undefined : { ...intlComputed, $hash: intlHash }, features: !featuresComputed ? undefined : { ...featuresComputed, $hash: featuresHash }, }; return { fingerprint, styleSystemHash, styleHash, domRectHash, mimeTypesHash, canvas2dImageHash, canvasWebglImageHash, canvas2dPaintHash, canvas2dTextHash, canvas2dEmojiHash, canvasWebglParametersHash, deviceOfTimezoneHash, timeEnd, }; }; // fingerprint and render const [{ fingerprint: fp, styleSystemHash, styleHash, domRectHash, mimeTypesHash, canvas2dImageHash, canvas2dPaintHash, canvas2dTextHash, canvas2dEmojiHash, canvasWebglImageHash, canvasWebglParametersHash, deviceOfTimezoneHash, timeEnd, },] = await Promise.all([ fingerprint().catch((error) => console.error(error)) || {}, ]); if (!fp) { throw new Error('Fingerprint failed!'); } console.log('%cāœ” loose fingerprint passed', 'color:#4cca9f'); console.groupCollapsed('Loose Fingerprint'); console.log(fp); console.groupEnd(); console.groupCollapsed('Loose Fingerprint JSON'); console.log('diff check at https://www.diffchecker.com/diff\n\n', JSON.stringify(fp, null, '\t')); console.groupEnd(); const hardenEntropy = (workerScope, prop) => { return (!workerScope ? prop : (workerScope.localeEntropyIsTrusty && workerScope.localeIntlEntropyIsTrusty) ? prop : undefined); }; const privacyResistFingerprinting = (fp.resistance && /^(tor browser|firefox)$/i.test(fp.resistance.privacy)); // harden gpu const hardenGPU = (canvasWebgl) => { const { gpu: { confidence, compressedGPU } } = canvasWebgl; return (confidence == 'low' ? {} : { UNMASKED_RENDERER_WEBGL: compressedGPU, UNMASKED_VENDOR_WEBGL: canvasWebgl.parameters.UNMASKED_VENDOR_WEBGL, }); }; const creep = { navigator: (!fp.navigator || fp.navigator.lied ? undefined : { bluetoothAvailability: fp.navigator.bluetoothAvailability, device: fp.navigator.device, deviceMemory: fp.navigator.deviceMemory, hardwareConcurrency: fp.navigator.hardwareConcurrency, maxTouchPoints: fp.navigator.maxTouchPoints, oscpu: fp.navigator.oscpu, platform: fp.navigator.platform, system: fp.navigator.system, userAgentData: { ...(fp.navigator.userAgentData || {}), // loose brandsVersion: undefined, uaFullVersion: undefined, }, vendor: fp.navigator.vendor, }), screen: (!fp.screen || fp.screen.lied || privacyResistFingerprinting || LowerEntropy.SCREEN ? undefined : hardenEntropy(fp.workerScope, { height: fp.screen.height, width: fp.screen.width, pixelDepth: fp.screen.pixelDepth, colorDepth: fp.screen.colorDepth, lied: fp.screen.lied, })), workerScope: !fp.workerScope || fp.workerScope.lied ? undefined : { deviceMemory: (braveFingerprintingBlocking ? undefined : fp.workerScope.deviceMemory), hardwareConcurrency: (braveFingerprintingBlocking ? undefined : fp.workerScope.hardwareConcurrency), // system locale in blink language: !LowerEntropy.TIME_ZONE ? fp.workerScope.language : undefined, platform: fp.workerScope.platform, system: fp.workerScope.system, device: fp.workerScope.device, timezoneLocation: (!LowerEntropy.TIME_ZONE ? hardenEntropy(fp.workerScope, fp.workerScope.timezoneLocation) : undefined), webglRenderer: ((fp.workerScope.gpu.confidence != 'low') ? fp.workerScope.gpu.compressedGPU : undefined), webglVendor: ((fp.workerScope.gpu.confidence != 'low') ? fp.workerScope.webglVendor : undefined), userAgentData: { ...fp.workerScope.userAgentData, // loose brandsVersion: undefined, uaFullVersion: undefined, }, }, media: fp.media, canvas2d: ((canvas2d) => { if (!canvas2d) { return; } const { lied, liedTextMetrics } = canvas2d; let data; if (!lied) { const { dataURI, paintURI, textURI, emojiURI } = canvas2d; data = { lied, ...{ dataURI, paintURI, textURI, emojiURI }, }; } if (!liedTextMetrics) { const { textMetricsSystemSum, emojiSet } = canvas2d; data = { ...(data || {}), ...{ textMetricsSystemSum, emojiSet }, }; } return data; })(fp.canvas2d), canvasWebgl: (!fp.canvasWebgl || fp.canvasWebgl.lied || LowerEntropy.WEBGL) ? undefined : (braveFingerprintingBlocking ? { parameters: { ...getBraveUnprotectedParameters(fp.canvasWebgl.parameters), ...hardenGPU(fp.canvasWebgl), }, } : { ...((gl, canvas2d) => { if ((canvas2d && canvas2d.lied) || LowerEntropy.CANVAS) { // distrust images const { extensions, gpu, lied, parameterOrExtensionLie } = gl; return { extensions, gpu, lied, parameterOrExtensionLie, }; } return gl; })(fp.canvasWebgl, fp.canvas2d), parameters: { ...fp.canvasWebgl.parameters, ...hardenGPU(fp.canvasWebgl), }, }), cssMedia: !fp.cssMedia ? undefined : { reducedMotion: caniuse(() => fp.cssMedia.mediaCSS['prefers-reduced-motion']), colorScheme: (braveFingerprintingBlocking ? undefined : caniuse(() => fp.cssMedia.mediaCSS['prefers-color-scheme'])), monochrome: caniuse(() => fp.cssMedia.mediaCSS.monochrome), invertedColors: caniuse(() => fp.cssMedia.mediaCSS['inverted-colors']), forcedColors: caniuse(() => fp.cssMedia.mediaCSS['forced-colors']), anyHover: caniuse(() => fp.cssMedia.mediaCSS['any-hover']), hover: caniuse(() => fp.cssMedia.mediaCSS.hover), anyPointer: caniuse(() => fp.cssMedia.mediaCSS['any-pointer']), pointer: caniuse(() => fp.cssMedia.mediaCSS.pointer), colorGamut: caniuse(() => fp.cssMedia.mediaCSS['color-gamut']), screenQuery: (privacyResistFingerprinting || (LowerEntropy.SCREEN || LowerEntropy.IFRAME_SCREEN) ? undefined : hardenEntropy(fp.workerScope, caniuse(() => fp.cssMedia.screenQuery))), }, css: !fp.css ? undefined : fp.css.system.fonts, timezone: !fp.timezone || fp.timezone.lied || LowerEntropy.TIME_ZONE ? undefined : { locationMeasured: hardenEntropy(fp.workerScope, fp.timezone.locationMeasured), lied: fp.timezone.lied, }, offlineAudioContext: !fp.offlineAudioContext ? undefined : (fp.offlineAudioContext.lied || LowerEntropy.AUDIO ? undefined : fp.offlineAudioContext), fonts: !fp.fonts || fp.fonts.lied || LowerEntropy.FONTS ? undefined : fp.fonts.fontFaceLoadFonts, forceRenew: 1737085481442, }; console.log('%cāœ” stable fingerprint passed', 'color:#4cca9f'); console.groupCollapsed('Stable Fingerprint'); console.log(creep); console.groupEnd(); console.groupCollapsed('Stable Fingerprint JSON'); console.log('diff check at https://www.diffchecker.com/diff\n\n', JSON.stringify(creep, null, '\t')); console.groupEnd(); const [fpHash, creepHash] = await Promise.all([hashify(fp), hashify(creep)]).catch((error) => { console.error(error.message); }) || []; const blankFingerprint = '0000000000000000000000000000000000000000000000000000000000000000'; const el = document.getElementById('fingerprint-data'); patch(el, html `
FP ID: Computing...
Fuzzy: ${blankFingerprint}
${(timeEnd || 0).toFixed(2)} ms
WebRTC
host connection:
candidate:0000000000 1 udp 9353978903 93549af7-47d4-485c-a57a-751a3d213876.local 56518 typ host generation 0 ufrag bk84 network-cost 999
foundation/ip:
0000000000
000.000.000.000
capabilities:
stun connection:
candidate:0000000000 1 udp 9353978903 93549af7-47d4-485c-a57a-751a3d213876.local 56518 typ host generation 0 ufrag bk84 network-cost 999
devices (0):
mic, audio, webcam
${timezoneHTML(fp)} ${intlHTML(fp)}
${headlessFeaturesHTML(fp)} ${resistanceHTML(fp)}
${workerScopeHTML(fp)}
${webglHTML(fp)} ${screenHTML(fp)}
${canvasHTML(fp)} ${fontsHTML(fp)}
${clientRectsHTML(fp)} ${svgHTML(fp)}
${audioHTML(fp)} ${voicesHTML(fp)} ${mediaHTML(fp)}
${featuresHTML(fp)}
${cssMediaHTML(fp)} ${cssHTML(fp)}
${mathsHTML(fp)} ${consoleErrorsHTML(fp)}
${windowFeaturesHTML(fp)} ${htmlElementVersionHTML(fp)}
${navigatorHTML(fp)}
Status
network:
battery:
available:
Tests
Workers
Iframes
Fonts
Timezone
Window Version
Screen
Prototype
DOMRect
Emojis
Math
Machine
Chrome Extensions
JS Proxy
`, async () => { // send analysis fingerprint Promise.all([ getWebRTCData(), getWebRTCDevices(), getStatus(), ]).then(async (data) => { const [webRTC, mediaDevices, status] = data || []; patch(document.getElementById('webrtc-connection'), html `
${webrtcHTML(webRTC, mediaDevices)}
`); patch(document.getElementById('status-info'), html `
${statusHTML(status)}
`); }).catch((err) => console.error(err)); // expose results to the window // @ts-expect-error does not exist window.Fingerprint = JSON.parse(JSON.stringify(fp)); // @ts-expect-error does not exist window.Creep = JSON.parse(JSON.stringify(creep)); const fuzzyFingerprint = await getFuzzyHash(fp); const fuzzyFpEl = document.getElementById('fuzzy-fingerprint'); patch(fuzzyFpEl, html `
Fuzzy: ${fuzzyFingerprint}
`); // Display fingerprint const rand = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); setTimeout(() => { patch(document.getElementById('creep-fingerprint'), html `
FP ID: ${creepHash?.split('').map((x, i) => { return `${x}`; }).join('')}
`); }, 50); }); }(); })();