diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index eccf12ada2e8..aa6b7864c15c 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -169,6 +169,8 @@ pref("extensions.dss.switchPending", false); // Non-dynamic switch pending af 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\"}]"); diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index d67a1a8cd748..424788d58dfc 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -1264,3 +1264,5 @@ toolbarpaletteitem[place="palette"][hidden] { opacity: 0.65; -moz-window-shadow: none; } + +%include theme-vars.inc.css diff --git a/browser/base/content/theme-vars.inc.css b/browser/base/content/theme-vars.inc.css new file mode 100644 index 000000000000..6acd106cfccf --- /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; +} diff --git a/browser/components/extensions/test/browser/browser-common.ini b/browser/components/extensions/test/browser/browser-common.ini index bb29e1f6eb64..1a334cb7a42d 100644 --- a/browser/components/extensions/test/browser/browser-common.ini +++ b/browser/components/extensions/test/browser/browser-common.ini @@ -116,6 +116,7 @@ support-files = [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_newtab.js] [browser_ext_webRequest.js] diff --git a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js index a3d31bd19750..b66415332d5a 100644 --- a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js +++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js @@ -7,8 +7,7 @@ add_task(function* () { "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: { diff --git a/browser/components/extensions/test/browser/browser_ext_themes_icons.js b/browser/components/extensions/test/browser/browser_ext_themes_icons.js new file mode 100644 index 000000000000..73d537938030 --- /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(); +}); diff --git a/browser/components/extensions/test/browser/head.js b/browser/components/extensions/test/browser/head.js index d8674fc5bd67..3bd8f8b42f3d 100644 --- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -12,7 +12,8 @@ * openExtensionContextMenu closeExtensionContextMenu * openActionContextMenu openSubmenu closeActionContextMenu * openTabContextMenu closeTabContextMenu - * imageBuffer getListStyleImage getPanelForNode + * imageBuffer imageBufferFromDataURI + * getListStyleImage getPanelForNode * awaitExtensionPanel awaitPopupResize * promiseContentDimensions alterContent */ @@ -64,8 +65,13 @@ var focusWindow = Task.async(function* focusWindow(win) { 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); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 12bd78e27e03..ae2610d7c4c3 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4775,6 +4775,7 @@ pref("extensions.webextensions.keepUuidOnUninstall", false); 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 diff --git a/toolkit/components/extensions/ext-theme.js b/toolkit/components/extensions/ext-theme.js index c2ff4b9a63c3..28380e73d6e3 100644 --- a/toolkit/components/extensions/ext-theme.js +++ b/toolkit/components/extensions/ext-theme.js @@ -8,6 +8,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Preferences", // WeakMap[Extension -> Theme] let themeMap = new WeakMap(); +const ICONS = Preferences.get("extensions.webextensions.themes.icons.buttons", "").split(","); + /** Class representing a theme. */ class Theme { /** @@ -18,7 +20,9 @@ class Theme { */ constructor(baseURI) { // A dictionary of light weight theme styles. - this.lwtStyles = {}; + this.lwtStyles = { + icons: {}, + }; this.baseURI = baseURI; } @@ -38,6 +42,10 @@ class Theme { 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 && @@ -103,13 +111,40 @@ class Theme { } } + 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)); } } diff --git a/toolkit/components/extensions/schemas/theme.json b/toolkit/components/extensions/schemas/theme.json index 4755d8cd2511..1e7fd968cba4 100644 --- a/toolkit/components/extensions/schemas/theme.json +++ b/toolkit/components/extensions/schemas/theme.json @@ -51,6 +51,136 @@ "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 + } + } } } }, diff --git a/toolkit/modules/LightweightThemeConsumer.jsm b/toolkit/modules/LightweightThemeConsumer.jsm index 1d91dbdf93a3..4c74ea631e3b 100644 --- a/toolkit/modules/LightweightThemeConsumer.jsm +++ b/toolkit/modules/LightweightThemeConsumer.jsm @@ -122,8 +122,19 @@ LightweightThemeConsumer.prototype = { 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