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