Bug 967895 - Prompt (w/ Site Permission) before allowing content to extract canvas data (Tor 6253) draft
authorJonathan Hao <jhao@mozilla.com>
Wed, 14 Jun 2017 16:41:37 +0800
changeset 593940 938122ab3d60071a12385a44ea9630043dd523de
parent 593939 c9dc65b30198580384fdcd4251934b9323a4ae99
child 633280 ba7c7fcddb498dddbeff941a4ac2a071302bcfa8
push id63879
push userbmo:jhao@mozilla.com
push dateWed, 14 Jun 2017 09:49:53 +0000
bugs967895, 6253, 12684, 21620, 13021, 13439, 15640, 17446
milestone55.0a1
Bug 967895 - Prompt (w/ Site Permission) before allowing content to extract canvas data (Tor 6253) Preventing canvas data extraction is an important part of the anti-fingerprinting work. When the pref privacy.resistFingerprinting is on and content is trying to extract canvas data, we will ask for user permission by a PopupNotification. The three patches of bug 967895 are uplifted from Tor Browser. This patch is about the PopupNotification UI. See the original commit message below. From 2c9aa3b1c278653c27a0339db89675ad89933ec9 Mon Sep 17 00:00:00 2001 From: Kathy Brade <brade@pearlcrescent.com> Date: Thu, 26 Sep 2013 17:11:19 -0400 Subject: Bug 6253: Add canvas image extraction prompt. (See also Bug #12684, Make "Not now" default for HTML5 canvas permission dialogue, patched by Isis Lovecruft.) This implements a `PopupNotification` [0] which notifies users that a website has attempted to access an HTML5 canvas. The default ordering for buttons is: Not Now Never for this site (recommended) Allow in the future * FIXES #12684 [1] by making "Not Now" the default in the HTML5 canvas fingerprinting permissions dialogue. * Palette icons included in HTML5 canvas permissions PopupNotification UI. The image is freely licensed and obtainable from: https://openclipart.org/image/300px/svg_to_png/21620/ben_palette.png * Includes a CSS whitespace hack from Pearl Crescent to the `CanvasPermissionPromptHelper_init()` function in `browser/base/content/browser.js` for causing the newlines in the `canvas.siteprompt` string (in torbutton.git, in `chrome/locale/en/torbutton.properties`) to render correctly in PopupNotification XUL <description> elements. [2] NOTE: Applying this patch requires an additional patch to TorButton, to store the additional UI strings before localisation. [3] [0]: https://mxr.mozilla.org/mozilla-esr24/source/toolkit/modules/PopupNotifications.jsm [1]: https://bugs.torproject.org/12684 [2]: https://trac.torproject.org/projects/tor/ticket/12684#comment:21 [3]: https://github.com/isislovecruft/torbutton/commit/368e74d62df349b27cf578525c3fa15da19ccdc2 Also includes: Bug 13021: Prompt before allowing Canvas isPointIn*() calls. Display our data extraction prompt and implement site-specific preferences for access to the isPointInPath() and isPointInStroke() methods. Bug 13439: No canvas prompt for content-callers. Both the Inspector and PDF.js raise canvas prompts although they are no danger as they are delivered with the browser itself and are no untrusted content. This patch exempts both of them from canvas prompts, too. If calling `DescribeScriptedCaller` fails neither `scriptFile` nor `scriptLine` are logged. Bug 15640: Place Canvas MediaStream behind site permission prompt. Bug 17446: Ensure that third parties are never able to extract canvas image data, even if the same domain has been given permission previously as a first party. Also, refactor slightly to clarify logic of IsImageExtractionAllowed. MozReview-Commit-ID: 3TkN3Syk73q
browser/base/content/browser.js
browser/base/content/browser.xul
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/themes/linux/canvas-popup.svg
browser/themes/linux/jar.mn
browser/themes/osx/canvas-popup.svg
browser/themes/osx/jar.mn
browser/themes/shared/notification-icons.inc.css
browser/themes/windows/canvas-popup.svg
browser/themes/windows/jar.mn
toolkit/themes/shared/popupnotification.inc.css
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1514,16 +1514,19 @@ var gBrowserInit = {
     Services.obs.addObserver(gXPInstallObserver, "addon-install-origin-blocked");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-confirmation");
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete");
     window.messageManager.addMessageListener("Browser:URIFixup", gKeywordURIFixup);
 
     BrowserOffline.init();
     IndexedDBPromptHelper.init();
+    if (Services.prefs.getBoolPref("privacy.resistFingerprinting", false)) {
+      CanvasPermissionPromptHelper.init();
+    }
 
     if (AppConstants.E10S_TESTING_ONLY)
       gRemoteTabsUI.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
@@ -1842,16 +1845,17 @@ var gBrowserInit = {
       }
 
       if (this.gmpInstallManager) {
         this.gmpInstallManager.uninit();
       }
 
       BrowserOffline.uninit();
       IndexedDBPromptHelper.uninit();
+      CanvasPermissionPromptHelper.uninit();
       PanelUI.uninit();
       AutoShowBookmarksToolbar.uninit();
     }
 
     // Final window teardown, do this last.
     window.XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
@@ -6409,16 +6413,98 @@ var IndexedDBPromptHelper = {
       browser, topic, message, this._notificationIcon, mainAction, secondaryActions,
       {
         persistent: true,
         hideClose: !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton"),
       });
   }
 };
 
