Backed out changeset e8dbcc5c516f (bug 1548769) test_ext_manifest_themes.js on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Wed, 08 May 2019 23:10:44 +0300
changeset 531929 eb55d30a839a0cf77a84e00909c069d6c7e050fb
parent 531928 6d4ac4873f7af5683f73ba2c66b832c75e7619e1
child 531930 14654d375cc67708a8fc03e7e3e87bcad8d5abe6
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1548769
milestone68.0a1
backs oute8dbcc5c516f05fe34f90efaf6fd199118979012
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset e8dbcc5c516f (bug 1548769) test_ext_manifest_themes.js on a CLOSED TREE
browser/app/profile/firefox.js
browser/base/content/browser.css
browser/base/content/theme-vars.inc.css
browser/components/extensions/test/browser/browser-common.ini
browser/components/extensions/test/browser/browser_ext_themes_icons.js
modules/libpref/init/all.js
toolkit/components/extensions/parent/ext-theme.js
toolkit/components/extensions/schemas/theme.json
toolkit/modules/LightweightThemeConsumer.jsm
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -180,16 +180,18 @@ pref("app.update.BITS.enabled", false);
 //  .. etc ..
 //
 pref("extensions.update.enabled", true);
 pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.interval", 86400);  // Check for updates to Extensions and
                                             // Themes every day
 
+pref("extensions.webextensions.themes.icons.buttons", "back,forward,reload,stop,bookmark_star,bookmark_menu,downloads,home,app_menu,cut,copy,paste,new_window,new_private_window,save_page,print,history,full_screen,find,options,addons,developer,synced_tabs,open_file,sidebars,share_page,subscribe,text_encoding,email_link,forget,pocket");
+
 pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
 
 #if defined(MOZ_WIDEVINE_EME)
 pref("browser.eme.ui.enabled", true);
 #else
 pref("browser.eme.ui.enabled", false);
 #endif
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1416,8 +1416,10 @@ toolbarpaletteitem > toolbaritem {
   #sidebar-box[sidebarcommand$="-sidebar-action"] > #sidebar-header > #sidebar-switcher-target > #sidebar-icon {
     list-style-image: var(--webextension-menuitem-image-2x, inherit);
   }
 }
 
 toolbar[keyNav=true]:not([collapsed=true]):not([customizing=true]) toolbartabstop {
   -moz-user-focus: normal;
 }
