Bug 1418602 - Allow Theming Sidebars (WIP) draft
authorConnor Masini <cjmasini@gmail.com>
Fri, 23 Feb 2018 12:17:56 -0500
changeset 803995 740c62d906d4293c875a1976e7f2640eda4c6bf7
parent 801663 a2fd9b7493a1d028685856220f246242a1a1b316
push id112264
push userbmo:ntim.bugs@gmail.com
push dateTue, 05 Jun 2018 10:20:33 +0000
bugs1418602
milestone62.0a1
Bug 1418602 - Allow Theming Sidebars (WIP) MozReview-Commit-ID: 8OcbExWxTkI * * * Bug 1418602: Allow theming sidebars (Mac WIP) MozReview-Commit-ID: EkArCs4K8rQ
browser/components/places/content/bookmarksSidebar.js
browser/components/places/content/historySidebar.js
browser/components/syncedtabs/sidebar.js
browser/modules/ThemeVariableMap.jsm
browser/themes/osx/browser.css
browser/themes/osx/places/places.css
browser/themes/shared/places/places.inc.css
browser/themes/shared/places/tree-icons.inc.css
browser/themes/shared/sidebar.inc.css
browser/themes/windows/browser.css
browser/themes/windows/places/places.css
toolkit/components/extensions/parent/ext-theme.js
toolkit/components/extensions/schemas/theme.json
toolkit/components/extensions/test/browser/browser.ini
toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js
toolkit/modules/LightweightThemeConsumer.jsm
toolkit/themes/windows/global/tree.css
--- a/browser/components/places/content/bookmarksSidebar.js
+++ b/browser/components/places/content/bookmarksSidebar.js
@@ -1,15 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* 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/. */
 
 /* import-globals-from ../../../../toolkit/components/places/PlacesUtils.jsm */
 
+const { LightweightThemeConsumer } = ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm", {});
+new LightweightThemeConsumer(document, window.top);
+
 function init() {
   let uidensity = window.top.document.documentElement.getAttribute("uidensity");
   if (uidensity) {
     document.documentElement.setAttribute("uidensity", uidensity);
   }
 
   document.getElementById("bookmarks-view").place =
     "place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
--- a/browser/components/places/content/historySidebar.js
+++ b/browser/components/places/content/historySidebar.js
@@ -2,16 +2,19 @@
 /* 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/. */
 
 /* import-globals-from ../../../../toolkit/components/places/PlacesUtils.jsm */
 
 ChromeUtils.import("resource://gre/modules/TelemetryStopwatch.jsm");
 
+const { LightweightThemeConsumer } = ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm", {});
+new LightweightThemeConsumer(document, window.top);
+
 var gHistoryTree;
 var gSearchBox;
 var gHistoryGrouping = "";
 var gSearching = false;
 
 function HistorySidebarInit() {
   let uidensity = window.top.document.documentElement.getAttribute("uidensity");
   if (uidensity) {
--- a/browser/components/syncedtabs/sidebar.js
+++ b/browser/components/syncedtabs/sidebar.js
@@ -6,16 +6,19 @@
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://services-sync/SyncedTabs.jsm");
 ChromeUtils.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js");
 
 ChromeUtils.defineModuleGetter(this, "fxAccounts",
                                "resource://gre/modules/FxAccounts.jsm");
 
+const { LightweightThemeConsumer } = ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm", {});
+new LightweightThemeConsumer(document, window.top);
+
 var syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts});
 
 let onLoaded = () => {
   syncedTabsDeckComponent.init();
   document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container);
 };
 
 let onUnloaded = () => {
--- a/browser/modules/ThemeVariableMap.jsm
+++ b/browser/modules/ThemeVariableMap.jsm
@@ -18,16 +18,31 @@ const ThemeVariableMap = [
   }],
   ["--tab-loading-fill", {
     lwtProperty: "tab_loading",
     optionalElementID: "tabbrowser-tabs"
   }],
   ["--lwt-tab-text", {
     lwtProperty: "tab_text"
   }],
