Bug 1343921 - Implement support for custom icons through the Theming API. r?mikedeboer,mattw draft
authorJared Wein <jwein@mozilla.com>
Mon, 06 Mar 2017 18:10:39 -0500
changeset 494261 ad48eeb22f3903bfab768e6622b51987d179dea4
parent 494260 6583496f169cd8a13c531ed16e98e8bf313eda8e
child 495022 930c54ae908c93df674f1305ede17fda51513f50
push id47998
push userjwein@mozilla.com
push dateMon, 06 Mar 2017 23:15:15 +0000
reviewersmikedeboer, mattw
bugs1343921
milestone54.0a1
Bug 1343921 - Implement support for custom icons through the Theming API. r?mikedeboer,mattw 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
@@ -4767,16 +4767,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, name, value);
+      }
+    } else {
+      root.removeAttribute("lwthemeicons");
+    }
+
     _setImage(root, active, "--lwt-header-image", aData.headerURL);
     _setImage(root, active, "--lwt-footer-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