+
+%include theme-vars.inc.css
new file mode 100644
--- /dev/null
+++ b/browser/base/content/theme-vars.inc.css
@@ -0,0 +1,150 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+:root[lwthemeicons~="--back-icon"] #back-button:-moz-lwtheme {
+  list-style-image: var(--back-icon) !important;
+}
+
+:root[lwthemeicons~="--forward-icon"] #forward-button:-moz-lwtheme {
+  list-style-image: var(--forward-icon) !important;
+}
+
+:root[lwthemeicons~="--reload-icon"] #reload-button:-moz-lwtheme {
+  list-style-image: var(--reload-icon) !important;
+}
+
+:root[lwthemeicons~="--stop-icon"] #stop-button:-moz-lwtheme {
+  list-style-image: var(--stop-icon) !important;
+}
+
+:root[lwthemeicons~="--bookmark_star-icon"] #star-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_star-icon"] #bookmarks-menu-button:-moz-lwtheme {
+  list-style-image: var(--bookmark_star-icon) !important;
+}
+
+:root[lwthemeicons~="--bookmark_menu-icon"] #bookmarks-menu-button:-moz-lwtheme {
+  list-style-image: var(--bookmark_menu-icon) !important;
+}
+
+:root[lwthemeicons~="--downloads-icon"] #downloads-button:-moz-lwtheme {
+  list-style-image: var(--downloads-icon) !important;
+}
+
+:root[lwthemeicons~="--home-icon"] #home-button:-moz-lwtheme {
+  list-style-image: var(--home-icon) !important;
+}
+
+:root[lwthemeicons~="--app_menu-icon"] #PanelUI-menu-button:-moz-lwtheme {
+  list-style-image: var(--app_menu-icon) !important;
+}
+
+:root[lwthemeicons~="--cut-icon"] #cut-button:-moz-lwtheme {
+  list-style-image: var(--cut-icon) !important;
+}
+
+:root[lwthemeicons~="--copy-icon"] #copy-button:-moz-lwtheme {
+  list-style-image: var(--copy-icon) !important;
+}
+
+:root[lwthemeicons~="--paste-icon"] #paste-button:-moz-lwtheme {
+  list-style-image: var(--paste-icon) !important;
+}
+
+:root[lwthemeicons~="--new_window-icon"] #new-window-button:-moz-lwtheme {
+  list-style-image: var(--new_window-icon) !important;
+}
+
+:root[lwthemeicons~="--new_private_window-icon"] #privatebrowsing-button:-moz-lwtheme {
+  list-style-image: var(--new_private_window-icon) !important;
+}
+
+:root[lwthemeicons~="--save_page-icon"] #save-page-button:-moz-lwtheme {
+  list-style-image: var(--save_page-icon) !important;
+}
+
+:root[lwthemeicons~="--print-icon"] #print-button:-moz-lwtheme {
+  list-style-image: var(--print-icon) !important;
+}
+
+:root[lwthemeicons~="--history-icon"] #history-panelmenu:-moz-lwtheme {
+  list-style-image: var(--history-icon) !important;
+}
+
+:root[lwthemeicons~="--full_screen-icon"] #fullscreen-button:-moz-lwtheme {
+  list-style-image: var(--full_screen-icon) !important;
+}
+
+:root[lwthemeicons~="--find-icon"] #find-button:-moz-lwtheme {
+  list-style-image: var(--find-icon) !important;
+}
+
+:root[lwthemeicons~="--options-icon"] #preferences-button:-moz-lwtheme {
+  list-style-image: var(--options-icon) !important;
+}
+
+:root[lwthemeicons~="--addons-icon"] #add-ons-button:-moz-lwtheme {
+  list-style-image: var(--addons-icon) !important;
+}
+
+:root[lwthemeicons~="--developer-icon"] #developer-button:-moz-lwtheme {
+  list-style-image: var(--developer-icon) !important;
+}
+
+:root[lwthemeicons~="--synced_tabs-icon"] #sync-button:-moz-lwtheme {
+  list-style-image: var(--synced_tabs-icon) !important;
+}
+
+:root[lwthemeicons~="--open_file-icon"] #open-file-button:-moz-lwtheme {
+  list-style-image: var(--open_file-icon) !important;
+}
+
+:root[lwthemeicons~="--sidebars-icon"] #sidebar-button:-moz-lwtheme {
+  list-style-image: var(--sidebars-icon) !important;
+}
+
+:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme {
+  list-style-image: var(--text_encoding-icon) !important;
+}
+
+:root[lwthemeicons~="--email_link-icon"] #email-link-button:-moz-lwtheme {
+  list-style-image: var(--email_link-icon) !important;
+}
+
+:root[lwthemeicons~="--forget-icon"] #panic-button:-moz-lwtheme {
+  list-style-image: var(--forget-icon) !important;
+}
+
+:root[lwthemeicons~="--bookmark_star-icon"] #star-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_menu-icon"] #bookmarks-menu-button:-moz-lwtheme,
+:root[lwthemeicons~="--back-icon"] #back-button:-moz-lwtheme,
+:root[lwthemeicons~="--forward-icon"] #forward-button:-moz-lwtheme,
+:root[lwthemeicons~="--reload-icon"] #reload-button:-moz-lwtheme,
+:root[lwthemeicons~="--stop-icon"] #stop-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_star-icon"] #bookmarks-menu-button:-moz-lwtheme,
+:root[lwthemeicons~="--downloads-icon"] #downloads-button:-moz-lwtheme,
+:root[lwthemeicons~="--home-icon"] #home-button:-moz-lwtheme,
+:root[lwthemeicons~="--app_menu-icon"] #PanelUI-menu-button:-moz-lwtheme,
+:root[lwthemeicons~="--cut-icon"] #cut-button:-moz-lwtheme,
+:root[lwthemeicons~="--copy-icon"] #copy-button:-moz-lwtheme,
+:root[lwthemeicons~="--paste-icon"] #paste-button:-moz-lwtheme,
+:root[lwthemeicons~="--new_window-icon"] #new-window-button:-moz-lwtheme,
+:root[lwthemeicons~="--new_private_window-icon"] #privatebrowsing-button:-moz-lwtheme,
+:root[lwthemeicons~="--save_page-icon"] #save-page-button:-moz-lwtheme,
+:root[lwthemeicons~="--print-icon"] #print-button:-moz-lwtheme,
+:root[lwthemeicons~="--history-icon"] #history-panelmenu:-moz-lwtheme,
+:root[lwthemeicons~="--full_screen-icon"] #fullscreen-button:-moz-lwtheme,
+:root[lwthemeicons~="--find-icon"] #find-button:-moz-lwtheme,
+:root[lwthemeicons~="--options-icon"] #preferences-button:-moz-lwtheme,
+:root[lwthemeicons~="--addons-icon"] #add-ons-button:-moz-lwtheme,
+:root[lwthemeicons~="--developer-icon"] #developer-button:-moz-lwtheme,
+:root[lwthemeicons~="--synced_tabs-icon"] #sync-button:-moz-lwtheme,
+:root[lwthemeicons~="--open_file-icon"] #open-file-button:-moz-lwtheme,
+:root[lwthemeicons~="--sidebars-icon"] #sidebar-button:-moz-lwtheme,
+:root[lwthemeicons~="--text_encoding-icon"] #characterencoding-button:-moz-lwtheme,
+:root[lwthemeicons~="--email_link-icon"] #email-link-button:-moz-lwtheme,
+:root[lwthemeicons~="--forget-icon"] #panic-button:-moz-lwtheme {
+  -moz-image-region: rect(0, 16px, 16px, 0) !important;
+}
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -237,16 +237,17 @@ skip-if = os == 'mac' # Save as PDF not 
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_sharingState.js]
 [browser_ext_tabs_successors.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_update_highlighted.js]
 [browser_ext_tabs_update_url.js]
 [browser_ext_tabs_zoom.js]