+  ["--lwt-sidebar-background", {
+    lwtProperty: "sidebar"
+  }],
+  ["--lwt-sidebar-text", {
+    lwtProperty: "sidebar_text"
+  }],
+  ["--lwt-sidebar-border", {
+    lwtProperty: "sidebar_border"
+  }],
+  ["--lwt-sidebar-highlight", {
+    lwtProperty: "sidebar_highlight"
+  }],
+  ["--lwt-sidebar-highlight-text", {
+    lwtProperty: "sidebar_highlight_text"
+  }],
   ["--tab-line-color", {
     lwtProperty: "tab_line",
     optionalElementID: "tabbrowser-tabs"
   }],
   ["--lwt-background-tab-separator-color", {
     lwtProperty: "tab_background_separator",
   }],
   ["--toolbar-bgcolor", {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -600,22 +600,22 @@ html|input.urlbar-input {
 #sidebar-box {
   -moz-appearance: -moz-mac-source-list;
   -moz-font-smoothing-background-color: -moz-mac-source-list;
   /* Default font size is 11px on mac, so this is 12px */
   font-size: 1.0909rem;
 }
 
 #sidebar-header {
-  border-bottom: 1px solid hsla(240, 5%, 5%, .1);
-  background-color: transparent;
+  border-bottom: 1px solid var(--lwt-sidebar-border, hsla(240, 5%, 5%, .1));
+  background-color: var(--lwt-sidebar-background, transparent);
 }
 
 .sidebar-splitter {
-  border-color: hsla(240, 5%, 5%, .1);
+  border-color: var(--lwt-sidebar-border, hsla(240, 5%, 5%, .1));
 }
 
 /* ----- CONTENT ----- */
 
 .browserContainer > findbar {
   background: @scopeBarBackground@;
   border-top: @scopeBarSeparatorBorder@;
   color: -moz-DialogText;
@@ -728,16 +728,18 @@ html|input.urlbar-input {
 
 @media (min-resolution: 2dppx) {
   .tab-drop-indicator {
     list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator@2x.png);
     width: 12px;
   }
 }
 
+%include ../shared/places/places.inc.css
+
 /* Bookmarks toolbar */
 #PlacesToolbarDropIndicator {
   list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
 }
 
 /* Bookmark drag and drop styles */
 
 .bookmark-item[dragover-into="true"] {
--- a/browser/themes/osx/places/places.css
+++ b/browser/themes/osx/places/places.css
@@ -21,16 +21,18 @@
 
 :root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
   min-height: 32px;
 }
 
 .sidebar-placesTree {
   -moz-appearance: -moz-mac-source-list;
   -moz-font-smoothing-background-color: -moz-mac-source-list;
+  color: var(--lwt-sidebar-text, inherit);
+  background-color: transparent;
 }
 
 .sidebar-placesTreechildren::-moz-tree-separator {
   border-top: 1px solid #505d6d;
   margin: 0 10px;
 }
 
 .sidebar-placesTreechildren::-moz-tree-row {
@@ -88,23 +90,27 @@
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-rtl");
 }
 
 .sidebar-placesTreechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed, selected) {
   list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted-rtl");
 }
 
 @media (-moz-mac-yosemite-theme) {
+  .sidebar-placesTreechildren {
+    background-color: var(--lwt-sidebar-background, transparent);
+  }
+
   .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
     color: -moz-dialogtext;
     font-weight: 500;
   }
 
   .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) {
-    color: #fff;
+    color: var(--lwt-sidebar-highlight-text, #fff);
   }
 
   .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed");
   }
 
   .sidebar-placesTreechildren::-moz-tree-twisty(closed, selected, focus) {
     list-style-image: url("chrome://global/skin/tree/arrow-disclosure.svg#arrow-disclosure-collapsed-inverted");
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/places/places.inc.css
@@ -0,0 +1,22 @@
+/* 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/. */
+
+.sidebar,
+#sidebar {
+  background-color: var(--lwt-sidebar-background, transparent);
+  color: color: var(--lwt-sidebar-text, black);
+  border-color: var(--lwt-sidebar-border, transparent);
+}
+
+.sidebar-header,
+#sidebar-header {
+  background-color: var(--lwt-sidebar-background, transparent);
+  color: var(--lwt-sidebar-text, black);
+  border-color: var(--lwt-sidebar-border, transparent);
+}
+
+:root[style*="--lwt-sidebar-highlight"] treechildren::-moz-tree-row(hover) {
+  color: var(--lwt-sidebar-highlight-text);
+  background-color: var(--lwt-sidebar-highlight);
+}
\ No newline at end of file
--- a/browser/themes/shared/places/tree-icons.inc.css
+++ b/browser/themes/shared/places/tree-icons.inc.css
@@ -1,15 +1,15 @@
 /* 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/. */
 
 treechildren:-moz-tree-image {
   -moz-context-properties: fill, fill-opacity;
-  fill: -moz-FieldText;
+  fill: currentColor;
   fill-opacity: 0.7;
 }
 
 treechildren::-moz-tree-image(title) {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   padding-inline-end: 2px;
   margin: 0 2px;
   width: 16px;
--- a/browser/themes/shared/sidebar.inc.css
+++ b/browser/themes/shared/sidebar.inc.css
@@ -1,14 +1,16 @@
 %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
 
+%include ../shared/places/places.inc.css
+
 .sidebar-header,
 #sidebar-header {
   font-size: 1.333em;
   font-weight: lighter;
   padding: 8px;
 }
 
 %ifndef MOZ_WIDGET_GTK
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -657,21 +657,21 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 %include ../shared/sidebar.inc.css
 
 #sidebar-box {
   background-color: -moz-Field;
   color: -moz-FieldText;
 }
 
 #sidebar-header {
