-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdev-readbackwarn.html
More file actions
102 lines (94 loc) · 5.47 KB
/
Copy pathdev-readbackwarn.html
File metadata and controls
102 lines (94 loc) · 5.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>does the readback warning still fire?</title></head>
<body><pre id="out">running…</pre>
<script>
// Reproduce what CreepJS does to one canvas element: paint, export, repaint,
// export — five times over, plus its own getImageData probes. Before the change
// every export read the page's canvas, and Chrome printed
// "Canvas2D: Multiple readback operations using getImageData ..."
// naming mw/mw-canvas-audio.js. Watch the devtools console while this runs.
const F = {canvas:true,audio:true,webgl:true,webrtc:true,navigator:true,screen:true,timezone:true,
geolocation:true,battery:true,fonts:true,clientRects:true,plugins:true,network:true,hideAdBlocker:true};
sessionStorage.setItem('v.ui.s', JSON.stringify({
mode:'normal', screenWidth:1920, screenHeight:1080, colorDepth:32, devicePixelRatio:1,
platform:'Win32', hwConcurrency:8, deviceMemory:8, vendor:'Google Inc.',
language:'et-EE', locale:'et-EE', countryCode:'EE', languages:['et-EE','et','en','en-US'],
timezone:'Europe/Tallinn', noiseSeed:305419896, maxTouchPoints:0,
userAgent:navigator.userAgent, appVersion:'5.0 (Windows NT 10.0; Win64; x64)',
webglVendor:'Google Inc. (Intel)', webglRenderer:'ANGLE (Intel, Iris Xe)', webglParams:{},
clientHints:{platform:'Windows',mobile:false,platformVersion:'10.0.0',architecture:'x86',bitness:'64',wow64:false,model:'',formFactors:['Desktop']},
allowedFonts:['Arial','Segoe UI','Tahoma'], mediaDevices:[], plugins:[], mimeTypes:[],
features:F
}));
sessionStorage.setItem('v.ui.m','normal');
document.documentElement.setAttribute('data-v-ft', JSON.stringify(F));
const ORDER=['profile-injector.js','mw/mw-bag.js','mw/mw-core.js','mw/mw-timezone-screen.js',
'mw/mw-navigator.js','mw/mw-canvas-audio.js','mw/mw-misc.js','mw/mw-workers.js','mw/mw-geo.js',
'mw/mw-adblock.js','mw/mw-cleanup.js'];
const load=(s)=>new Promise(r=>{const e=document.createElement('script');e.src=s+'?x='+Date.now();
e.onload=()=>r(1);e.onerror=()=>r(0);document.head.appendChild(e);});
function h32(b){ let h=2166136261>>>0;
for(let i=0;i<b.length;i++){ h^=b[i]; h=Math.imul(h,16777619)>>>0; }
return h.toString(16).padStart(8,'0'); }
// ?clean=1 runs the identical scenario with NO extension code, to show how many
// warnings the page's own two getImageData probes produce on their own.
const CLEAN = new URLSearchParams(location.search).get('clean') === '1';
(async () => {
if (!CLEAN) { for (const f of ORDER) await load(f); }
const L = [];
// markers: the panel's console history survives navigation, so the only way to
// count what THIS run produced is to bracket it
console.log('=== PROBE START ' + (CLEAN ? 'CLEAN' : 'PATCHED') + ' ===');
// one canvas element, reused across every export — exactly CreepJS's shape
const canvas = document.createElement('canvas');
canvas.width = 75; canvas.height = 75;
const ctx = canvas.getContext('2d');
const uris = [];
for (let round = 0; round < 5; round++) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const g = ctx.createLinearGradient(0, 0, 75, 75);
g.addColorStop(0, ['#f00','#0f0','#00f','#ff0','#f0f'][round]);
g.addColorStop(1, '#0088ff88');
ctx.fillStyle = g; ctx.fillRect(0, 0, 75, 75);
ctx.font = '20px sans-serif'; ctx.fillStyle = '#fff';
ctx.fillText(String(round), 8, 30);
uris.push(canvas.toDataURL());
}
// CreepJS also reads back directly a couple of times
const probe1 = Math.max(...ctx.getImageData(0, 0, 8, 8).data);
ctx.clearRect(0, 0, canvas.width, canvas.height);
const probe2 = Math.max(...ctx.getImageData(0, 0, 8, 8).data);
// and getPixelMods() reads a small canvas 64 times, pixel by pixel
const pm = document.createElement('canvas'); pm.width = 8; pm.height = 8;
const pmx = pm.getContext('2d', { willReadFrequently: true, desynchronized: true });
let pmDiff = 0;
for (let px = 0; px < 8; px++) for (let py = 0; py < 8; py++) {
const rgb = [(px*31)%256, (py*57)%256, ((px+py)*13)%256];
pmx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 255)`;
pmx.fillRect(px, py, 1, 1);
const d = pmx.getImageData(px, py, 1, 1).data;
if (d[0] !== rgb[0] || d[1] !== rgb[1] || d[2] !== rgb[2]) pmDiff++;
}
// toBlob path too
const blobLen = await new Promise((r) => canvas.toBlob((b) => r(b ? b.size : -1), 'image/png'));
console.log('=== PROBE END ' + (CLEAN ? 'CLEAN' : 'PATCHED') + ' ===');
L.push('5 toDataURL exports on ONE canvas element, each after a repaint,');
L.push('plus 2 direct getImageData probes and 1 toBlob — the CreepJS shape.');
L.push('');
uris.forEach((u, i) => L.push(' export ' + i + ' ' + h32(Uint8Array.from(atob(u.split(',')[1]), (c) => c.charCodeAt(0)))));
L.push('');
L.push(' all five distinct: ' + (new Set(uris).size === 5));
L.push(' getImageData probe before clear: max ' + probe1);
L.push(' getImageData probe after clear: max ' + probe2 + ' (must be 0)');
L.push(' toBlob size: ' + blobLen + ' bytes');
L.push(' getPixelMods-style 8x8 write/read: ' + pmDiff + ' of 64 pixels altered (must be 0)');
L.push('');
L.push('Now check the devtools console for');
L.push(' "Canvas2D: Multiple readback operations using getImageData ..."');
L.push('Our exports no longer read this canvas at all — they read a fresh copy,');
L.push('so any remaining warning would come from the two probes above, which are');
L.push('the PAGE\'s own reads and would warn in a clean browser too.');
document.getElementById('out').textContent = L.join('\n');
})();
</script>
</body></html>