您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Stops fingerprinting scripts from testing 3000+ fonts by faking layout results after 200 probes. Includes metric spoofing and font stability per session.
// ==UserScript== // @name Anti Font Fingerprinting (Limited Probes) // @namespace http://tampermonkey.net/ // @version 2.1 // @description Stops fingerprinting scripts from testing 3000+ fonts by faking layout results after 200 probes. Includes metric spoofing and font stability per session. // @author Atom // @match *://*/* // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Exclude trusted domains if needed const excludeSites = [ "gmail.com", "outlook.com", "notion.so", "office.com", "figma.com" ]; const currentHost = location.hostname; if (excludeSites.some(site => currentHost.includes(site))) return; const randomOffset = () => (Math.random() - 0.5) * 2; const MAX_FONT_PROBES = 200; let fontProbeCount = 0; // Monkey patch getBoundingClientRect to limit effectiveness of font testing const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect; Element.prototype.getBoundingClientRect = function(...args) { const tag = this.tagName.toLowerCase(); const font = this.style?.fontFamily?.toLowerCase?.() || ""; const isHidden = this.offsetParent === null || this.offsetWidth === 0; const likelyProbe = tag === "span" && font && isHidden; const rect = originalGetBoundingClientRect.apply(this, args); if (likelyProbe) { fontProbeCount++; if (fontProbeCount > MAX_FONT_PROBES) { // Return fake, constant rect to break detection return { ...rect, width: 100, height: 10, top: 0, left: 0, bottom: 10, right: 100, x: 0, y: 0, toJSON: rect.toJSON.bind(rect) }; } } // Otherwise slightly randomize return { ...rect, width: rect.width + randomOffset(), height: rect.height + randomOffset(), x: rect.x + randomOffset(), y: rect.y + randomOffset(), top: rect.top + randomOffset(), left: rect.left + randomOffset(), right: rect.right + randomOffset(), bottom: rect.bottom + randomOffset(), toJSON: rect.toJSON.bind(rect) }; }; // Same for offsetWidth / offsetHeight const patchOffset = (prop) => { const descriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, prop); Object.defineProperty(HTMLElement.prototype, prop, { get: function() { const font = this.style?.fontFamily?.toLowerCase?.() || ""; const tag = this.tagName.toLowerCase(); const isHidden = this.offsetParent === null || descriptor.get.call(this) === 0; const likelyProbe = tag === "span" && font && isHidden; if (likelyProbe) { fontProbeCount++; if (fontProbeCount > MAX_FONT_PROBES) { return prop.includes("Width") ? 100 : 10; } } return descriptor.get.call(this) + randomOffset(); }, configurable: true }); }; patchOffset("offsetWidth"); patchOffset("offsetHeight"); // Patch Canvas measureText const originalMeasureText = CanvasRenderingContext2D.prototype.measureText; CanvasRenderingContext2D.prototype.measureText = function(text) { const metrics = originalMeasureText.apply(this, arguments); return { ...metrics, width: metrics.width + randomOffset(), actualBoundingBoxLeft: metrics.actualBoundingBoxLeft + randomOffset(), actualBoundingBoxRight: metrics.actualBoundingBoxRight + randomOffset(), actualBoundingBoxAscent: metrics.actualBoundingBoxAscent + randomOffset(), actualBoundingBoxDescent: metrics.actualBoundingBoxDescent + randomOffset() }; }; // Optional: Hide document.fonts completely (not used by browserleaks, but useful elsewhere) Object.defineProperty(document, 'fonts', { get: () => ({ forEach: () => {}, ready: Promise.resolve(), add: () => {}, delete: () => {}, clear: () => {}, size: 0 }), configurable: true }); console.log("[Anti-Fingerprint] Font probe limiter active. Max probes:", MAX_FONT_PROBES); })();