-  border-bottom: 1px solid ThreeDLightShadow;
+  border-bottom: 1px solid var(--lwt-sidebar-border, ThreeDLightShadow);
 }
 
 .sidebar-splitter {
-  border-color: ThreeDLightShadow;
+  border-color: var(--lwt-sidebar-border, ThreeDLightShadow);;
 }
 
 .browserContainer > findbar {
   background-color: -moz-dialog;
   color: -moz-DialogText;
   text-shadow: none;
 }
 
@@ -711,16 +711,18 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 }
 
 /* All tabs menupopup */
 
 .alltabs-item[selected="true"] {
   font-weight: bold;
 }
 
+%include ../shared/places/places.inc.css
+
 /* Bookmarks toolbar */
 #PlacesToolbarDropIndicator {
   list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
 }
 
 toolbarbutton.bookmark-item[dragover="true"][open="true"] {
   -moz-appearance: none;
   background: Highlight !important;
--- a/browser/themes/windows/places/places.css
+++ b/browser/themes/windows/places/places.css
@@ -7,17 +7,17 @@
 #history-panel,
 #bookmarksPanel {
   background-color: transparent;
 }
 
 .sidebar-placesTree {
   -moz-appearance: none;
   background-color: transparent;
-  color: inherit;
+  color: var(--lwt-sidebar-text, inherit);
   border: 0;
   margin: 0;
 }
 
 :root[uidensity=touch] #search-box,
 :root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
   min-height: 32px;
 }
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -165,16 +165,21 @@ class Theme {
         case "toolbar_vertical_separator":
         case "button_background_hover":
         case "button_background_active":
         case "popup":
         case "popup_text":
         case "popup_border":
         case "popup_highlight":
         case "popup_highlight_text":
+        case "sidebar":
+        case "sidebar_text":
+        case "sidebar_border":
+        case "sidebar_highlight":
+        case "sidebar_highlight_text":
           this.lwtStyles[color] = cssColor;
           break;
       }
     }
   }
 
   /**
    * Helper method for loading images found in the extension's manifest.
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -191,16 +191,36 @@
               },
               "popup_highlight": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "popup_highlight_text": {
                 "$ref": "ThemeColor",
                 "optional": true
+              },
+              "sidebar": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_text": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_border": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_highlight": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "sidebar_highlight_text": {
+                "$ref": "ThemeColor",
+                "optional": true
               }
             },
             "additionalProperties": { "$ref": "UnrecognizedProperty" }
           },
           "icons": {
             "type": "object",
             "optional": true,
             "properties": {
--- a/toolkit/components/extensions/test/browser/browser.ini
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -21,11 +21,12 @@ support-files =
 [browser_ext_themes_toolbar_fields_focus.js]
 [browser_ext_themes_toolbar_fields.js]
 [browser_ext_themes_toolbars.js]
 [browser_ext_themes_toolbarbutton_icons.js]
 [browser_ext_themes_toolbarbutton_colors.js]
 [browser_ext_themes_theme_transition.js]
 [browser_ext_themes_arrowpanels.js]
 [browser_ext_themes_tab_selected.js]
+[browser_ext_themes_sidebars.js]
 [browser_ext_themes_autocomplete_popup.js]
 [browser_ext_themes_sanitization.js]
 [browser_ext_themes_findbar.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js
@@ -0,0 +1,62 @@
+"use strict";
+
+// This test checks applied WebExtension themes that attempt to change
+// popup properties
+
+add_task(async function test_popup_styling(browser, accDoc) {
+  const BACKGROUND_COLOR = "#FF0000";
+  const TEXT_COLOR = "#008000";
+  const BORDER_COLOR = "#0000FF";
+  const HIGHLIGHT_COLOR = "#FF00FF";
+  const HIGHLIGHT_TEXT_COLOR = "#00FF00";
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": "image1.png",
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
+          "sidebar": BACKGROUND_COLOR,
+          "sidebar_text": TEXT_COLOR,
+          "sidebar_border": BORDER_COLOR,
+          "sidebar_highlight": HIGHLIGHT_COLOR,
+          "sidebar_highlight_text": HIGHLIGHT_TEXT_COLOR,
+        },
+      },
+    },
+    files: {
+      "image1.png": BACKGROUND,
+    },
+  });
+
+
+  await BrowserTestUtils.withNewTab({gBrowser, url: "https://example.com"}, async function(browser) {
+    await extension.startup();
+    let sidebar = document.getElementById("sidebar");
+    BrowserTestUtils.waitForEvent(sidebar, "load", true);
+    SidebarUI.toggle("viewBookmarksSidebar");
+
+    let sidebarContent = document.getElementById("sidebar");
+    let sidebarComputedStyle = window.getComputedStyle(sidebarContent);
+
+    // Ensure sidebar background color was set properly
+    Assert.equal(
+      sidebarComputedStyle.getPropertyValue("background-color"),
+      `rgb(${hexToRGB(BACKGROUND_COLOR).join(", ")})`,
+      "Popup background color should have been themed"
+    );
+
+    // Ensure sidebar text color was set properly
+    Assert.equal(
+      sidebarComputedStyle.getPropertyValue("color"),
+      `rgb(${hexToRGB(TEXT_COLOR).join(", ")})`,
+      "Popup text color should have been themed"
+    );
+
+    SidebarUI.toggle("viewBookmarksSidebar");
+    await extension.unload();
+  });
+});
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -87,25 +87,38 @@ const toolkitVariableMap = [
 
 // Get the theme variables from the app resource directory.
 // This allows per-app variables.
 ChromeUtils.import("resource:///modules/ThemeVariableMap.jsm");
 
 ChromeUtils.defineModuleGetter(this, "LightweightThemeImageOptimizer",
   "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
 
-function LightweightThemeConsumer(aDocument) {
+/**
+ * Class that allows to watch theme changes to a certain window
+ * @param aDocument The document where the theme should applied
+ * @param aWindow The window that is targeted by the theme update
+ */
+function LightweightThemeConsumer(aDocument, aWindow = aDocument.defaultView) {
   this._doc = aDocument;
-  this._win = aDocument.defaultView;
+  this._win = aWindow;
+  this._doc.defaultView.gThemeConsumer = this;
 
   Services.obs.addObserver(this, "lightweight-theme-styling-update");
 
   var temp = {};
   ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
-  this._update(temp.LightweightThemeManager.currentThemeForDisplay);
+
+  if (aWindow == aDocument.defaultView) {
+    // If this is the root window, sync with LWTManager
+    this._update(temp.LightweightThemeManager.currentThemeForDisplay);
+  } else {
+    // Otherwise, sync with root window
+    this._update(aWindow.gThemeConsumer._lastData);
+  }
 
   this._win.addEventListener("resolutionchange", this);
   this._win.addEventListener("unload", this, { once: true });
 }
 
 LightweightThemeConsumer.prototype = {
   _lastData: null,
   // Whether the active lightweight theme should be shown on the window.
@@ -236,16 +249,19 @@ function _setProperties(root, active, th
       const {
         lwtProperty,
         optionalElementID,
         processColor,
         isColor = true
       } = definition;
       let elem = optionalElementID ? root.ownerDocument.getElementById(optionalElementID)
                                    : root;
+      if (!elem) {
+        continue;
+      }
 
       let val = themeData[lwtProperty];
       if (isColor) {
         val = _sanitizeCSSColor(root.ownerDocument, val);
         if (processColor) {
           val = processColor(_parseRGBA(val), elem);
         }
       }
--- a/toolkit/themes/windows/global/tree.css
+++ b/toolkit/themes/windows/global/tree.css
@@ -332,35 +332,35 @@ treechildren::-moz-tree-cell-text(active
 
 %ifdef XP_WIN
 @media (-moz-windows-default-theme) {
   treechildren {
     --treechildren-outline: none;
     --treechildren-focusColor: rgb(123,195,255);
     --treechildren-selectedFocusColor: rgb(205,232,255);
     --treechildren-currentColor: rgb(125,162,206);
-    --treechildren-hoverColor: rgb(229,243,255);
+    --treechildren-hoverColor: var(--lwt-sidebar-highlight-text, rgb(229,243,255));
     --treechildren-selectedBorder: rgb(217,217,217);
     --treechildren-selectedBackground: rgb(217,217,217);
     --treechildren-currentFocusBorder: var(--treechildren-focusColor);
     --treechildren-selectedFocusBorder: var(--treechildren-selectedFocusColor) var(--treechildren-selectedFocusColor) rgb(165,214,255);
     --treechildren-selectedFocusBackground: var(--treechildren-selectedFocusColor);
     --treechildren-selectedFocusCurrentBorder: var(--treechildren-focusColor);
     --treechildren-selectedFocusCurrentBackground: rgb(205,232,255);
     --treechildren-hoverBorder: var(--treechildren-hoverColor);
-    --treechildren-hoverBackground: rgb(229,243,255);
+    --treechildren-hoverBackground: var(--lwt-sidebar-highlight, rgb(229,243,255));
     --treechildren-hoverCurrentBorder: var(--treechildren-currentColor);
     --treechildren-hoverCurrentBackground: rgba(131,183,249,.16);
     --treechildren-hoverSelectedBorder: var(--treechildren-focusColor);
     --treechildren-hoverSelectedBackground: rgb(205,232,255);
   }
 
   treechildren::-moz-tree-row {
     height: 1.8em;
-    color: -moz-FieldText;
+    color: currentColor;
     margin-inline-start: 1px;
     margin-inline-end: 1px;
     border-width: 1px;
     border-color: transparent;
     background-repeat: no-repeat;
     background-size: 100% 100%;
   }
 
@@ -411,27 +411,27 @@ treechildren::-moz-tree-cell-text(active
 
   treechildren::-moz-tree-cell(dropOn) {
     background-image: none;
     background-color: transparent;
     border-radius: 0;
   }
 
   treechildren::-moz-tree-cell-text(primary, dropOn) {
-    color: -moz-FieldText;
+    color: currentColor;
   }
 
   treechildren::-moz-tree-cell-text {
     padding-bottom: initial;
     border-color: transparent;
     background-color: transparent;
   }
 
   treechildren::-moz-tree-cell-text(selected, focus) {
-    color: -moz-DialogText;
+    color: currentColor;
   }
 
   @media (-moz-os-version: windows-win7),
          (-moz-os-version: windows-win8) {
     treechildren {
       --treechildren-2ndBorderColor: rgba(255,255,255,.4);
       --treechildren-outline: 1px solid var(--treechildren-2ndBorderColor);
       --treechildren-selectedBackground: rgba(190,190,190,.4);