browser_canvas_fingerprinting_resistance.js (8132B)
1 /** 2 * When "privacy.resistFingerprinting" is set to true, user permission is 3 * required for canvas data extraction. 4 * This tests whether the site permission prompt for canvas data extraction 5 * works properly. 6 * Canvas data extraction should result in random data. 7 */ 8 "use strict"; 9 10 const kUrl = "https://example.com/"; 11 const kPrincipal = Services.scriptSecurityManager.createContentPrincipal( 12 Services.io.newURI(kUrl), 13 {} 14 ); 15 const kPermission = "canvas"; 16 17 function initTab() { 18 let contentWindow = content.wrappedJSObject; 19 20 let drawCanvas = (fillStyle, id) => { 21 let contentDocument = contentWindow.document; 22 let width = 64, 23 height = 64; 24 let canvas = contentDocument.createElement("canvas"); 25 if (id) { 26 canvas.setAttribute("id", id); 27 } 28 canvas.setAttribute("width", width); 29 canvas.setAttribute("height", height); 30 contentDocument.body.appendChild(canvas); 31 32 let context = canvas.getContext("2d"); 33 context.fillStyle = fillStyle; 34 context.fillRect(0, 0, width, height); 35 36 if (id) { 37 let button = contentDocument.createElement("button"); 38 button.addEventListener("click", function () { 39 canvas.toDataURL(); 40 }); 41 button.setAttribute("id", "clickme"); 42 button.innerHTML = "Click Me!"; 43 contentDocument.body.appendChild(button); 44 } 45 46 return canvas; 47 }; 48 49 let canvas = drawCanvas("cyan", "canvas-id-canvas"); 50 contentWindow.kPlacedData = canvas.toDataURL(); 51 is( 52 canvas.toDataURL(), 53 contentWindow.kPlacedData, 54 "privacy.resistFingerprinting = false, canvas data == placed data" 55 ); 56 } 57 58 function enableResistFingerprinting(autoDeclineNoInput) { 59 return SpecialPowers.pushPrefEnv({ 60 set: [ 61 ["privacy.fingerprintingProtection", true], 62 [ 63 "privacy.fingerprintingProtection.overrides", 64 "+CanvasRandomization,+CanvasImageExtractionPrompt,+CanvasExtractionFromThirdPartiesIsBlocked" + 65 (autoDeclineNoInput 66 ? ",+CanvasExtractionBeforeUserInputIsBlocked" 67 : ""), 68 ], 69 ], 70 }); 71 } 72 73 function promisePopupShown() { 74 return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 75 } 76 77 function promisePopupHidden() { 78 return BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden"); 79 } 80 81 function extractCanvasData(grantPermission) { 82 let contentWindow = content.wrappedJSObject; 83 let canvas = contentWindow.document.getElementById("canvas-id-canvas"); 84 let canvasData = canvas.toDataURL(); 85 if (grantPermission) { 86 is( 87 canvasData, 88 contentWindow.kPlacedData, 89 "privacy.resistFingerprinting = true, permission granted, canvas data == placed data" 90 ); 91 } else if (grantPermission === false) { 92 isnot( 93 canvasData, 94 contentWindow.kPlacedData, 95 "privacy.resistFingerprinting = true, permission denied, canvas data != placed data" 96 ); 97 } else { 98 isnot( 99 canvasData, 100 contentWindow.kPlacedData, 101 "privacy.resistFingerprinting = true, requesting permission, canvas data != placed data" 102 ); 103 } 104 } 105 106 function triggerCommand(button) { 107 let notifications = PopupNotifications.panel.children; 108 let notification = notifications[0]; 109 EventUtils.synthesizeMouseAtCenter(notification[button], {}); 110 } 111 112 function triggerMainCommand() { 113 triggerCommand("button"); 114 } 115 116 function triggerSecondaryCommand() { 117 triggerCommand("secondaryButton"); 118 } 119 120 function testPermission() { 121 return Services.perms.testPermissionFromPrincipal(kPrincipal, kPermission); 122 } 123 124 async function withNewTabNoInput(grantPermission, browser) { 125 await SpecialPowers.spawn(browser, [], initTab); 126 await enableResistFingerprinting(false); 127 let popupShown = promisePopupShown(); 128 await SpecialPowers.spawn(browser, [], extractCanvasData); 129 await popupShown; 130 let popupHidden = promisePopupHidden(); 131 if (grantPermission) { 132 triggerMainCommand(); 133 await popupHidden; 134 is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted"); 135 } else { 136 triggerSecondaryCommand(); 137 await popupHidden; 138 is(testPermission(), Services.perms.DENY_ACTION, "permission denied"); 139 } 140 await SpecialPowers.spawn(browser, [grantPermission], extractCanvasData); 141 await SpecialPowers.popPrefEnv(); 142 } 143 144 async function doTestNoInput(grantPermission) { 145 await BrowserTestUtils.withNewTab( 146 kUrl, 147 withNewTabNoInput.bind(null, grantPermission) 148 ); 149 Services.perms.removeFromPrincipal(kPrincipal, kPermission); 150 } 151 152 // With auto-declining disabled (not the default) 153 // Tests clicking "Don't Allow" button of the permission prompt. 154 add_task(doTestNoInput.bind(null, false)); 155 156 // Tests clicking "Allow" button of the permission prompt. 157 add_task(doTestNoInput.bind(null, true)); 158 159 async function withNewTabAutoBlockNoInput(browser) { 160 await SpecialPowers.spawn(browser, [], initTab); 161 await enableResistFingerprinting(true); 162 163 let noShowHandler = () => { 164 ok(false, "The popup notification should not show in this case."); 165 }; 166 PopupNotifications.panel.addEventListener("popupshown", noShowHandler, { 167 once: true, 168 }); 169 170 let promisePopupObserver = TestUtils.topicObserved( 171 "PopupNotifications-updateNotShowing" 172 ); 173 174 // Try to extract canvas data without user inputs. 175 await SpecialPowers.spawn(browser, [], extractCanvasData); 176 177 await promisePopupObserver; 178 info("There should be no popup shown on the panel."); 179 180 // Check that the icon of canvas permission is shown. 181 let canvasNotification = PopupNotifications.getNotification( 182 "canvas-permissions-prompt", 183 browser 184 ); 185 186 is( 187 canvasNotification.anchorElement.getAttribute("showing"), 188 "true", 189 "The canvas permission icon is correctly shown." 190 ); 191 PopupNotifications.panel.removeEventListener("popupshown", noShowHandler); 192 193 await SpecialPowers.popPrefEnv(); 194 } 195 196 async function doTestAutoBlockNoInput() { 197 await BrowserTestUtils.withNewTab( 198 kUrl, 199 withNewTabAutoBlockNoInput.bind(null) 200 ); 201 } 202 203 add_task(doTestAutoBlockNoInput.bind(null)); 204 205 function extractCanvasDataUserInput(grantPermission) { 206 let contentWindow = content.wrappedJSObject; 207 let canvas = contentWindow.document.getElementById("canvas-id-canvas"); 208 let canvasData = canvas.toDataURL(); 209 if (grantPermission) { 210 is( 211 canvasData, 212 contentWindow.kPlacedData, 213 "privacy.resistFingerprinting = true, permission granted, canvas data == placed data" 214 ); 215 } else if (grantPermission === false) { 216 isnot( 217 canvasData, 218 contentWindow.kPlacedData, 219 "privacy.resistFingerprinting = true, permission denied, canvas data != placed data" 220 ); 221 } else { 222 isnot( 223 canvasData, 224 contentWindow.kPlacedData, 225 "privacy.resistFingerprinting = true, requesting permission, canvas data != placed data" 226 ); 227 } 228 } 229 230 async function withNewTabInput(grantPermission, browser) { 231 await SpecialPowers.spawn(browser, [], initTab); 232 await enableResistFingerprinting(true); 233 let popupShown = promisePopupShown(); 234 await SpecialPowers.spawn(browser, [], function () { 235 E10SUtils.wrapHandlingUserInput(content, true, function () { 236 var button = content.document.getElementById("clickme"); 237 button.click(); 238 }); 239 }); 240 await popupShown; 241 let popupHidden = promisePopupHidden(); 242 if (grantPermission) { 243 triggerMainCommand(); 244 await popupHidden; 245 is(testPermission(), Services.perms.ALLOW_ACTION, "permission granted"); 246 } else { 247 triggerSecondaryCommand(); 248 await popupHidden; 249 is(testPermission(), Services.perms.DENY_ACTION, "permission denied"); 250 } 251 await SpecialPowers.spawn( 252 browser, 253 [grantPermission], 254 extractCanvasDataUserInput 255 ); 256 await SpecialPowers.popPrefEnv(); 257 } 258 259 async function doTestInput(grantPermission) { 260 await BrowserTestUtils.withNewTab( 261 kUrl, 262 withNewTabInput.bind(null, grantPermission) 263 ); 264 Services.perms.removeFromPrincipal(kPrincipal, kPermission); 265 } 266 267 // With auto-declining enabled (the default) 268 // Tests clicking "Don't Allow" button of the permission prompt. 269 add_task(doTestInput.bind(null, false)); 270 271 // Tests clicking "Allow" button of the permission prompt. 272 add_task(doTestInput.bind(null, true));