+var CanvasPermissionPromptHelper = {
+  _permissionsPrompt: "canvas-permissions-prompt",
+  _notificationIcon: "canvas-notification-icon",
+
+  init() {
+    Services.obs.addObserver(this, this._permissionsPrompt);
+  },
+
+  uninit() {
+    Services.obs.removeObserver(this, this._permissionsPrompt);
+  },
+
+  // aSubject is an nsIBrowser (e10s) or an nsIDOMWindow (non-e10s).
+  // aData is an URL string.
+  observe(aSubject, aTopic, aData) {
+    if (aTopic != this._permissionsPrompt) {
+      return;
+    }
+
+    let browser;
+    if (aSubject instanceof Ci.nsIDOMWindow) {
+      let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
+      browser = gBrowser.getBrowserForContentWindow(contentWindow);
+    } else {
+      browser = aSubject.QueryInterface(Ci.nsIBrowser);
+    }
+
+    let uri = makeURI(aData);
+    if (gBrowser.selectedBrowser !== browser) {
+      // Must belong to some other window.
+      return;
+    }
+
+    let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
+    let message = gNavigatorBundle.getFormattedString("canvas.siteprompt", [ uri.asciiHost, brandShortName ]);
+
+    let mainAction = {
+      label: gNavigatorBundle.getString("canvas.allow"),
+      accessKey: gNavigatorBundle.getString("canvas.allowAccessKey"),
+      callback(state) {
+        setCanvasPermission(uri, Ci.nsIPermissionManager.ALLOW_ACTION,
+                            state && state.checkboxChecked);
+      }
+    };
+
+    let secondaryActions = [
+      {
+        label: gNavigatorBundle.getString("canvas.notNow"),
+        accessKey: gNavigatorBundle.getString("canvas.notNowAccessKey"),
+        callback() {
+          return null;
+        }
+      },
+      {
+        label: gNavigatorBundle.getString("canvas.never"),
+        accessKey: gNavigatorBundle.getString("canvas.neverAccessKey"),
+        callback(state) {
+          setCanvasPermission(uri, Ci.nsIPermissionManager.DENY_ACTION,
+                              state && state.checkboxChecked);
+        }
+      }
+    ];
+
+    function setCanvasPermission(aURI, aPerm, aPersistent) {
+      Services.perms.add(aURI, "canvas/extractData", aPerm,
+                         aPersistent ? Ci.nsIPermissionManager.EXPIRE_NEVER
+                                     : Ci.nsIPermissionManager.EXPIRE_SESSION);
+    }
+
+    let checkbox = {
+      // In PB mode, we don't want the "always remember" checkbox
+      show: !PrivateBrowsingUtils.isWindowPrivate(window)
+    };
+    if (checkbox.show) {
+      checkbox.checked = true;
+      checkbox.label = gBrowserBundle.GetStringFromName("canvas.remember");
+    }
+    PopupNotifications.show(browser, aTopic, message, this._notificationIcon,
+                            mainAction, secondaryActions, { checkbox });
+  }
+};
+
 function CanCloseWindow() {
   // Avoid redundant calls to canClose from showing multiple
   // PermitUnload dialogs.
   if (Services.startup.shuttingDown || window.skipNextCanClose) {
     return true;
   }
 
   let timedOutProcesses = new WeakSet();
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -851,16 +851,18 @@
                      onmouseout="document.getElementById('identity-icon').classList.remove('no-hover');"
                      align="center">
                   <image id="default-notification-icon" class="notification-anchor-icon" role="button"
                          tooltiptext="&urlbar.defaultNotificationAnchor.tooltip;"/>
                   <image id="geo-notification-icon" class="notification-anchor-icon geo-icon" role="button"
                          tooltiptext="&urlbar.geolocationNotificationAnchor.tooltip;"/>
                   <image id="addons-notification-icon" class="notification-anchor-icon install-icon" role="button"
                          tooltiptext="&urlbar.addonsNotificationAnchor.tooltip;"/>
+                  <image id="canvas-notification-icon" class="notification-anchor-icon" role="button"
+                         tooltiptext="&urlbar.canvasNotificationAncher.tooltip;"/>
                   <image id="indexedDB-notification-icon" class="notification-anchor-icon indexedDB-icon" role="button"
                          tooltiptext="&urlbar.indexedDBNotificationAnchor.tooltip;"/>
                   <image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
                          tooltiptext="&urlbar.passwordNotificationAnchor.tooltip;"/>
                   <image id="plugins-notification-icon" class="notification-anchor-icon plugin-icon" role="button"
                          tooltiptext="&urlbar.pluginsNotificationAnchor.tooltip;"/>
                   <image id="web-notifications-notification-icon" class="notification-anchor-icon desktop-notification-icon" role="button"
                          tooltiptext="&urlbar.webNotificationAnchor.tooltip;"/>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -204,16 +204,17 @@ These should match what Safari and other
 <!ENTITY printButton.label            "Print">
 <!ENTITY printButton.tooltip          "Print this page">
 
 <!ENTITY urlbar.viewSiteInfo.label                      "View site information">
 
 <!ENTITY urlbar.defaultNotificationAnchor.tooltip         "Open message panel">
 <!ENTITY urlbar.geolocationNotificationAnchor.tooltip     "Open location request panel">
 <!ENTITY urlbar.addonsNotificationAnchor.tooltip          "Open add-on installation message panel">
+<!ENTITY urlbar.canvasNotificationAncher.tooltip          "Manage canvas extraction permission">
 <!ENTITY urlbar.indexedDBNotificationAnchor.tooltip       "Open offline storage message panel">
 <!ENTITY urlbar.passwordNotificationAnchor.tooltip        "Open save password message panel">
 <!ENTITY urlbar.pluginsNotificationAnchor.tooltip         "Manage plug-in use">
 <!ENTITY urlbar.webNotificationAnchor.tooltip             "Change whether you can receive notifications from the site">
 <!ENTITY urlbar.persistentStorageNotificationAnchor.tooltip     "Store data in Persistent Storage">
 <!ENTITY urlbar.remoteControlNotificationAnchor.tooltip   "Browser is under remote control">
 
 <!ENTITY urlbar.webRTCShareDevicesNotificationAnchor.tooltip      "Manage sharing your camera and/or microphone with the site">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -440,16 +440,27 @@ offlineApps.allowStoring.label=Allow Sto
 offlineApps.allowStoring.accesskey=A
 offlineApps.dontAllow.label=Don’t Allow
 offlineApps.dontAllow.accesskey=n
 
 offlineApps.usage=This website (%S) is now storing more than %SMB of data on your computer for offline use.
 offlineApps.manageUsage=Show settings
 offlineApps.manageUsageAccessKey=S
 
+# Canvas permission prompt
+# LOCALIZATION NOTE (canvas.siteprompt): %1$S is hostname, %2$S is brandShortName (e.g. Firefox)
+canvas.siteprompt=This website (%1$S) attempted to extract HTML5 canvas image data, which may be used to uniquely identify your computer.\n\nShould %2$S allow this website to extract HTML5 canvas image data?
+canvas.notNow=Not Now
+canvas.notNowAccessKey=N
+canvas.allow=Allow in the Future
+canvas.allowAccessKey=A
+canvas.never=Never for This Site
+canvas.neverAccessKey=e
+canvas.remember=Remember this decision across sessions
+
 identity.identified.verifier=Verified by: %S
 identity.identified.verified_by_you=You have added a security exception for this site.
 identity.identified.state_and_country=%S, %S
 
 identity.icon.tooltip=Show site information
 identity.extension.label=Extension (%S)
 identity.extension.tooltip=Loaded by extension: %S
 identity.showDetails.tooltip=Show connection details
new file mode 100644
--- /dev/null
+++ b/browser/themes/linux/canvas-popup.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
+
+ <metadata>image/svg+xmlOpen Clip Art Librarypalette2009-02-17T21:15:25http://openclipart.org/detail/21620/palette-by-benbenclip artclipartcolorcoloriconiconimagemediapaintpaintpalettepalettepngpublic domainsvg</metadata>
+ <g>
+  <title>Layer 1</title>
+  <g id="layer1">
+   <g transform="matrix(4.65116, 0, 0, 4.65116, -1717.85, -314.201)" id="g6820">
+    <path fill="context-fill" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" d="m388.618042,70.128563c-4.746887,0 -18.367279,0.849472 -18.34375,12.3125c0.001312,0.644249 1.226776,2.886879 2.25,3.125c7.5755,1.762962 7.986664,7.834511 7.53125,12.625c-0.03479,0.362747 0.589233,2.891006 1.125,3.53125c4.344635,5.191872 5.822723,7.468742 11.6875,7.46875c11.729614,0 18.3125,-5.93206 18.3125,-17.8125c0,-11.880463 -10.832916,-21.25 -22.5625,-21.25zm-11.875,8.1875c0.986481,0.058029 2.139893,0.464394 3.21875,1.1875c2.157715,1.44622 3.225708,3.668251 2.375,4.9375c-0.850739,1.269257 -3.311005,1.10247 -5.46875,-0.34375c-2.157745,-1.446228 -3.194458,-3.637001 -2.34375,-4.90625c0.425354,-0.634628 1.232269,-0.933029 2.21875,-0.875z" id="path6822"/>
+    <path fill="#ff7a00" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6824" d="m392.586761,76.615746a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#95d300" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6826" d="m402.524292,81.553246a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#00a6e4" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6828" d="m408.461792,90.615746a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#f9de00" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6830" d="m403.643463,99.60791a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#e600ad" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6832" d="m393.518463,100.60791a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+   </g>
+  </g>
+ </g>
+</svg>
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -102,14 +102,15 @@ browser.jar:
   skin/classic/browser/sync-horizontalbar.png
   skin/classic/browser/sync-horizontalbar@2x.png
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/syncProgress-horizontalbar.png
   skin/classic/browser/syncProgress-horizontalbar@2x.png
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+  skin/classic/browser/canvas-popup.svg
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/canvas-popup.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
+
+ <metadata>image/svg+xmlOpen Clip Art Librarypalette2009-02-17T21:15:25http://openclipart.org/detail/21620/palette-by-benbenclip artclipartcolorcoloriconiconimagemediapaintpaintpalettepalettepngpublic domainsvg</metadata>
+ <g>
+  <title>Layer 1</title>
+  <g id="layer1">
+   <g transform="matrix(4.65116, 0, 0, 4.65116, -1717.85, -314.201)" id="g6820">
+    <path fill="context-fill" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" d="m388.618042,70.128563c-4.746887,0 -18.367279,0.849472 -18.34375,12.3125c0.001312,0.644249 1.226776,2.886879 2.25,3.125c7.5755,1.762962 7.986664,7.834511 7.53125,12.625c-0.03479,0.362747 0.589233,2.891006 1.125,3.53125c4.344635,5.191872 5.822723,7.468742 11.6875,7.46875c11.729614,0 18.3125,-5.93206 18.3125,-17.8125c0,-11.880463 -10.832916,-21.25 -22.5625,-21.25zm-11.875,8.1875c0.986481,0.058029 2.139893,0.464394 3.21875,1.1875c2.157715,1.44622 3.225708,3.668251 2.375,4.9375c-0.850739,1.269257 -3.311005,1.10247 -5.46875,-0.34375c-2.157745,-1.446228 -3.194458,-3.637001 -2.34375,-4.90625c0.425354,-0.634628 1.232269,-0.933029 2.21875,-0.875z" id="path6822"/>
+    <path fill="#ff7a00" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6824" d="m392.586761,76.615746a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#95d300" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6826" d="m402.524292,81.553246a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#00a6e4" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6828" d="m408.461792,90.615746a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#f9de00" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6830" d="m403.643463,99.60791a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#e600ad" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6832" d="m393.518463,100.60791a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+   </g>
+  </g>
+ </g>
+</svg>
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -166,16 +166,17 @@ browser.jar:
   skin/classic/browser/yosemite/tab-active-middle-inactive@2x.png      (tabbrowser/tab-active-middle-yosemite-inactive@2x.png)
   skin/classic/browser/yosemite/tab-stroke-end-inactive.png            (tabbrowser/tab-stroke-end-yosemite-inactive.png)
   skin/classic/browser/yosemite/tab-stroke-end-inactive@2x.png         (tabbrowser/tab-stroke-end-yosemite-inactive@2x.png)
   skin/classic/browser/yosemite/tab-stroke-start-inactive.png          (tabbrowser/tab-stroke-start-yosemite-inactive.png)
   skin/classic/browser/yosemite/tab-stroke-start-inactive@2x.png       (tabbrowser/tab-stroke-start-yosemite-inactive@2x.png)
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+  skin/classic/browser/canvas-popup.svg
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/feeds/audioFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png                   chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png                 chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/menuPanel-customize.png                   chrome://browser/skin/yosemite/menuPanel-customize.png                  os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel-customize@2x.png                chrome://browser/skin/yosemite/menuPanel-customize@2x.png               os=Darwin osversion>=10.10
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -130,16 +130,23 @@
 .screen-icon.in-use {
   list-style-image: url(chrome://browser/skin/notification-icons.svg#screen-sharing);
 }
 
 .screen-icon.blocked-permission-icon {
   list-style-image: url(chrome://browser/skin/notification-icons.svg#screen-blocked);
 }
 
+#canvas-notification-icon,
+.popup-notification-icon[popupid="canvas-permissions-prompt"] {
+  list-style-image: url(chrome://browser/skin/canvas-popup.svg);
+  -moz-context-properties: fill;
+  fill: currentColor;
+}
+
 #webRTC-preview:not([hidden]) {
   display: -moz-stack;
   border-radius: 4px;
   border: 1px solid GrayText;
   overflow: hidden;
   min-width: 300px;
   min-height: 10em;
 }
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/canvas-popup.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
+
+ <metadata>image/svg+xmlOpen Clip Art Librarypalette2009-02-17T21:15:25http://openclipart.org/detail/21620/palette-by-benbenclip artclipartcolorcoloriconiconimagemediapaintpaintpalettepalettepngpublic domainsvg</metadata>
+ <g>
+  <title>Layer 1</title>
+  <g id="layer1">
+   <g transform="matrix(4.65116, 0, 0, 4.65116, -1717.85, -314.201)" id="g6820">
+    <path fill="context-fill" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" d="m388.618042,70.128563c-4.746887,0 -18.367279,0.849472 -18.34375,12.3125c0.001312,0.644249 1.226776,2.886879 2.25,3.125c7.5755,1.762962 7.986664,7.834511 7.53125,12.625c-0.03479,0.362747 0.589233,2.891006 1.125,3.53125c4.344635,5.191872 5.822723,7.468742 11.6875,7.46875c11.729614,0 18.3125,-5.93206 18.3125,-17.8125c0,-11.880463 -10.832916,-21.25 -22.5625,-21.25zm-11.875,8.1875c0.986481,0.058029 2.139893,0.464394 3.21875,1.1875c2.157715,1.44622 3.225708,3.668251 2.375,4.9375c-0.850739,1.269257 -3.311005,1.10247 -5.46875,-0.34375c-2.157745,-1.446228 -3.194458,-3.637001 -2.34375,-4.90625c0.425354,-0.634628 1.232269,-0.933029 2.21875,-0.875z" id="path6822"/>
+    <path fill="#ff7a00" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6824" d="m392.586761,76.615746a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#95d300" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6826" d="m402.524292,81.553246a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#00a6e4" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6828" d="m408.461792,90.615746a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#f9de00" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6830" d="m403.643463,99.60791a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+    <path fill="#e600ad" stroke-width="1.924773" stroke-miterlimit="4" stroke-dasharray="3.84954572, 3.84954572" stroke-dashoffset="3.657068" id="path6832" d="m393.518463,100.60791a3.5,3.5 0 1 1 -7,0a3.5,3.5 0 1 1 7,0z"/>
+   </g>
+  </g>
+ </g>
+</svg>
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -142,16 +142,17 @@ browser.jar:
   skin/classic/browser/window-controls/minimize-themes.svg       (window-controls/minimize-themes.svg)
   skin/classic/browser/window-controls/restore.svg               (window-controls/restore.svg)
   skin/classic/browser/window-controls/restore-highcontrast.svg  (window-controls/restore-highcontrast.svg)
   skin/classic/browser/window-controls/restore-themes.svg        (window-controls/restore-themes.svg)
 
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+  skin/classic/browser/canvas-popup.svg
 
 [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar:
 % override chrome://browser/skin/page-livemarks.png                   chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/audioFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/audioFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 % override chrome://browser/skin/feeds/videoFeedIcon.png              chrome://browser/skin/feeds/feedIcon.png
 % override chrome://browser/skin/feeds/videoFeedIcon16.png            chrome://browser/skin/feeds/feedIcon16.png
 
--- a/toolkit/themes/shared/popupnotification.inc.css
+++ b/toolkit/themes/shared/popupnotification.inc.css
@@ -122,8 +122,12 @@
 
 .popup-notification-dropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
   width: 16px;
   height: 16px;
   list-style-image: url(chrome://global/skin/icons/menubutton-dropmarker.svg);
   -moz-context-properties: fill;
   fill: currentColor;
 }
+
+.popup-notification-description[popupid=canvas-permissions-prompt] description {
+  white-space: pre-wrap;
+}