+[browser_ext_themes_icons.js]
 [browser_ext_themes_validation.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_user_events.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_themes_icons.js
@@ -0,0 +1,244 @@
+"use strict";
+
+const ENCODED_IMAGE_DATA = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNjQgNjQiPjxwYXRoIGQ9Im01NS45IDMyLjFsLTIyLjctMTQuOWMwIDAgMTIuOS0xNy40IDE5LjQtMTQuOSAzLjEgMS4xIDUuNCAyNS4xIDMuMyAyOS44IiBmaWxsPSIjM2U0MzQ3Ii8+PHBhdGggZD0ibTU0LjkgMzMuOWwtOS00LjFjMCAwLTUuMy0xNCA2LjEtMjQuMSAyLjQgMiA1LjEgMjUgMi45IDI4LjIiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJtOC4xIDMyLjFsMjIuNi0xNC45YzAgMC0xMi45LTE3LjQtMTkuNC0xNC45LTMgMS4xLTUuMyAyNS4xLTMuMiAyOS44IiBmaWxsPSIjM2U0MzQ3Ii8+PHBhdGggZD0ibTkuMSAzMy45bDktNC4xYzAgMCA1LjMtMTQtNi4xLTI0LjEtMi40IDItNS4xIDI1LTIuOSAyOC4yIiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTMyLDEzQzE4LjksMTMsMiwzMy42LDIsNDUuNEMyMC41LDQ1LjQsMTkuNyw2MiwzMiw2MnMxMS41LTE2LjYsMzAtMTYuNkM2MiwzMy42LDQ1LjEsMTMsMzIsMTN6IiBmaWxsPSIjZmY4NzM2Ii8+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTMyLDU2LjJjMCw1LjEsOS42LDQuMiw5LjUtMi45YzYuNy05LjQsMTkuOS04LjcsMTkuOS04LjdDMzkuNiwzMi40LDMyLDU2LjIsMzIsNTYuMnoiLz48cGF0aCBkPSJNMzIsNTYuMmMwLDUuMS05LjYsNC4yLTkuNS0yLjlDMTUuOCw0NCwyLjYsNDQuNywyLjYsNDQuN0MyNC40LDMyLjQsMzIsNTYuMiwzMiw1Ni4yeiIvPjwvZz48ZyBmaWxsPSIjZmY4NzM2Ij48cGF0aCBkPSJtNTMuNCAxOC41Yy00IC43LTQuOSA2LjMtNC45IDYuM2w2IDUuM2MtMi4zLTUuOS0xLjEtMTEuNi0xLjEtMTEuNiIvPjxwYXRoIGQ9Im01MS4xIDEzLjVjLTQuNCAzLjktNS4xIDguNy01LjEgOC43bDYgNS4zYy0yLjQtNS44LS45LTE0LS45LTE0Ii8+PHBhdGggZD0ibTEwLjYgMTguNWM0IC43IDQuOSA2LjMgNC45IDYuM2wtNiA1LjNjMi4zLTUuOSAxLjEtMTEuNiAxLjEtMTEuNiIvPjxwYXRoIGQ9Im0xMi45IDEzLjVjNC40IDMuOSA1LjEgOC43IDUuMSA4LjdsLTYgNS4zYzIuNC01LjguOS0xNCAuOS0xNCIvPjwvZz48cGF0aCBkPSJtNTIuOCAzMS4xYy01LjctMS44LTEwLjktMy40LTEzLjguOS0yLjQgMy43LjcgOS40LjcgOS40IDExLjIgMS4yIDEzLjEtMTAuMyAxMy4xLTEwLjMiIGZpbGw9IiMzZTQzNDciLz48ZWxsaXBzZSBjeD0iNDMiIGN5PSIzNi4zIiByeD0iNC4yIiByeT0iNC4xIiBmaWxsPSIjZDVmZjgzIi8+PGcgZmlsbD0iIzNlNDM0NyI+PGVsbGlwc2UgY3g9IjQzIiBjeT0iMzYuMyIgcng9IjIuNyIgcnk9IjIuNyIvPjxwYXRoIGQ9Im0xMS4yIDMxLjFjNS43LTEuOCAxMC45LTMuNCAxMy43LjkgMi40IDMuNy0uNyA5LjQtLjcgOS40LTExLjEgMS4yLTEzLTEwLjMtMTMtMTAuMyIvPjwvZz48ZWxsaXBzZSBjeD0iMjEiIGN5PSIzNi4zIiByeD0iNC4yIiByeT0iNC4xIiBmaWxsPSIjZDVmZjgzIi8+PGcgZmlsbD0iIzNlNDM0NyI+PGVsbGlwc2UgY3g9IjIxIiBjeT0iMzYuMyIgcng9IjIuNyIgcnk9IjIuNyIvPjxwYXRoIGQ9Im00MS4yIDQ3LjljLS43LTIuMy0xLjgtNC40LTMtNi41IDEuMSAyLjEgMiA0LjMgMi41IDYuNi41IDIuMy43IDQuNyAwIDYuOC0uNCAxLTEgMi0xLjggMi42LS44LjYtMS44IDEtMi43IDEtLjkgMC0xLjktLjMtMi41LTEtLjYtLjctLjktMS42LS44LTIuNmwtLjkuMmgtLjljMCAxLS4yIDEuOS0uOCAyLjYtLjYuNy0xLjUgMS0yLjUgMS0uOSAwLTEuOS0uNC0yLjctMS0uOC0uNi0xLjQtMS42LTEuOC0yLjYtLjgtMi4xLS42LTQuNiAwLTYuOC41LTIuMyAxLjUtNC41IDIuNS02LjYtMS4yIDItMi4zIDQuMS0zIDYuNS0uNyAyLjMtMS4xIDQuOC0uNCA3LjMuMyAxLjIgMSAyLjQgMS45IDMuMy45LjkgMi4xIDEuNCAzLjQgMS41IDEuMi4xIDIuNi0uMiAzLjctMS4yLjMtLjIuNS0uNS43LS44LjIuMy40LjYuNy44IDEgMSAyLjQgMS4zIDMuNyAxLjIgMS4zLS4xIDIuNC0uNyAzLjQtMS41LjktLjkgMS42LTIgMS45LTMuMy41LTIuNi4xLTUuMi0uNi03LjUiLz48cGF0aCBkPSJtMzcuNiA1MC4zYy0xLjEtMS4xLTQuNS0xLjItNS42LTEuMi0xIDAtNC41LjEtNS42IDEuMi0uOC44LS4yIDIuOCAxLjkgNC41IDEuMyAxLjEgMi42IDEuNCAzLjYgMS40IDEgMCAyLjMtLjMgMy42LTEuNCAyLjMtMS43IDIuOS0zLjcgMi4xLTQuNSIvPjwvZz48L3N2Zz4=";
+
+/**
+ * Verifies that the button uses the expected icon.
+ *
+ * @param {string} selector The CSS selector used to find the button
+ *   within the DOM.
+ * @param {boolean} shouldHaveCustomStyling True if the button should
+ *   have custom styling, False otherwise.
+ * @param {string} message The message that is printed to the console
+ *   by the verifyFn.
+ */
+function verifyButtonProperties(selector, shouldHaveCustomStyling, message) {
+  try {
+    let element = document.querySelector(selector);
+    let listStyleImage = getComputedStyle(element).listStyleImage;
+    info(`listStyleImage for fox.svg is ${listStyleImage}`);
+    is(listStyleImage.includes("moz-extension:"), shouldHaveCustomStyling, message);
+  } catch (ex) {
+    ok(false, `Unable to verify ${selector}: ${ex}`);
+  }
+}
+
+/**
+ * Verifies that the button uses default styling.
+ *
+ * @param {string} selector The CSS selector used to find the button
+ *   within the DOM.
+ * @param {string} message The message that is printed to the console
+ *   by the verifyFn.
+ */
+function verifyButtonWithoutCustomStyling(selector, message) {
+  verifyButtonProperties(selector, false, message);
+}
+
+/**
+ * Verifies that the button uses non-default styling.
+ *
+ * @param {string} selector The CSS selector used to find the button
+ *   within the DOM.
+ * @param {string} message The message that is printed to the console
+ *   by the verifyFn.
+ */
+function verifyButtonWithCustomStyling(selector, message) {
+  verifyButtonProperties(selector, true, message);
+}
+
+/**
+ * Loops through all of the buttons to confirm that they are styled
+ * as expected (either with or without custom styling).
+ *
+ * @param {object} icons Array of an array that specifies which buttons should
+ *   have custom icons.
+ * @param {object} iconInfo An array of arrays that maps API names to
+ *   CSS selectors.
+ * @param {string} area The name of the area that the button resides in.
+ */
+function checkButtons(icons, iconInfo, area) {
+  for (let button of iconInfo) {
+    let iconInfo = icons.find(arr => arr[0] == button[0]);
+    if (iconInfo[1]) {
+      verifyButtonWithCustomStyling(
+        button[1],
+        `The ${button[1]} should have its icon customized in the ${area}`);
+    } else {
+      verifyButtonWithoutCustomStyling(
+        button[1],
+        `The ${button[1]} should not have its icon customized in the ${area}`);
+    }
+  }
+}
+
+async function runTestWithIcons(icons) {
+  const FRAME_COLOR = [71, 105, 91];
+  const TAB_BACKGROUND_TEXT_COLOR = [207, 221, 192, .9];
+  let manifest = {
+    "theme": {
+      "images": {
+        "theme_frame": "fox.svg",
+      },
+      "colors": {
+        "frame": FRAME_COLOR,
+        "tab_background_text": TAB_BACKGROUND_TEXT_COLOR,
+      },
+      "icons": {},
+    },
+  };
+  let files = {
+    "fox.svg": imageBufferFromDataURI(ENCODED_IMAGE_DATA),
+  };
+
+  // Each item in this array has the following setup:
+  // At position 0: The name that is used in the theme manifest.
+  // At position 1: The CSS selector for the button in the DOM.
+  // At position 2: The CustomizableUI name for the widget, only defined
+  //                if customizable.
+  const ICON_INFO = [
+    ["back", "#back-button"],
+    ["forward", "#forward-button"],
+    ["reload", "#reload-button"],
+    ["stop", "#stop-button"],
+    ["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"],
+    ["text_encoding", "#characterencoding-button", "characterencoding-button"],
+    ["email_link", "#email-link-button", "email-link-button"],
+    ["forget", "#panic-button", "panic-button"],
+  ];
+  // We add these at the beginning because adding them at the end can end up
+  // putting them in the overflow panel, where they aren't displayed the same way.
+  ICON_INFO.unshift(["bookmark_star", "#star-button"]);
+  ICON_INFO.unshift(["bookmark_menu", "#bookmarks-menu-button", "bookmarks-menu-button"]);
+
+  window.maximize();
+
+  for (let button of ICON_INFO) {
+    if (button[2]) {
+      CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_NAVBAR);
+    }
+
+    verifyButtonWithoutCustomStyling(
+      button[1],
+      `The ${button[1]} should not have its icon customized when the test starts`);
+
+    let iconInfo = icons.find(arr => arr[0] == button[0]);
+    manifest.theme.icons[button[0]] = iconInfo[1];
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({manifest, files});
+
+  await extension.startup();
+
+  checkButtons(icons, ICON_INFO, "toolbar");
+
+  await extension.unload();
+
+  for (let button of ICON_INFO) {
+    verifyButtonWithoutCustomStyling(
+      button[1],
+      `The ${button[1]} should not have its icon customized when the theme is unloaded`);
+  }
+}
+
+add_task(async function setup() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.webextensions.themes.icons.enabled", true]],
+  });
+});
+
+add_task(async function test_all_icons() {
+  let icons = [
+    ["back", "fox.svg"],
+    ["forward", "fox.svg"],
+    ["reload", "fox.svg"],
+    ["stop", "fox.svg"],
+    ["bookmark_star", "fox.svg"],
+    ["bookmark_menu", "fox.svg"],
+    ["downloads", "fox.svg"],
+    ["home", "fox.svg"],
+    ["app_menu", "fox.svg"],
+    ["cut", "fox.svg"],
+    ["copy", "fox.svg"],
+    ["paste", "fox.svg"],
+    ["new_window", "fox.svg"],
+    ["new_private_window", "fox.svg"],
+    ["save_page", "fox.svg"],
+    ["print", "fox.svg"],
+    ["history", "fox.svg"],
+    ["full_screen", "fox.svg"],
+    ["find", "fox.svg"],
+    ["options", "fox.svg"],
+    ["addons", "fox.svg"],
+    ["developer", "fox.svg"],
+    ["synced_tabs", "fox.svg"],
+    ["open_file", "fox.svg"],
+    ["sidebars", "fox.svg"],
+    ["text_encoding", "fox.svg"],
+    ["email_link", "fox.svg"],
+    ["forget", "fox.svg"],
+  ];
+  await runTestWithIcons(icons);
+});
+
+add_task(async function teardown() {
+  CustomizableUI.reset();
+  window.restore();
+});
+
+add_task(async function test_some_icons() {
+  let icons = [
+    ["back", ""],
+    ["forward", ""],
+    ["reload", "fox.svg"],
+    ["stop", ""],
+    ["bookmark_star", ""],
+    ["bookmark_menu", ""],
+    ["downloads", ""],
+    ["home", "fox.svg"],
+    ["app_menu", "fox.svg"],
+    ["cut", ""],
+    ["copy", ""],
+    ["paste", ""],
+    ["new_window", ""],
+    ["new_private_window", ""],
+    ["save_page", ""],
+    ["print", ""],
+    ["history", ""],
+    ["full_screen", ""],
+    ["find", ""],
+    ["options", ""],
+    ["addons", ""],
+    ["developer", ""],
+    ["synced_tabs", ""],
+    ["open_file", ""],
+    ["sidebars", ""],
+    ["text_encoding", ""],
+    ["email_link", ""],
+    ["forget", ""],
+  ];
+  await runTestWithIcons(icons);
+});
+
+add_task(async function teardown() {
+  CustomizableUI.reset();
+  window.restore();
+});
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5146,17 +5146,18 @@ pref("extensions.webExtensionsMinPlatfor
 pref("extensions.legacy.enabled", true);
 
 // Other webextensions prefs
 pref("extensions.webextensions.keepStorageOnUninstall", false);
 pref("extensions.webextensions.keepUuidOnUninstall", false);
 // Redirect basedomain used by identity api
 pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
 pref("extensions.webextensions.restrictedDomains", "accounts-static.cdn.mozilla.net,accounts.firefox.com,addons.cdn.mozilla.net,addons.mozilla.org,api.accounts.firefox.com,content.cdn.mozilla.net,discovery.addons.mozilla.org,install.mozilla.org,oauth.accounts.firefox.com,profile.accounts.firefox.com,support.mozilla.org,sync.services.mozilla.com");
-
+// Whether or not webextension icon theming is supported.
+pref("extensions.webextensions.themes.icons.enabled", false);
 pref("extensions.webextensions.remote", false);
 // Whether or not the moz-extension resource loads are remoted. For debugging
 // purposes only. Setting this to false will break moz-extension URI loading
 // unless other process sandboxing and extension remoting prefs are changed.
 pref("extensions.webextensions.protocol.remote", true);
 
 // Enable tab hiding API by default.
 pref("extensions.webextensions.tabhide.enabled", true);
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -8,16 +8,18 @@ var {Services} = ChromeUtils.import("res
 
 ChromeUtils.defineModuleGetter(this, "LightweightThemeManager",
                                "resource://gre/modules/LightweightThemeManager.jsm");
 
 var {
   getWinUtils,
 } = ExtensionUtils;
 
+const ICONS = Services.prefs.getStringPref("extensions.webextensions.themes.icons.buttons", "").split(",");
+
 const onUpdatedEmitter = new EventEmitter();
 
 // Represents an empty theme for convenience of use
 const emptyTheme = {
   details: {},
 };
 
 
@@ -40,22 +42,20 @@ class Theme {
     this.extension = extension;
     this.details = details;
     this.darkDetails = darkDetails;
     this.windowId = windowId;
 
     if (startupData && startupData.lwtData) {
       Object.assign(this, startupData);
     } else {
-      // TODO(ntim): clean this in bug 1550090
-      this.lwtStyles = {};
+      this.lwtStyles = {
+        icons: {},
+      };
       this.lwtDarkStyles = null;
-      if (darkDetails) {
-        this.lwtDarkStyles = {};
-      }
 
       if (experiment) {
         if (extension.experimentsAllowed) {
           this.lwtStyles.experimental = {
             colors: {},
             images: {},
             properties: {},
           };
@@ -80,16 +80,19 @@ class Theme {
    *
    * @param {Object} details Theme part of the manifest. Supported
    *   properties can be found in the schema under ThemeType.
    */
   load() {
     if (!this.lwtData) {
       this.loadDetails(this.details, this.lwtStyles);
       if (this.darkDetails) {
+        this.lwtDarkStyles = {
+          icons: {},
+        };
         this.loadDetails(this.darkDetails, this.lwtDarkStyles);
       }
 
       this.lwtData = {
         theme: this.lwtStyles,
         darkTheme: this.lwtDarkStyles,
       };
 
@@ -129,16 +132,20 @@ class Theme {
     if (details.colors) {
       this.loadColors(details.colors, styles);
     }
 
     if (details.images) {
       this.loadImages(details.images, styles);
     }
 
+    if (details.icons) {
+      this.loadIcons(details.icons, styles);
+    }
+
     if (details.properties) {
       this.loadProperties(details.properties, styles);
     }
 
     this.loadMetadata(this.extension, styles);
   }
 
   /**
@@ -266,16 +273,44 @@ class Theme {
           }
           break;
         }
       }
     }
   }
 
   /**
+   * Helper method for loading icons found in the extension's manifest.
+   *
+   * @param {Object} icons Dictionary mapping icon properties to extension URLs.
+   * @param {Object} styles Styles object in which to store the colors.
+   */
+  loadIcons(icons, styles) {
+    const {baseURI} = this.extension;
+
+    if (!Services.prefs.getBoolPref("extensions.webextensions.themes.icons.enabled")) {
+      // Return early if icons are disabled.
+      return;
+    }
+
+    for (let icon of Object.getOwnPropertyNames(icons)) {
+      let val = icons[icon];
+      // We also have to compare against the baseURI spec because
+      // `val` might have been resolved already. Resolving "" against
+      // the baseURI just produces that URI, so check for equality.
+      if (!val || val == baseURI.spec || !ICONS.includes(icon)) {
+        continue;
+      }
+      let variableName = `--${icon}-icon`;
+      let resolvedURL = baseURI.resolve(val);
+      styles.icons[variableName] = resolvedURL;
+    }
+  }
+
+  /**
    * Helper method for preparing properties found in the extension's manifest.
    * Properties are commonly used to specify more advanced behavior of colors,
    * images or icons.
    *
    * @param {Object} properties Dictionary mapping properties to values.
    * @param {Object} styles Styles object in which to store the colors.
    */
   loadProperties(properties, styles) {
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -267,16 +267,343 @@
               },
               "toolbar_field_highlight_text": {
                 "$ref": "ThemeColor",
                 "optional": true
               }
             },
             "additionalProperties": { "$ref": "ThemeColor" }
           },
+          "icons": {
+            "type": "object",
+            "optional": true,
+            "properties": {
+              "back": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "forward": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "reload": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "stop": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "bookmark_star": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "bookmark_menu": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "downloads": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "home": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "app_menu": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "cut": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "copy": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "paste": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "new_window": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "new_private_window": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "save_page": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "print": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "history": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "full_screen": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "find": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "options": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "addons": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "developer": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "synced_tabs": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "open_file": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "sidebars": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "subscribe": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "text_encoding": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "email_link": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "forget": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "pocket": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "getmsg": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "newmsg": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "address": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "reply": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "replyall": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "replylist": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "forwarding": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "delete": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "junk": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "file": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "nextUnread": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "prevUnread": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "mark": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "tag": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "compact": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "archive": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "chat": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "nextMsg": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "prevMsg": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "QFB": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "conversation": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "newcard": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "newlist": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "editcard": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "newim": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "send": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "spelling": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "attach": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "security": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "save": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "quote": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "buddy": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "join_chat": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "chat_accounts": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "calendar": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "tasks": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "synchronize": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "newevent": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "newtask": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "editevent": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "today": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "category": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "complete": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "priority": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "saveandclose": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "attendees": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "privacy": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "status": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "freebusy": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              },
+              "timezones": {
+                "$ref": "ExtensionURL",
+                "optional": true
+              }
+            },
+            "additionalProperties": { "$ref": "UnrecognizedProperty" }
+          },
           "properties": {
             "type": "object",
             "optional": true,
             "properties": {
               "additional_backgrounds_alignment": {
                 "type": "array",
                 "items": {
                   "type": "string",
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var EXPORTED_SYMBOLS = ["LightweightThemeConsumer"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const DEFAULT_THEME_ID = "default-theme@mozilla.org";
+const ICONS = Services.prefs.getStringPref("extensions.webextensions.themes.icons.buttons", "").split(",");
 
 ChromeUtils.defineModuleGetter(this, "AppConstants",
   "resource://gre/modules/AppConstants.jsm");
 // Get the theme variables from the app resource directory.
 // This allows per-app variables.
 ChromeUtils.defineModuleGetter(this, "ThemeContentPropertyList",
   "resource:///modules/ThemeVariableMap.jsm");
 ChromeUtils.defineModuleGetter(this, "ThemeVariableMap",
@@ -201,16 +202,28 @@ LightweightThemeConsumer.prototype = {
     let root = this._doc.documentElement;
 
     if (active && theme.headerURL) {
       root.setAttribute("lwtheme-image", "true");
     } else {
       root.removeAttribute("lwtheme-image");
     }
 
+    if (active && theme.icons) {
+      let activeIcons = Object.keys(theme.icons).join(" ");
+      root.setAttribute("lwthemeicons", activeIcons);
+    } else {
+      root.removeAttribute("lwthemeicons");
+    }
+
+    for (let icon of ICONS) {
+      let value = theme.icons ? theme.icons[`--${icon}-icon`] : null;
+      _setImage(root, active, `--${icon}-icon`, value);
+    }
+
     this._setExperiment(active, themeData.experiment, theme.experimental);
 
     if (theme.headerImage) {
       this._doc.mozSetImageElement("lwt-header-image", theme.headerImage);
       root.style.setProperty("--lwt-header-image", "-moz-element(#lwt-header-image)");
     } else {
       this._doc.mozSetImageElement("lwt-header-image", null);
       root.style.removeProperty("--lwt-header-image");