browser_canvascompare_popups_data.js (6969B)
1 /** 2 * This test compares canvas randomization on a parent and a data popup, and ensures that the canvas randomization key 3 * is inherited correctly. (e.g. that the canvases have the same random value) 4 * 5 * Covers the following cases: 6 * - RFP/FPP is disabled entirely 7 * - RFP is enabled entirely, and only in PBM 8 * - FPP is enabled entirely, and only in PBM 9 * - A normal window when FPP is enabled globally and RFP is enabled in PBM, Protections Enabled and Disabled 10 * 11 */ 12 13 "use strict"; 14 15 // ============================================================================================= 16 17 /** 18 * Compares two Uint8Arrays and returns the number of bits that are different. 19 * 20 * @param {Uint8ClampedArray} arr1 - The first Uint8ClampedArray to compare. 21 * @param {Uint8ClampedArray} arr2 - The second Uint8ClampedArray to compare. 22 * @returns {number} - The number of bits that are different between the two 23 * arrays. 24 */ 25 function countDifferencesInUint8Arrays(arr1, arr2) { 26 let count = 0; 27 for (let i = 0; i < arr1.length; i++) { 28 let diff = arr1[i] ^ arr2[i]; 29 while (diff > 0) { 30 count += diff & 1; 31 diff >>= 1; 32 } 33 } 34 return count; 35 } 36 37 // ============================================================================================= 38 39 async function testCanvasRandomization(result, expectedResults, extraData) { 40 let testDesc = extraData.testDesc; 41 42 let parent = result.mine; 43 let child = result.theirs; 44 45 let differencesInRandom = countDifferencesInUint8Arrays(parent, child); 46 let differencesFromUnmodifiedParent = countDifferencesInUint8Arrays( 47 UNMODIFIED_CANVAS_DATA, 48 parent 49 ); 50 let differencesFromUnmodifiedChild = countDifferencesInUint8Arrays( 51 UNMODIFIED_CANVAS_DATA, 52 child 53 ); 54 55 Assert.greaterOrEqual( 56 differencesFromUnmodifiedParent, 57 expectedResults[0], 58 `Checking ${testDesc} for canvas randomization, comparing parent - lower bound for random pixels.` 59 ); 60 Assert.lessOrEqual( 61 differencesFromUnmodifiedParent, 62 expectedResults[1], 63 `Checking ${testDesc} for canvas randomization, comparing parent - upper bound for random pixels.` 64 ); 65 66 Assert.greaterOrEqual( 67 differencesFromUnmodifiedChild, 68 expectedResults[0], 69 `Checking ${testDesc} for canvas randomization, comparing child - lower bound for random pixels.` 70 ); 71 Assert.lessOrEqual( 72 differencesFromUnmodifiedChild, 73 expectedResults[1], 74 `Checking ${testDesc} for canvas randomization, comparing child - upper bound for random pixels.` 75 ); 76 77 Assert.greaterOrEqual( 78 differencesInRandom, 79 expectedResults[2], 80 `Checking ${testDesc} and comparing randomization - lower bound for different random pixels.` 81 ); 82 Assert.lessOrEqual( 83 differencesInRandom, 84 expectedResults[3], 85 `Checking ${testDesc} and comparing randomization - upper bound for different random pixels.` 86 ); 87 } 88 89 requestLongerTimeout(2); 90 91 let expectedResults = {}; 92 var UNMODIFIED_CANVAS_DATA = undefined; 93 94 add_setup(async function () { 95 // Make sure Old Randomization is the only one on 96 await SpecialPowers.pushPrefEnv({ 97 set: [ 98 [ 99 "privacy.fingerprintingProtection.overrides", 100 "-EfficientCanvasRandomization,+CanvasRandomization", 101 ], 102 ], 103 }); 104 105 // Disable the fingerprinting randomization. 106 await SpecialPowers.pushPrefEnv({ 107 set: [ 108 ["privacy.fingerprintingProtection", false], 109 ["privacy.fingerprintingProtection.pbmode", false], 110 ["privacy.resistFingerprinting", false], 111 ], 112 }); 113 114 let extractCanvasData = function () { 115 let offscreenCanvas = new OffscreenCanvas(100, 100); 116 117 const context = offscreenCanvas.getContext("2d"); 118 119 // Draw a red rectangle 120 context.fillStyle = "#EE2222"; 121 context.fillRect(0, 0, 100, 100); 122 context.fillStyle = "#2222EE"; 123 context.fillRect(20, 20, 100, 100); 124 125 const imageData = context.getImageData(0, 0, 100, 100); 126 return imageData.data; 127 }; 128 129 function runExtractCanvasData(tab) { 130 let code = extractCanvasData.toString(); 131 return SpecialPowers.spawn(tab.linkedBrowser, [code], async funccode => { 132 await content.eval(`var extractCanvasData = ${funccode}`); 133 let result = await content.eval(`extractCanvasData()`); 134 return result; 135 }); 136 } 137 138 const emptyPage = 139 getRootDirectory(gTestPath).replace( 140 "chrome://mochitests/content", 141 "https://example.com" 142 ) + "empty.html"; 143 144 // Open a tab for extracting the canvas data. 145 const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, emptyPage); 146 147 let data = await runExtractCanvasData(tab); 148 UNMODIFIED_CANVAS_DATA = data; 149 150 BrowserTestUtils.removeTab(tab); 151 await SpecialPowers.popPrefEnv(); 152 }); 153 154 // Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a 155 // deep copy and avoiding corrupting the original 'const' object 156 // The first value represents the minimum number of random pixels we should see 157 // The second, the maximum number of random pixels 158 // The third, the minimum number of differences between the canvases of the parent and child 159 // The fourth, the maximum number of differences between the canvases of the parent and child 160 const rfpFullyRandomized = [10000, 999999999, 20000, 999999999]; 161 const fppRandomizedSameDomain = [1, 260, 0, 0]; 162 const noRandom = [0, 0, 0, 0]; 163 164 let uri = `https://${IFRAME_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_canvascompare_data_popupmaker.html`; 165 166 expectedResults = structuredClone(noRandom); 167 add_task( 168 defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults) 169 ); 170 171 expectedResults = structuredClone(fppRandomizedSameDomain); 172 add_task( 173 defaultsPBMTest.bind(null, uri, testCanvasRandomization, expectedResults) 174 ); 175 176 expectedResults = structuredClone(rfpFullyRandomized); 177 add_task( 178 simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults) 179 ); 180 181 // Test a private window with RFP enabled in PBMode 182 expectedResults = structuredClone(rfpFullyRandomized); 183 add_task( 184 simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults) 185 ); 186 187 expectedResults = structuredClone(fppRandomizedSameDomain); 188 add_task( 189 simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults) 190 ); 191 192 // Test a Private Window with FPP Enabled in PBM 193 expectedResults = structuredClone(fppRandomizedSameDomain); 194 add_task( 195 simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults) 196 ); 197 198 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections 199 expectedResults = structuredClone(noRandom); 200 add_task( 201 RFPPBMFPP_NormalMode_NoProtectionsTest.bind( 202 null, 203 uri, 204 testCanvasRandomization, 205 expectedResults 206 ) 207 ); 208 209 // Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled 210 expectedResults = structuredClone(fppRandomizedSameDomain); 211 add_task( 212 RFPPBMFPP_NormalMode_ProtectionsTest.bind( 213 null, 214 uri, 215 testCanvasRandomization, 216 expectedResults 217 ) 218 );