Bug 1343921 - Implement support for custom icons through the Theming API. r=mattw,mikedeboer
☠☠ backed out by 25f5d0648983 ☠ ☠
authorJared Wein <jwein@mozilla.com>
Mon, 06 Mar 2017 15:16:35 -0500
changeset 346109 1666512ef0813b231b82a713075e09b614143f6f
parent 346108 55243aaba0c89568b07a85c34fe5fab5d5a1b0b5
child 346110 e33e742b3302c74d4149bf7830ca392ff5fd9c24
push id31459
push usercbook@mozilla.com
push dateTue, 07 Mar 2017 14:05:14 +0000
treeherdermozilla-central@1fb56ba248d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattw, mikedeboer
bugs1343921
milestone54.0a1
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
Bug 1343921 - Implement support for custom icons through the Theming API. r=mattw,mikedeboer MozReview-Commit-ID: KMMR7cT8Mzp
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_contextMenus_icons.js
browser/components/extensions/test/browser/browser_ext_themes_icons.js
browser/components/extensions/test/browser/head.js
modules/libpref/init/all.js
toolkit/components/extensions/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
@@ -164,16 +164,18 @@ pref("extensions.update.interval", 86400
 // Non-symmetric (not shared by extensions) extension-specific [update] preferences
 pref("extensions.dss.enabled", false);          // Dynamic Skin Switching
 pref("extensions.dss.switchPending", false);    // Non-dynamic switch pending after next
                                                 // restart.
 
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name", "chrome://browser/locale/browser.properties");
 pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties");
 
+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.update.enabled", true);
 pref("lightweightThemes.getMoreURL", "https://addons.mozilla.org/%LOCALE%/firefox/themes");
 pref("lightweightThemes.recommendedThemes", "[{\"id\":\"recommended-1\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/a-web-browser-renaissance/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.footer.jpg\",\"textcolor\":\"#000000\",\"accentcolor\":\"#f2d9b1\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/1.preview.jpg\",\"author\":\"Sean.Martell\",\"version\":\"0\"},{\"id\":\"recommended-2\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/space-fantasy/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.header.jpg\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.footer.jpg\",\"textcolor\":\"#ffffff\",\"accentcolor\":\"#d9d9d9\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.icon.jpg\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/2.preview.jpg\",\"author\":\"fx5800p\",\"version\":\"1.0\"},{\"id\":\"recommended-4\",\"homepageURL\":\"https://addons.mozilla.org/firefox/addon/pastel-gradient/\",\"headerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.header.png\",\"footerURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.footer.png\",\"textcolor\":\"#000000\",\"accentcolor\":\"#000000\",\"iconURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.icon.png\",\"previewURL\":\"resource:///chrome/browser/content/browser/defaultthemes/4.preview.png\",\"author\":\"darrinhenein\",\"version\":\"1.0\"}]");
 
 #if defined(MOZ_WIDEVINE_EME)
 pref("browser.eme.ui.enabled", true);
 #else
 pref("browser.eme.ui.enabled", false);
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1259,8 +1259,10 @@ toolbarpaletteitem[place="palette"][hidd
   box-shadow: 0 0 2px 2px rgba(255,0,0,0.4);
 }
 
 .dragfeedback-tab {
   -moz-appearance: none;
   opacity: 0.65;
   -moz-window-shadow: none;
 }
+
+%include theme-vars.inc.css
new file mode 100644
--- /dev/null
+++ b/browser/base/content/theme-vars.inc.css
@@ -0,0 +1,166 @@
+%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"] #urlbar-reload-button:-moz-lwtheme {
+  list-style-image: var(--reload-icon) !important;
+}
+
+:root[lwthemeicons~="--stop-icon"] #urlbar-stop-button:-moz-lwtheme {
+  list-style-image: var(--stop-icon) !important;
+}
+
+: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[cui-areatype='toolbar'] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-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~="--share_page-icon"] #social-share-button:-moz-lwtheme {
+  list-style-image: var(--share_page-icon) !important;
+}
+
+:root[lwthemeicons~="--subscribe-icon"] #feed-button:-moz-lwtheme {
+  list-style-image: var(--subscribe-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~="--pocket-icon"] #pocket-button:-moz-lwtheme {
+  list-style-image: var(--pocket-icon) !important;
+}
+
+:root[lwthemeicons~="--back-icon"] #back-button:-moz-lwtheme,
+:root[lwthemeicons~="--forward-icon"] #forward-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_star-icon"] #bookmarks-menu-button:-moz-lwtheme,
+:root[lwthemeicons~="--bookmark_menu-icon"] #bookmarks-menu-button[cui-areatype='toolbar'] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-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~="--share_page-icon"] #social-share-button:-moz-lwtheme,
+:root[lwthemeicons~="--subscribe-icon"] #feed-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,
+:root[lwthemeicons~="--pocket-icon"] #pocket-button:-moz-lwtheme {
+  -moz-image-region: rect(0, 18px, 18px, 0) !important;
+}
+
+:root[lwthemeicons~="--reload-icon"] #urlbar-reload-button:-moz-lwtheme,
+:root[lwthemeicons~="--stop-icon"] #urlbar-stop-button:-moz-lwtheme {
+  -moz-image-region: rect(0, 14px, 14px, 0) !important;
+}
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -111,16 +111,17 @@ support-files =
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
+[browser_ext_themes_icons.js]
 [browser_ext_topwindowid.js]
 [browser_ext_url_overrides_all.js]
 [browser_ext_url_overrides_home.js]
 [browser_ext_url_overrides_newtab.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_onCreatedNavigationTarget.js]
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -2,18 +2,17 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* () {
   let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
     "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
 
   let encodedImageData = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
-  let decodedImageData = atob(encodedImageData);
-  const IMAGE_ARRAYBUFFER = Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+  const IMAGE_ARRAYBUFFER = imageBufferFromDataURI(encodedImageData);
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["contextMenus"],
       "icons": {
         "18": "extension.png",
       },
     },
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_themes_icons.js
@@ -0,0 +1,275 @@
+"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;
+    // This selector is different than the others because it's the only
+    // toolbarbutton that we ship by default that has type="menu-button",
+    // which don't place a unique ID on the associated dropmarker-icon.
+    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 listStyleImage = getComputedStyle(element).listStyleImage;
+    info(`listStyleImage for fox.svg is ${listStyleImage}`);
+    is(listStyleImage.includes("fox.svg"), 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 it's icon customized in the ${area}`);
+    } else {
+      verifyButtonWithoutCustomStyling(button[1],
+        `The ${button[1]} should not have it's icon customized in the ${area}`);
+    }
+  }
+}
+
+function* runTestWithIcons(icons) {
+  const FRAME_COLOR = [71, 105, 91];
+  const TAB_TEXT_COLOR = [207, 221, 192, .9];
+  let manifest = {
+    "theme": {
+      "images": {
+        "theme_frame": "fox.svg",
+      },
+      "colors": {
+        "frame": FRAME_COLOR,
+        "tab_text": TAB_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", "#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();
+
+  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 it's 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});
+
+  yield extension.startup();
+
+  checkButtons(icons, ICON_INFO, "toolbar");
+
+  for (let button of ICON_INFO) {
+    if (button[2]) {
+      CustomizableUI.addWidgetToArea(button[2], CustomizableUI.AREA_PANEL);
+    }
+  }
+
+  yield PanelUI.show();
+
+  checkButtons(icons, ICON_INFO, "panel");
+
+  yield PanelUI.hide();
+
+  yield extension.unload();
+
+  for (let button of ICON_INFO) {
+    verifyButtonWithoutCustomStyling(button[1],
+      `The ${button[1]} should not have it's icon customized when the theme is unloaded`);
+  }
+}
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [["extensions.webextensions.themes.enabled", true],
+          ["extensions.webextensions.themes.icons.enabled", true]],
+  });
+});
+
+add_task(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"],
+    ["share_page", "fox.svg"],
+    ["subscribe", "fox.svg"],
+    ["text_encoding", "fox.svg"],
+    ["email_link", "fox.svg"],
+    ["forget", "fox.svg"],
+    ["pocket", "fox.svg"],
+  ];
+  yield runTestWithIcons(icons);
+});
+
+add_task(function* teardown() {
+  CustomizableUI.reset();
+  window.restore();
+});
+
+add_task(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", ""],
+    ["share_page", ""],
+    ["subscribe", ""],
+    ["text_encoding", ""],
+    ["email_link", ""],
+    ["forget", ""],
+    ["pocket", "fox.svg"],
+  ];
+  yield runTestWithIcons(icons);
+});
+
+add_task(function* teardown() {
+  CustomizableUI.reset();
+  window.restore();
+});
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -7,17 +7,18 @@
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openExtensionContextMenu closeExtensionContextMenu
  *          openActionContextMenu openSubmenu closeActionContextMenu
  *          openTabContextMenu closeTabContextMenu
