// Шум canvas профиля (#89): младший бит одного канала у ~1/64 непрозрачных пикселей. // Детерминирован от seed профиля и номера пикселя: внутри профиля хеш canvas стабилен, // между профилями — разный. Пустые (прозрачные) пиксели не меняются, иначе пустой canvas // перестал бы быть пустым. Подменённые функции выглядят нативными для toString. (() => { const SEED = __SEED__ | 0; const MAX_PIXELS = 4096 * 4096; const fnToString = Function.prototype.toString; const nativeSource = new WeakMap(); const noise = (data) => { for (let i = 0; i < data.length; i += 4) { if (data[i + 3] === 0) continue; let x = Math.imul((i >>> 2) ^ SEED, 0x9e3779b1); x ^= x >>> 15; x = Math.imul(x, 0x85ebca77); x ^= x >>> 13; if ((x & 63) === 0) { const c = i + ((x >>> 6) % 3); data[c] ^= 1; } } }; const replace = (proto, name, make) => { const desc = Object.getOwnPropertyDescriptor(proto, name); if (!desc || typeof desc.value !== 'function') return null; const orig = desc.value; const impl = make(orig); const fn = { [name](...args) { return impl.call(this, args); } }[name]; Object.defineProperty(fn, 'length', { value: orig.length }); nativeSource.set(fn, fnToString.call(orig)); Object.defineProperty(proto, name, { ...desc, value: fn }); return orig; }; const toStringDesc = Object.getOwnPropertyDescriptor(Function.prototype, 'toString'); const toString = { toString() { return nativeSource.has(this) ? nativeSource.get(this) : fnToString.call(this); } }.toString; nativeSource.set(toString, fnToString.call(fnToString)); Object.defineProperty(Function.prototype, 'toString', { ...toStringDesc, value: toString }); const ctxProto = CanvasRenderingContext2D.prototype; const origGetImageData = replace(ctxProto, 'getImageData', (orig) => function (args) { const img = orig.apply(this, args); noise(img.data); return img; }); // toDataURL/toBlob: шумим копию, исходный canvas страницы не трогаем const noisyCopy = (canvas) => { const w = canvas.width, h = canvas.height; if (!w || !h || w * h > MAX_PIXELS) return canvas; const copy = document.createElement('canvas'); copy.width = w; copy.height = h; const ctx = copy.getContext('2d'); ctx.drawImage(canvas, 0, 0); const img = origGetImageData.call(ctx, 0, 0, w, h); noise(img.data); ctx.putImageData(img, 0, 0); return copy; }; const canvasProto = HTMLCanvasElement.prototype; replace(canvasProto, 'toDataURL', (orig) => function (args) { return orig.apply(noisyCopy(this), args); }); replace(canvasProto, 'toBlob', (orig) => function (args) { return orig.apply(noisyCopy(this), args); }); })();