Bug 1315255 - Add ability for themes to change toolbar icons. r?mdeboer draft
authorJared Wein <jwein@mozilla.com>
Fri, 04 Nov 2016 16:50:51 -0400
changeset 440555 0317989ddb7500327728087bdb87343419affa64
parent 440554 76094c82d70cebcf7e596a43a96585cf3100d64b
child 537405 4af19273c7b2e3ae8443a3c629f61ff0beaa3367
push id36260
push userjwein@mozilla.com
push dateThu, 17 Nov 2016 19:58:19 +0000
reviewersmdeboer
bugs1315255
milestone53.0a1
Bug 1315255 - Add ability for themes to change toolbar icons. r?mdeboer MozReview-Commit-ID: H8qKR05PpBU
browser/components/extensions/ext-theme.js
browser/components/extensions/schemas/theme.json
browser/components/extensions/test/browser/browser.ini
browser/components/extensions/test/browser/browser_ext_theme_icons.js
browser/components/extensions/test/browser/burrito.svg
browser/components/extensions/test/browser/hamburger.svg
browser/components/extensions/test/browser/pile-of-poop.svg
--- a/browser/components/extensions/ext-theme.js
+++ b/browser/components/extensions/ext-theme.js
@@ -59,16 +59,49 @@ const kThemePropertiesVarMap = new Map([
   ["space_above_tabbar", ["--space-above-tabbar", ":root"]],
   ["square_tabs", ["--tab-curve-width", "#TabsToolbar"]],
   ["toolbar_button_shadow", ["--toolbarbutton-active-boxshadow", ":root"]],
   ["toolbar_button_shadow_inactive", ["--toolbarbutton-hover-boxshadow", ":root"]],
   ["toolbar_navbar_overlap", ["--tab-toolbar-navbar-overlap", ":root"]],
   ["toolbar_navbar_highlight_overlap", ["--navbar-tab-toolbar-highlight-overlap", ":root"]],
   ["toolbar_text_shadow", ["--toolbarbutton-text-shadow", ":root"]],
 ]);
+const kIconsVarMap = new Map([
+  ["back", ["--back-icon", ":root"]],
+  ["forward", ["--forward-icon", ":root"]],
+  ["reload", ["--reload-icon", ":root"]],
+  ["stop", ["--stop-icon", ":root"]],
+  ["bookmark_star", ["--bookmark_star-icon", ":root"]],
+  ["bookmark_menu", ["--bookmark_menu-icon", ":root"]],
+  ["downloads", ["--downloads-icon", ":root"]],
+  ["home", ["--home-icon", ":root"]],
+  ["app_menu", ["--app_menu-icon", ":root"]],
+  ["cut", ["--cut-icon", ":root"]],
+  ["copy", ["--copy-icon", ":root"]],
+  ["paste", ["--paste-icon", ":root"]],
+  ["new_window", ["--new_window-icon", ":root"]],
+  ["new_private_window", ["--new_private_window-icon", ":root"]],
+  ["save_page", ["--save_page-icon", ":root"]],
+  ["print", ["--print-icon", ":root"]],
+  ["history", ["--history-icon", ":root"]],
+  ["full_screen", ["--full_screen-icon", ":root"]],
+  ["find", ["--find-icon", ":root"]],
+  ["options", ["--options-icon", ":root"]],
+  ["addons", ["--addons-icon", ":root"]],
+  ["developer", ["--developer-icon", ":root"]],
+  ["synced_tabs", ["--synced_tabs-icon", ":root"]],
+  ["open_file", ["--open_file-icon", ":root"]],
+  ["sidebars", ["--sidebars-icon", ":root"]],
+  ["share_page", ["--share_page-icon", ":root"]],
+  ["subscribe", ["--subscribe-icon", ":root"]],
+  ["text_encoding", ["--text_encoding-icon", ":root"]],
+  ["email_link", ["--email_link-icon", ":root"]],
+  ["forget", ["--forget-icon", ":root"]],
+  ["pocket", ["--pocket-icon", ":root"]],
+]);
 
 // Some color settings or properties from the manifest need to trigger core browser
 // CSS styles to be overridden in order to be visually applied.
 // The static maps below provide the basic mechanism to do just that:
 // key< 'property_name' > => value< [ 'css_selector', 'css_style_properties_bag' ] >
 //   ^-- if present in the       ^-- An array that consists of
 //       manifest, this rule         0. The selector to which the following styles
 //       will be used                   should apply on, overriding the current theme.
@@ -148,16 +181,112 @@ const kBrowserOverridePropertiesVarMap =
     ["toolbar[brighttext] #downloads-indicator-counter", {"text-shadow": "--toolbarbutton-text-shadow"}],
   ]],
   ["space_above_tabbar", [
     // Give some space to drag the window around while customizing (normal space
     // to left and right of tabs doesn't work in this case)
     ["#main-window[tabsintitlebar][customizing]", {"--space-above-tabbar": "9px"}],
   ]],
 ]);