- *          imageBuffer getListStyleImage getPanelForNode
+ *          imageBuffer imageBufferFromDataURI
+ *          getListStyleImage getPanelForNode
  *          awaitExtensionPanel awaitPopupResize
  *          promiseContentDimensions alterContent
  */
 
 const {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm", {});
 const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
 // We run tests under two different configurations, from browser.ini and
@@ -59,18 +60,23 @@ var focusWindow = Task.async(function* f
       resolve();
     }, {capture: true, once: true});
   });
 
   win.focus();
   yield promise;
 });
 
+function imageBufferFromDataURI(encodedImageData) {
+  let decodedImageData = atob(encodedImageData);
+  return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
+}
+
 let img = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==";
-var imageBuffer = Uint8Array.from(atob(img), byte => byte.charCodeAt(0)).buffer;
+var imageBuffer = imageBufferFromDataURI(img);
 
 function getListStyleImage(button) {
   let style = button.ownerGlobal.getComputedStyle(button);
 
   let match = /^url\("(.*)"\)$/.exec(style.listStyleImage);
 
   return match && match[1];
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4770,16 +4770,17 @@ pref("extensions.webExtensionsMinPlatfor
 
 // 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");
 // Whether or not webextension themes are supported.
 pref("extensions.webextensions.themes.enabled", false);
+pref("extensions.webextensions.themes.icons.enabled", false);
 pref("extensions.webextensions.remote", false);
 
 // Report Site Issue button
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
 #ifdef NIGHTLY_BUILD
 pref("extensions.webcompat-reporter.enabled", true);
 #else
 pref("extensions.webcompat-reporter.enabled", false);
--- a/toolkit/components/extensions/ext-theme.js
+++ b/toolkit/components/extensions/ext-theme.js
@@ -3,27 +3,31 @@
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 
 // WeakMap[Extension -> Theme]
 let themeMap = new WeakMap();
 
+const ICONS = Preferences.get("extensions.webextensions.themes.icons.buttons", "").split(",");
+
 /** Class representing a theme. */
 class Theme {
   /**
    * Creates a theme instance.
    *
    * @param {string} baseURI The base URI of the extension, used to
    *   resolve relative filepaths.
    */
   constructor(baseURI) {
     // A dictionary of light weight theme styles.
-    this.lwtStyles = {};
+    this.lwtStyles = {
+      icons: {},
+    };
     this.baseURI = baseURI;
   }
 
   /**
    * Loads a theme by reading the properties from the extension's manifest.
    * This method will override any currently applied theme.
    *
    * @param {Object} details Theme part of the manifest. Supported
@@ -33,16 +37,20 @@ class Theme {
     if (details.colors) {
       this.loadColors(details.colors);
     }
 
     if (details.images) {
       this.loadImages(details.images);
     }
 
+    if (details.icons) {
+      this.loadIcons(details.icons);
+    }
+
     // Lightweight themes require all properties to be defined.
     if (this.lwtStyles.headerURL &&
         this.lwtStyles.accentcolor &&
         this.lwtStyles.textcolor) {
       Services.obs.notifyObservers(null,
         "lightweight-theme-styling-update",
         JSON.stringify(this.lwtStyles));
     }
@@ -98,23 +106,50 @@ class Theme {
           let resolvedURL = this.baseURI.resolve(val);
           this.lwtStyles.headerURL = resolvedURL;
           break;
         }
       }
     }
   }
 
+  loadIcons(icons) {
+    if (!Preferences.get("extensions.webextensions.themes.icons.enabled")) {
+      // Return early if icons are disabled.
+      return;
+    }
+
+    for (let icon of Object.getOwnPropertyNames(icons)) {
+      let val = icons[icon];
+      if (!val || !ICONS.includes(icon)) {
+        continue;
+      }
+      let variableName = `--${icon}-icon`;
+      let resolvedURL = this.baseURI.resolve(val);
+      this.lwtStyles.icons[variableName] = resolvedURL;
+    }
+  }
+
   /**
    * Unloads the currently applied theme.
    */
   unload() {
+    let lwtStyles = {
+      headerURL: "",
+      accentcolor: "",
+      textcolor: "",
+      icons: {},
+    };
+
+    for (let icon of ICONS) {
+      lwtStyles.icons[`--${icon}--icon`] = "";
+    }
     Services.obs.notifyObservers(null,
       "lightweight-theme-styling-update",
-      null);
+      JSON.stringify(lwtStyles));
   }
 }
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("manifest_theme", (type, directive, extension, manifest) => {
   if (!Preferences.get("extensions.webextensions.themes.enabled")) {
     // Return early if themes are disabled.
     return;
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -46,16 +46,146 @@
                 },
                 "optional": true
               },
               "textcolor": {
                 "type": "string",
                 "optional": true
               }
             }
+          },
+          "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
+              },
+              "share_page": {
+                "$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
+              }
+            }
           }
         }
       },
       {
         "$extend": "WebExtensionManifest",
         "properties": {
           "theme": {
             "optional": true,
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -117,18 +117,29 @@ LightweightThemeConsumer.prototype = {
       root.setAttribute("lwtheme", "true");
     } else {
       root.removeAttribute("lwthemetextcolor");
       root.removeAttribute("lwtheme");
     }
 
     this._active = active;
 
+    if (aData.icons) {
+      let activeIcons = active ? Object.keys(aData.icons).join(" ") : "";
+      root.setAttribute("lwthemeicons", activeIcons);
+      for (let [name, value] of Object.entries(aData.icons)) {
+        _setImage(root, active, value, name);
+      }
+    } else {
+      root.removeAttribute("lwthemeicons");
+    }
+
     _setImage(root, active, "--lwt-header-image", aData.headerURL);
-    _setImage(root, active, "--lwt-footer-image", aData.footerURL);
+    _setImage(root, active, "--lwt-header-image", aData.footerURL);
+
     if (active && aData.footerURL)
       root.setAttribute("lwthemefooter", "true");
     else
       root.removeAttribute("lwthemefooter");
 
     // On OS X, we extend the lightweight theme into the titlebar, which means setting
     // the chromemargin attribute. Some XUL applications already draw in the titlebar,
     // so we need to save the chromemargin value before we overwrite it with the value