+const kBrowserOverrideIconsVarMap = new Map([
+  ["back", [
+    ["#back-button", {"list-style-image": "--back-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["forward", [
+    ["#forward-button", {"list-style-image": "--forward-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["reload", [
+    ["#urlbar-reload-button", {"list-style-image": "--reload-icon", "-moz-image-region": "rect(0, 14px, 14px, 0)"}]
+  ]],
+  ["stop", [
+    ["#urlbar-stop-button", {"list-style-image": "--stop-icon", "-moz-image-region": "rect(0, 14px, 14px, 0)"}]
+  ]],
+  ["bookmark_star", [
+    ["#bookmarks-menu-button", {"list-style-image": "--bookmark_star-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["bookmark_menu", [
+    ["#bookmarks-menu-button[cui-areatype='toolbar'] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon", {"list-style-image": "--bookmark_menu-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["downloads", [
+    ["#downloads-button", {"list-style-image": "--downloads-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)", "background-image": "none"}]
+  ]],
+  ["home", [
+    ["#home-button", {"list-style-image": "--home-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["app_menu", [
+    ["#PanelUI-menu-button", {"list-style-image": "--app_menu-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["cut", [
+    ["#cut-button", {"list-style-image": "--cut-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["copy", [
+    ["#copy-button", {"list-style-image": "--copy-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["paste", [
+    ["#paste-button", {"list-style-image": "--paste-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["new_window", [
+    ["#new-window-button", {"list-style-image": "--new_window-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["new_private_window", [
+    ["#privatebrowsing-button", {"list-style-image": "--new_private_window-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["save_page", [
+    ["#save-page-button", {"list-style-image": "--save_page-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["print", [
+    ["#print-button", {"list-style-image": "--print-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["history", [
+    ["#history-panelmenu", {"list-style-image": "--history-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["full_screen", [
+    ["#fullscreen-button", {"list-style-image": "--full_screen-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["find", [
+    ["#find-button", {"list-style-image": "--find-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["options", [
+    ["#preferences-button", {"list-style-image": "--options-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["addons", [
+    ["#add-ons-button", {"list-style-image": "--addons-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["developer", [
+    ["#developer-button", {"list-style-image": "--developer-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["synced_tabs", [
+    ["#sync-button", {"list-style-image": "--synced_tabs-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["open_file", [
+    ["#open-file-button", {"list-style-image": "--open_file-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["sidebars", [
+    ["#sidebar-button", {"list-style-image": "--sidebars-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["share_page", [
+    ["#social-share-button", {"list-style-image": "--share_page-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["subscribe", [
+    ["#feed-button", {"list-style-image": "--subscribe-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["text_encoding", [
+    ["#characterencoding-button", {"list-style-image": "--text_encoding-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["email_link", [
+    ["#email-link-button", {"list-style-image": "--email_link-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["forget", [
+    ["#panic-button", {"list-style-image": "--forget-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+  ["pocket", [
+    ["#pocket-button", {"list-style-image": "--pocket-icon", "-moz-image-region": "rect(0, 18px, 18px, 0)"}]
+  ]],
+]);
+
 const kTransparentGif = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
 const kVideoElementID = "__WebExtThemeVideoElement__";
 
 function hueToRgb(p, q, t) {
   if (t < 0) {
     t += 1;
   }
   if (t > 1) {
@@ -293,32 +422,35 @@ class Theme {
     this.browserStyleOverrides = {};
     this.cssVars = {};
     this.ntp_video = null;
     this.load(manifest.theme);
     this.render();
   }
 
   load(theme) {
-    // Order of sections matters here!
+    // Order of sections matters because gradients apply on top of colors.
     if (theme.images) {
       this.loadImages(theme.images);
     }
     if (theme.colors) {
       this.loadColors(theme.colors);
     }
     if (theme.tints) {
       this.loadTints(theme.tints);
     }
     if (theme.gradients) {
       this.loadGradients(theme.gradients);
     }
     if (theme.properties) {
       this.loadProperties(theme.properties);
     }
+    if (theme.icons) {
+      this.loadIcons(theme.icons);
+    }
   }
 
   loadColors(colors) {
     for (let color of Object.getOwnPropertyNames(colors)) {
       let val = colors[color];
       // Since values are optional, they may be `null`.
       if (val === null) {
         continue;
@@ -554,54 +686,72 @@ class Theme {
           if (kBrowserOverrideTintsVarMap.has(tint)) {
             addToBrowserOverrides(this.browserStyleOverrides, cssColor, kBrowserOverrideTintsVarMap.get(tint));
           }
         }
       }
     }
   }
 
+  loadIcons(icons) {
+    for (let icon of Object.getOwnPropertyNames(icons || {})) {
+      let val = icons[icon];
+      if (!val) {
+        continue;
+      }
+      if (kIconsVarMap.has(icon)) {
+        // The icons in the manifest are not pre-resolved, and must be local.
+        let resolvedURL = this.baseURI.resolve(val);
+
+        let cssIcon = `url("${resolvedURL.replace(/"/g, '\\"')}")`;
+        let [varName, ...selectors] = kIconsVarMap.get(icon);
+        addToCSSVars.apply(null, [this.cssVars, cssIcon, varName].concat(selectors));
+        addToBrowserOverrides(this.browserStyleOverrides, resolvedURL, kBrowserOverrideIconsVarMap.get(icon));
+      }
+    }
+  }
+
   receiveMessage(message) {
     if (message.name != "Ext:Theme:RequestRender") {
       return;
     }
 
     if (this.ntp_video) {
       Services.mm.broadcastAsyncMessage("Ext:Theme:Render", {
         video: this.ntp_video,
       });
     }
   }
 
   render({asUpdate} = {asUpdate: false}) {
     let browserStyles = [`
-      @-moz-document url("about:home"),
-                     url("about:newtab"),
-                     url("chrome://browser/content/abouthome/aboutHome.xhtml"),
-                     url("chrome://browser/content/newtab/newTab.xhtml") {`];
+      @-moz-document url("about:home"),\n
+                     url("about:newtab"),\n
+                     url("chrome://browser/content/abouthome/aboutHome.xhtml"),\n
+                     url("chrome://browser/content/newtab/newTab.xhtml") {\n`];
     for (let selector of Object.getOwnPropertyNames(this.aboutHomeCSSVars)) {
       browserStyles.push(`
         ${selector} {
-          ${this.aboutHomeCSSVars[selector].join(" !important;")} !important;
+          ${this.aboutHomeCSSVars[selector].join(" !important;\n")} !important;
         }`);
     }
     if (this.ntp_video) {
       browserStyles.push(`
         body {
           background-size: 100% !important;
         }`);
     }
     // Close the '@-moz-document' block.
     browserStyles.push(`
       }`);
 
     for (let selector of Object.getOwnPropertyNames(this.cssVars)) {
       browserStyles.push(`
         ${selector} {
-          ${this.cssVars[selector].join(" !important;")} !important;
+          ${this.cssVars[selector].join(" !important;\n")} !important;
         }`);
     }
     for (let selector of Object.getOwnPropertyNames(this.browserStyleOverrides)) {
       let styles = this.browserStyleOverrides[selector];
       if (!styles.length) {
         continue;
       }
       browserStyles.push(`
--- a/browser/components/extensions/schemas/theme.json
+++ b/browser/components/extensions/schemas/theme.json
@@ -183,24 +183,24 @@
               },
               "toolbar_bottom_separator": {
                 "type": "array",
                 "items": {
                   "type": "number"
                 },
                 "optional": true
               },
-              "toolbar_button_stroke": {
+              "toolbar_stroke": {
                 "type": "array",
                 "items": {
                   "type": "number"
                 },
                 "optional": true
               },
-              "toolbar_button_stroke_inactive": {
+              "toolbar_stroke_inactive": {
                 "type": "array",
                 "items": {
                   "type": "number"
                 },
                 "optional": true
               },
               "toolbar_input_control": {
                 "type": "array",
@@ -231,21 +231,21 @@
                 "optional": true
               }
             }
           },
           "gradients": {
             "type": "object",
             "optional": true,
             "properties": {
-              "toolbar_button": {
+              "toolbar": {
                 "type": "string",
                 "optional": true
               },
-              "toolbar_button_pressed": {
+              "toolbar_pressed": {
                 "type": "string",
                 "optional": true
               }
             }
           },
           "properties": {
             "type": "object",
             "optional": true,
@@ -284,21 +284,21 @@
               "tab_curve_half_width": {
                 "type": "number",
                 "optional": true
               },
               "tab_curve_width": {
                 "type": "number",
                 "optional": true
               },
-              "toolbar_button_shadow": {
+              "toolbar_shadow": {
                 "type": "string",
                 "optional": true
               },
-              "toolbar_button_shadow_inactive": {
+              "toolbar_shadow_inactive": {
                 "type": "string",
                 "optional": true
               },
               "toolbar_navbar_highlight_overlap": {
                 "type": "number",
                 "optional": true
               },
               "toolbar_navbar_overlap": {
@@ -325,16 +325,146 @@
               "buttons": {
                 "type": "array",
                 "items": {
                   "type": "number"
                 },
                 "optional": true
               }
             }
+          },
+          "icons": {
+            "type": "object",
+            "optional": true,
+            "properties": {
+              "back": {
+                "type": "string",
+                "optional": true
+              },
+              "forward": {
+                "type": "string",
+                "optional": true
+              },
+              "reload": {
+                "type": "string",
+                "optional": true
+              },
+              "stop": {
+                "type": "string",
+                "optional": true
+              },
+              "bookmark_star": {
+                "type": "string",
+                "optional": true
+              },
+              "bookmark_menu": {
+                "type": "string",
+                "optional": true
+              },
+              "downloads": {
+                "type": "string",
+                "optional": true
+              },
+              "home": {
+                "type": "string",
+                "optional": true
+              },
+              "app_menu": {
+                "type": "string",
+                "optional": true
+              },
+              "cut": {
+                "type": "string",
+                "optional": true
+              },
+              "copy": {
+                "type": "string",
+                "optional": true
+              },
+              "paste": {
+                "type": "string",
+                "optional": true
+              },
+              "new_window": {
+                "type": "string",
+                "optional": true
+              },
+              "new_private_window": {
+                "type": "string",
+                "optional": true
+              },
+              "save_page": {
+                "type": "string",
+                "optional": true
+              },
+              "print": {
+                "type": "string",
+                "optional": true
+              },
+              "history": {
+                "type": "string",
+                "optional": true
+              },
+              "full_screen": {
+                "type": "string",
+                "optional": true
+              },
+              "find": {
+                "type": "string",
+                "optional": true
+              },
+              "options": {
+                "type": "string",
+                "optional": true
+              },
+              "addons": {
+                "type": "string",
+                "optional": true
+              },
+              "developer": {
+                "type": "string",
+                "optional": true
+              },
+              "synced_tabs": {
+                "type": "string",
+                "optional": true
+              },
+              "open_file": {
+                "type": "string",
+                "optional": true
+              },
+              "sidebars": {
+                "type": "string",
+                "optional": true
+              },
+              "share_page": {
+                "type": "string",
+                "optional": true
+              },
+              "subscribe": {
+                "type": "string",
+                "optional": true
+              },
+              "text_encoding": {
+                "type": "string",
+                "optional": true
+              },
+              "email_link": {
+                "type": "string",
+                "optional": true
+              },
+              "forget": {
+                "type": "string",
+                "optional": true
+              },
+              "pocket": {
+                "type": "string",
+                "optional": true
+              }
+            }
           }
         }
       },
       {
         "$extend": "WebExtensionManifest",
         "properties": {
           "theme": {
             "optional": true,
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -93,16 +93,21 @@ tags = webextensions
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_theme_abouthomebackground.js]
 [browser_ext_theme_chromeThemeSupport.js]
 [browser_ext_theme_devEdition.js]
 [browser_ext_theme_extreme.js]
 [browser_ext_theme_lwtsupport.js]
+[browser_ext_theme_icons.js]
+support-files =
+  burrito.svg
+  hamburger.svg
+  pile-of-poop.svg
 [browser_ext_topwindowid.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
 [browser_ext_windows_allowScriptsToClose.js]
 [browser_ext_windows_create.js]
 tags = fullscreen
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_theme_icons.js
@@ -0,0 +1,142 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const kBurgerIcon = "hamburger.svg";
+const kBurritoIcon = "burrito.svg";
+const kPoopIcon = "pile-of-poop.svg";
+
+function iconForButton(buttonName) {
+  let icon = kPoopIcon;
+  if (buttonName == "app_menu") {
+    icon = kBurgerIcon;
+  } else if (buttonName == "back") {
+    icon = kBurritoIcon;
+  }
+  return icon;
+}
+
+function shouldButtonHaveCustomStyling(selector, verifyFn, icon, message) {
+  try {
+    let element;
+    if (selector == "#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon") {
+      if (message.includes("panel")) {
+        // The dropmarker isn't shown in the menupanel.
+        return;
+      }
+      element = document.querySelector("#bookmarks-menu-button");
+      element = document.getAnonymousElementByAttribute(element, "class", "toolbarbutton-menubutton-dropmarker");
+      element = document.getAnonymousElementByAttribute(element, "class", "dropmarker-icon");
+    } else {
+      element = document.querySelector(selector);
+    }
+
+    let iconFileName = iconForButton(icon);
+    let listStyleImage = getComputedStyle(element).listStyleImage;
+    info(`listStyleImage for ${iconFileName} is ${listStyleImage}`);
+    verifyFn(listStyleImage.includes(iconFileName), message);
+  } catch (ex) {
+    ok(false, `Unable to verify ${selector}: ${ex}`);
+  }
+}
+
+function verifyButtonWithoutCustomStyling(selector, icon, message) {
+  shouldButtonHaveCustomStyling(selector, (result, message) => ok(!result, message), icon, message);
+}
+
+function verifyButtonWithCustomStyling(selector, icon, message) {
+  shouldButtonHaveCustomStyling(selector, ok, icon, message);
+}
+
+add_task(function* testCustomizedIcons() {
+  let buttons = [
+    ["back", "#back-button"],
+    ["forward", "#forward-button"],
+    ["reload", "#urlbar-reload-button"],
+    ["stop", "#urlbar-stop-button"],
+    ["bookmark_star", "#bookmarks-menu-button", "bookmarks-menu-button"],
+    ["bookmark_menu", "#bookmarks-menu-button > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon"],
+    ["downloads", "#downloads-button", "downloads-button"],
+    ["home", "#home-button", "home-button"],
+    ["app_menu", "#PanelUI-menu-button"],
+    ["cut", "#cut-button", "edit-controls"],
+    ["copy", "#copy-button"],
+    ["paste", "#paste-button"],
+    ["new_window", "#new-window-button", "new-window-button"],
+    ["new_private_window", "#privatebrowsing-button", "privatebrowsing-button"],
+    ["save_page", "#save-page-button", "save-page-button"],
+    ["print", "#print-button", "print-button"],
+    ["history", "#history-panelmenu", "history-panelmenu"],
+    ["full_screen", "#fullscreen-button", "fullscreen-button"],
+    ["find", "#find-button", "find-button"],
+    ["options", "#preferences-button", "preferences-button"],
+    ["addons", "#add-ons-button", "add-ons-button"],
+    ["developer", "#developer-button", "developer-button"],
+    ["synced_tabs", "#sync-button", "sync-button"],
+    ["open_file", "#open-file-button", "open-file-button"],
+    ["sidebars", "#sidebar-button", "sidebar-button"],
+    ["share_page", "#social-share-button", "social-share-button"],
+    ["subscribe", "#feed-button", "feed-button"],
+    ["text_encoding", "#characterencoding-button", "characterencoding-button"],
+    ["email_link", "#email-link-button", "email-link-button"],
+    ["forget", "#panic-button", "panic-button"],
+    ["pocket", "#pocket-button", "pocket-button"]
+  ];
+
+  window.maximize();
+
+  try {
+    for (let button of buttons) {
+      if (button[2]) {
+        CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_NAVBAR);
+      }
+
+      verifyButtonWithoutCustomStyling(button[1], button[0],
+        `The ${button[1]} should not have it's icon customized when the test starts`);
+    }
+
+    let manifest = {
+      theme: {
+        icons: {}
+      }
+    };
+
+    for (let button of buttons) {
+      manifest.theme.icons[button[0]] = iconForButton(button[0]);
+    }
+
+    let extension = ExtensionTestUtils.loadExtension({manifest});
+
+    yield extension.startup();
+
+    for (let button of buttons) {
+      verifyButtonWithCustomStyling(button[1], button[0],
+        `The ${button[1]} should have it's icon customized in the toolbar`);
+    }
+
+    for (let button of buttons) {
+      if (button[2]) {
+        CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_PANEL);
+      }
+      yield PanelUI.show();
+    }
+
+    for (let button of buttons) {
+      verifyButtonWithCustomStyling(button[1], button[0],
+        `The ${button[1]} should have it's icon customized in the panel`);
+    }
+
+    yield PanelUI.hide();
+
+    yield extension.unload();
+
+    for (let button of buttons) {
+      verifyButtonWithoutCustomStyling(button[1], button[0],
+        `The ${button[1]} should not have it's icon customized when the theme is unloaded`);
+    }
+  } finally {
+    CustomizableUI.reset();
+    window.restore();
+  }
+
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/burrito.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="m58.2 4.1c-3.8-4-21.8-4.9-47.1 20.2-10.2 10.1-6.3 32 10.2 36.2 18.2 4.6 24.1-5 27.3-9.4 10.1-13.7 18.6-43.5 9.6-47" fill="#fddfb3"/><path d="M44.8,30.2C38.5,20,29.7,20,24.9,20.3C2.3,21.6,1.4,55.5,23.2,59.4C47.4,63.9,51.1,40.5,44.8,30.2z" fill="#89664c"/><g fill="#d3976e"><path d="m25.3 29.2l-6 2-1.3 7.9 7 .4z"/><path d="m26 50.7l-3.2-6.9-7.3-.6.2 9.1z"/><path d="m19.8 47.5c0 0-5.7.3-6.7 2.5-1 2.3-2.5 6.3-1.5 8 1 1.7 5.9 2.5 8.2 1 2.4-1.5 1.8-11.7 0-11.5"/></g><path d="m13.1 50c-1 2.3-2.5 6.3-1.5 8-.9-3.4 4.5-1.8 6-2.4 1.5-.6 2.2-8.1 2.2-8.1s-5.7.2-6.7 2.5" opacity=".5" fill="#89664c"/><path d="m16.7 47c0 0-2-6.3-4.4-6.7-2.4-.4-6.5-.9-7.8.8-1.3 1.7-.5 7.3 1.6 9.5 2.1 2.2 11.3-1.6 10.6-3.6" fill="#d3976e"/><path d="m12.3 40.4c-2.4-.4-6.5-.9-7.8.8 2.8-2 3.1 4.4 4.1 5.9 1 1.5 8.1-.1 8.1-.1s-2-6.2-4.4-6.6" opacity=".5" fill="#89664c"/><path d="m14.3 29.8c0 0-5-2.4-6.8-1s-4.5 4.3-4.3 6.3c.2 2 4.1 4.9 6.7 4.7 2.7-.2 6-9.4 4.4-10" fill="#d3976e"/><path d="m7.6 28.8c-1.7 1.5-4.6 4.3-4.4 6.2.5-3.3 4.6.5 6.1.7 1.5.2 5-6 5-6s-5-2.4-6.7-.9" opacity=".5" fill="#89664c"/><path d="m23.8 23c0 0-4.8-3.9-6.9-2.7-2.1 1.1-5.8 3.3-5.9 5.5-.2 2.2 3.3 6.2 6.1 6.7 2.7.4 8.2-8.4 6.7-9.5" fill="#d3976e"/><path d="m16.9 20.3c-2.1 1.1-5.8 3.3-5.9 5.5 1.2-3.4 4.7 1.7 6.3 2.3 1.6.6 6.6-5 6.6-5s-4.9-4-7-2.8" opacity=".5" fill="#89664c"/><g fill="#ffc7ce"><path d="m26.2 42.3c-3.6 2-4.4 3-3.9 6.1.5 3.1 3-.8 8.2-2.3s2.4-7.6-4.3-3.8"/><path d="m29.2 31.5c-2.8-3.2-3.9-3.8-6.4-2.4-2.5 1.4 1.6 3 4.3 7.9s7.2.4 2.1-5.5"/></g><g fill="#83bf4f"><path d="m9.4 40.4c-5.1 3.4-8.7 6.3-7 7.6 2.2 1.6 8.9-4.5 11.5-7.7 2.7-3.2 6-6.8-4.5.1"/><path d="m14 49.7c-4.9 3.5-8.3 6.5-6.6 7.7 2.2 1.6 8.7-4.8 11.1-8 2.4-3.2 5.6-6.9-4.5.3"/><path d="m27.5 53.7c-5.7 2.2-9.8 4.3-8.4 6 1.8 2.1 9.7-2.6 12.9-5.1 3.3-2.6 7.3-5.5-4.5-.9"/><path d="m28.7 38.4c-6 1.1-10.5 2.4-9.4 4.3 1.4 2.4 10-.7 13.7-2.6 3.7-2 8.2-4-4.3-1.7"/><path d="m32.4 21.8c-5.5-1.5-9.8-2.2-9.6-.2.2 2.6 8.7 3.5 12.6 3.4 3.8-.2 8.3-.1-3-3.2"/></g><g fill="#fffbe9"><path d="m32 61.8c-.8.4-.1.2 0 0"/><path d="m44.2 28.3l-3.5-3.3-2.4-.2-1.3-2.5-4.1-.2-.8 3.1-3.2-.1v3l-2.4 1.3 4.1 3.6-.8 4.5 2.1 5.6-3.7 4 .6 1.6 1.9-1.3-2.6 7 3.1-2.6-2.3 6.4h2l-2 3 8-2 5.1-3.7 4.2-5.5-1.3-2.8 2.6-4 .2-4.4-1.1-4.5-2.7-3.5z"/></g><g fill="#e8e1d6"><path d="m42.5 51.2l-1.2 4 2.6-2.8.9-3.8z"/><path d="m45 38.9l.5 2.8 2-2.9-.7-2.8z"/><path d="m42.5 31.6l.1 3.1 2.4 1.9.1-3.1z"/><path d="m36.3 41.1l.1 3.1 2.4 1.9.1-3z"/><path d="m30.4 45l.1 3.1 2.4 1.9.1-3.1z"/><path d="m41.7 51.2l-3.1 2-1.7 3 3-1z"/><path d="m35 53l-3.1 2-1.7 3 3-1z"/><path d="m41.8 28.4l-1.6-2.8-3.7-1.1 1.8 2.8z"/><path d="m33.8 34.4l2.3 2.2 3.4-.1-2.3-2.1z"/><path d="m34.9 48l2.4 2.2 3.4-.1-2.3-2.1z"/><path d="m35.9 25.9l-3.6-.8-2.8 1.4 3.5.9z"/><path d="m33.4 31.9l-3.6-.8-2.9 1.5 3.6.8z"/><path d="m40.2 40l1.8-2.8-.7-2.9-1.7 2.9z"/><path d="m34.7 38.7l-1.1-2.8-1.3 2.9.9 2.7z"/><path d="m43.9 43.3l-1-2.8-1.3 2.9.8 2.8z"/><path d="m38.3 30.9l-1.1-2.8-1.3 2.8.9 2.8z"/></g><path d="m23 50.4c-2.4-2.9-3.3-3.4-5.5-2.3-2.2 1.1 1.3 2.6 3.5 7 2.3 4.4 6.4.6 2-4.7" fill="#ffc7ce"/><g fill="#e8662d"><path d="m25.5 47.8l-3.6 4.4 1.7 3.6 5.7-.2 1.5-6.6-2.1-2.3-3.2 1.1"/><path d="m28.7 46.7l-3.2 1.1-3.6 4.4 1.7 3.6 5.7-.2 1.5-6.6-2.1-2.3"/><path d="m23.5 22.2l-3.6 5.8 2.6 3.9 6.7-1.2.7-8.1-2.7-2.3-3.7 1.9"/><path d="m27.2 20.3l-3.7 1.9-3.6 5.8 2.6 3.9 6.7-1.2.7-8.1-2.7-2.3"/><path d="m10.1 45.4l-1.2 4.3 2.2 1.8 3.9-2.1-.9-5-2.1-.9-1.9 1.9"/><path d="m12 43.5l-1.9 1.9-1.2 4.3 2.2 1.8 3.9-2.1-.9-5-2.1-.9"/><path d="m14 34.9v6.4l3.7 1.5 4.5-4.7-3.2-6.5-3.2-.2-1.8 3.5"/><path d="m15.8 31.4l-1.8 3.5v6.4l3.7 1.5 4.5-4.7-3.2-6.5-3.2-.2"/></g><g fill="#89664c"><path opacity=".5" d="m39.3 12l-1 1-1-1 1-1z"/><path opacity=".5" d="m53.9 25.1l-1 1-1-1 1-1z"/><path opacity=".5" d="m37.5 15.3l-1 1-1-1 1-1z"/><path opacity=".5" d="m48.9 7.3l-1 1-1-1 1-1z"/><path transform="matrix(.705-.7092.7092.705 7.7418 38.1628)" opacity=".5" d="m49.4 9.4h.7v.7h-.7z"/><path transform="matrix(.705-.7092.7092.705-3.9034 43.8373)" opacity=".5" d="m50.4 26.3h.7v.7h-.7z"/><path transform="matrix(.7064-.7078.7078.7064 10.5203 36.9043)" opacity=".5" d="m49.4 5.4h.7v.7h-.7z"/><path opacity=".5" d="m55.4 28.1l-1.5 1.5-1.5-1.5 1.5-1.5z"/><path opacity=".5" d="m35.6 13.8l-2 2-2-2 2-2z"/><path d="m32.4 17.5c9.1 2.5 25.5 2.9 25.8-13.4 0 0 2.1 16.7-14.4 16.9-12.9.1-15.7-4.9-24.9-3.8-.1 0 5.1-2 13.5.3" opacity=".33"/></g></svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/hamburger.svg
@@ -0,0 +1,2 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="m62 44.5c0 12-10.9 17.5-29.8 17.5-19 0-29.8-5.5-29.8-17.5 0-17.6 59.6-17.6 59.6 0" fill="#e7a74f"/><path d="m61.8 36.2c0 12-10.8 17.5-29.7 17.5-18.8 0-29.7-5.5-29.7-17.5.1-17.5 59.4-17.5 59.4 0" fill="#ba8443"/><path d="m2.4 36.5c0-5 5.9-11.9 29.8-11.9s29.8 7 29.8 11.9c0 9.1-8.4 15.7-29.8 15.7-21.4 0-29.8-6.6-29.8-15.7" fill="#826046"/><path d="M3,31.3c0-5,5.8-11.9,29.2-11.9c23.4,0,29.2,7,29.2,11.9c0,9.1-8.2,15.7-29.2,15.7S3,40.4,3,31.3z" fill="#68472c"/><path d="m3.2 24.8c1.4-3 5.1-3.2 17.1 1.9 12 5.1 14.3 7.8 12.9 10.9-2.6 5.5-9.7 9.6-20.5 5s-12.2-12.2-9.5-17.8" fill="#ef4d3c"/><path d="m4.2 21.4c1.4-3 5.1-3.2 17.1 1.9 12 5.1 14.3 7.8 12.9 10.9-2.6 5.5-9.7 9.6-20.5 5-10.8-4.6-12.2-12.3-9.5-17.8" fill="#d33b23"/><path d="m29.3 34.5c-.8-3.2 2-5.5 14.9-8.3s16.4-2 17.2 1.3c1.5 5.9-1.5 13.2-13.1 15.7-11.5 2.5-17.6-2.8-19-8.7" fill="#ef4d3c"/><path d="m28 31.3c-.8-3.2 2-5.5 14.9-8.3 12.9-2.8 16.4-2 17.2 1.3 1.5 5.9-1.5 13.2-13.1 15.7-11.5 2.5-17.5-2.8-19-8.7" fill="#d33b23"/><path fill="#ffe62e" d="m9.7 30.9l20 19.2 19.6-19.2z"/><path d="m60.7 25.9c0 0 .9 3.4 0 4.4-.8.9-3.3-.1-4.4.7-1 .7-.7 3.4-1.9 4-1.1.6-3.2-1.1-4.4-.6-1.1.4-1.5 3.1-2.7 3.5-1.2.3-3-1.7-4.2-1.5-1.2.2-2 2.8-3.3 3-1.2.2-2.7-2.1-4-2-1.2.1-2.4 2.5-3.7 2.5-1.3 0-2.4-2.5-3.7-2.5-1.3-.1-2.7 2.2-4 2-1.3-.2-2.1-2.7-3.3-3-1.3-.3-3 1.8-4.2 1.5-1.2-.4-1.6-3-2.7-3.5-1.2-.5-3.4 1.2-4.4.6-1.2-.6-.9-3.3-1.9-4-1.1-.8-3.6.2-4.4-.7-.9-1 0-4.4 0-4.4h57.2" fill="#8cc63e"/><path d="M62,23.4c0,6.5-13.3,9.7-29.8,9.7c-16.5,0-29.8-3.2-29.8-9.7C2.4,11.6,15.7,2,32.2,2C48.7,2,62,11.6,62,23.4z
+			" fill="#e7a74f"/><g fill="#ffc17a"><path d="m16.3 14.4l-1.6 1.5-1.5-1.5 1.5-1.5z"/><path d="m23.7 12l-1.5 1.5-1.5-1.5 1.5-1.5z"/><path d="m28 18.8l-1.6 1.5-1.5-1.5 1.5-1.5z"/><path d="m33.2 13.4l-1 1-1-1 1-1z"/><path d="m51.9 17.3l-1 1-1-1 1-1z"/><path d="m58.1 18.3l-1 1-1-1 1-1z"/><path d="m46.4 11l-1 1-1-1 1-1z"/><path d="m15.9 9.7l-1 1-1-1 1-1z"/><path d="m10.5 14.4l-1 1-1-1 1-1z"/><path d="m16.8 21.1l-1 1-1.1-1 1.1-1z"/><path d="m28.5 5.7l-1 1-1.1-1 1.1-1z"/><path d="m38 4.7l-1 1-1-1 1-1z"/><path d="m22.2 6.7l-1 1-1-1 1-1z"/><path d="m50.9 9.1l-1 1-1-1 1-1z"/><path d="m40 21.7l-1.5 1.5-1.5-1.5 1.5-1.5z"/><path d="m37.6 14l-1.5 1.5-1.5-1.5 1.5-1.5z"/><path d="m43.2 8.2l-1.5 1.5-1.5-1.5 1.5-1.5z"/><path d="m47.6 14.7l-1.5 1.5-1.6-1.5 1.6-1.5z"/><path d="m54 12.5l-1.5 1.5-1.6-1.5 1.6-1.5z"/><path d="m49.1 21.3l-1.5 1.5-1.5-1.5 1.5-1.5z"/><path d="m12.5 18.7l-1.5 1.5-1.5-1.5 1.5-1.5z"/></g></svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/pile-of-poop.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" enable-background="new 0 0 64 64"><path d="m32.2 36.9c-17.1 0-30.2-7.1-30.2 8.8 0 12.8 16.9 16.3 29.8 16.3 15 0 30.2-3.5 30.2-16.3 0-15.9-12.2-8.8-29.8-8.8" fill="#89664c"/><path d="m31.7 20.9c-9.6 0-24.5 1.1-24.5 12.6 0 16.8 49.5 16.8 49.5 0 .1-11.5-14.8-12.6-25-12.6" fill="#9b7861"/><path d="m49 16.7c-4.6-10.3-26.4.3-22.6-12.2.6-1.9.5-2.7-.9-2.4-7.8 1.6-13.5 9.6-11.5 16.3 6.4 21.4 41.3 12.3 35-1.7" fill="#a88673"/><path d="m28.8 34.3c0 4-3.2 7.2-7.2 7.2-4 0-7.2-3.2-7.2-7.2 0-4 3.2-7.2 7.2-7.2 4 0 7.2 3.2 7.2 7.2" fill="#fff"/><circle cx="23.6" cy="34.3" r="3.6" fill="#231f20"/><path d="m49.6 34.3c0 4-3.2 7.2-7.2 7.2-4 0-7.2-3.2-7.2-7.2 0-4 3.2-7.2 7.2-7.2 3.9 0 7.2 3.2 7.2 7.2" fill="#fff"/><g fill="#231f20"><circle cx="40.4" cy="34.3" r="3.6"/><path d="m38 50.6c0 3.3-2.7 6-6 6-3.3 0-6-2.7-6-6 0-3.3 2.7-6 6-6 3.3 0 6 2.7 6 6"/></g></svg>
\ No newline at end of file