Bug 1417883 - Refactor theme code to have processing code in the variable map. r=dao draft
authorTim Nguyen <ntim.bugs@gmail.com>
Tue, 27 Mar 2018 13:04:21 +0200
changeset 773110 78671d378a790249e61a057158fdffafe94f834b
parent 772811 3bdfc99cb0bcf4f74a718d7077a8e50645f4cbf3
push id104125
push userbmo:ntim.bugs@gmail.com
push dateTue, 27 Mar 2018 11:04:51 +0000
reviewersdao
bugs1417883
milestone61.0a1
Bug 1417883 - Refactor theme code to have processing code in the variable map. r=dao MozReview-Commit-ID: 4PNGFWWosFJ
browser/modules/ThemeVariableMap.jsm
toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
toolkit/modules/LightweightThemeConsumer.jsm
--- a/browser/modules/ThemeVariableMap.jsm
+++ b/browser/modules/ThemeVariableMap.jsm
@@ -1,32 +1,106 @@
 /* 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/. */
 
 var EXPORTED_SYMBOLS = ["ThemeVariableMap"];
 
 const ThemeVariableMap = [
-  ["--lwt-accent-color-inactive", "accentcolorInactive"],
-  ["--lwt-background-alignment", "backgroundsAlignment"],
-  ["--lwt-background-tiling", "backgroundsTiling"],
-  ["--tab-loading-fill", "tab_loading", "tabbrowser-tabs"],
-  ["--lwt-tab-text", "tab_text"],
-  ["--tab-line-color", "tab_line", "tabbrowser-tabs"],
-  ["--toolbar-bgcolor", "toolbarColor"],
-  ["--toolbar-color", "toolbar_text"],
-  ["--url-and-searchbar-background-color", "toolbar_field"],
-  ["--url-and-searchbar-color", "toolbar_field_text"],
-  ["--lwt-toolbar-field-border-color", "toolbar_field_border"],
-  ["--urlbar-separator-color", "toolbar_field_separator"],
-  ["--tabs-border-color", "toolbar_top_separator", "navigator-toolbox"],
-  ["--lwt-toolbar-vertical-separator", "toolbar_vertical_separator"],
-  ["--toolbox-border-bottom-color", "toolbar_bottom_separator"],
-  ["--lwt-toolbarbutton-icon-fill", "icon_color"],
-  ["--lwt-toolbarbutton-icon-fill-attention", "icon_attention_color"],
-  ["--lwt-toolbarbutton-hover-background", "button_background_hover"],
-  ["--lwt-toolbarbutton-active-background", "button_background_active"],
-  ["--lwt-selected-tab-background-color", "tab_selected"],
-  ["--autocomplete-popup-background", "popup"],
-  ["--autocomplete-popup-color", "popup_text"],
-  ["--autocomplete-popup-highlight-background", "popup_highlight"],
-  ["--autocomplete-popup-highlight-color", "popup_highlight_text"],
+  ["--lwt-accent-color-inactive", {
+    lwtProperty: "accentcolorInactive"
+  }],
+  ["--lwt-background-alignment", {
+    isColor: false,
+    lwtProperty: "backgroundsAlignment"
+  }],
+  ["--lwt-background-tiling", {
+    isColor: false,
+    lwtProperty: "backgroundsTiling"
+  }],
+  ["--tab-loading-fill", {
+    lwtProperty: "tab_loading",
+    optionalElementID: "tabbrowser-tabs"
+  }],
+  ["--lwt-tab-text", {
+    lwtProperty: "tab_text"
+  }],
+  ["--tab-line-color", {
+    lwtProperty: "tab_line",
+    optionalElementID: "tabbrowser-tabs"
+  }],
+  ["--toolbar-bgcolor", {
+    lwtProperty: "toolbarColor"
+  }],
+  ["--toolbar-color", {
+    lwtProperty: "toolbar_text"
+  }],
+  ["--url-and-searchbar-background-color", {
+    lwtProperty: "toolbar_field"
+  }],
+  ["--url-and-searchbar-color", {
+    lwtProperty: "toolbar_field_text"
+  }],
+  ["--lwt-toolbar-field-border-color", {
+    lwtProperty: "toolbar_field_border"
+  }],
+  ["--urlbar-separator-color", {
+    lwtProperty: "toolbar_field_separator"
+  }],
+  ["--tabs-border-color", {
+    lwtProperty: "toolbar_top_separator",
+    optionalElementID: "navigator-toolbox"
+  }],
+  ["--lwt-toolbar-vertical-separator", {
+    lwtProperty: "toolbar_vertical_separator"
+  }],
+  ["--toolbox-border-bottom-color", {
+    lwtProperty: "toolbar_bottom_separator"
+  }],
+  ["--lwt-toolbarbutton-icon-fill", {
+    lwtProperty: "icon_color"
+  }],
+  ["--lwt-toolbarbutton-icon-fill-attention", {
+    lwtProperty: "icon_attention_color"
+  }],
+  ["--lwt-toolbarbutton-hover-background", {
+    lwtProperty: "button_background_hover"
+  }],
+  ["--lwt-toolbarbutton-active-background", {
+    lwtProperty: "button_background_active"
+  }],
+  ["--lwt-selected-tab-background-color", {
+    lwtProperty: "tab_selected"
+  }],
+  ["--autocomplete-popup-background", {
+    lwtProperty: "popup"
+  }],
+  ["--autocomplete-popup-color", {
+    lwtProperty: "popup_text",
+    processColor(rgbaChannels, element) {
+      const secondaryVariable = "--autocomplete-popup-secondary-color";
+
+      if (!rgbaChannels) {
+        element.removeAttribute("lwt-popup-brighttext");
+        element.style.removeProperty(secondaryVariable);
+        return null;
+      }
+
+      let {r, g, b, a} = rgbaChannels;
+      let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+
+      if (luminance <= 110) {
+        element.removeAttribute("lwt-popup-brighttext");
+      } else {
+        element.setAttribute("lwt-popup-brighttext", "true");
+      }
+
+      element.style.setProperty(secondaryVariable, `rgba(${r}, ${g}, ${b}, 0.5)`);
+      return `rgba(${r}, ${g}, ${b}, ${a})`;
+    }
+  }],
+  ["--autocomplete-popup-highlight-background", {
+    lwtProperty: "popup_highlight"
+  }],
+  ["--autocomplete-popup-highlight-color", {
+    lwtProperty: "popup_highlight_text"
+  }],
 ];
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
@@ -38,17 +38,17 @@ add_task(async function test_support_sep
       `rgb(${hexToRGB(SEPARATOR_TOP_COLOR).join(", ")})`
     ),
     "Top separator color properly set"
   );
 
   let mainWin = document.querySelector("#main-window");
   Assert.equal(
     window.getComputedStyle(mainWin).getPropertyValue("--urlbar-separator-color"),
-    SEPARATOR_FIELD_COLOR,
+    `rgb(${hexToRGB(SEPARATOR_FIELD_COLOR).join(", ")})`,
     "Toolbar field separator color properly set"
   );
 
   let panelUIButton = document.querySelector("#PanelUI-button");
   Assert.ok(
     window.getComputedStyle(panelUIButton)
           .getPropertyValue("border-image-source")
           .includes(`rgb(${hexToRGB(SEPARATOR_VERTICAL_COLOR).join(", ")})`),
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_toolbarbutton_icons.js
@@ -36,17 +36,17 @@ add_task(async function test_icons_prope
   );
 
   let starButton = document.querySelector("#star-button");
   starButton.setAttribute("starred", "true");
 
   let starComputedStyle = window.getComputedStyle(starButton);
   Assert.equal(
     starComputedStyle.getPropertyValue("--lwt-toolbarbutton-icon-fill-attention"),
-    ICONS_ATTENTION_COLOR,
+    `rgb(${hexToRGB(ICONS_ATTENTION_COLOR).join(", ")})`,
     "Variable is properly set"
   );
   Assert.equal(
     starComputedStyle.getPropertyValue("fill"),
     `rgb(${hexToRGB(ICONS_ATTENTION_COLOR).join(", ")})`,
     "Starred icon fill is properly set"
   );
 
@@ -96,9 +96,8 @@ add_task(async function test_no_icons_pr
     starComputedStyle.getPropertyValue("--lwt-toolbarbutton-icon-fill-attention"),
     "",
     "Icon attention fill should not be set when the value is not specified in the manifest."
   );
   starButton.removeAttribute("starred");
 
   await extension.unload();
 });
-
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -3,23 +3,56 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var EXPORTED_SYMBOLS = ["LightweightThemeConsumer"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
+const toolkitVariableMap = [
+  ["--lwt-accent-color", {
+    lwtProperty: "accentcolor",
+    processColor(rgbaChannels, element) {
+      if (!rgbaChannels) {
+        return "white";
+      }
+      // Remove the alpha channel
+      const {r, g, b} = rgbaChannels;
+      return `rgb(${r}, ${g}, ${b})`;
+    }
+  }],
+  ["--lwt-text-color", {
+    lwtProperty: "textcolor",
+    processColor(rgbaChannels, element) {
+      if (!rgbaChannels) {
+        element.removeAttribute("lwthemetextcolor");
+        element.removeAttribute("lwtheme");
+        return null;
+      }
+      const {r, g, b, a} = rgbaChannels;
+      const luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
+      element.setAttribute("lwthemetextcolor", luminance <= 110 ? "dark" : "bright");
+      element.setAttribute("lwtheme", "true");
+      return `rgba(${r}, ${g}, ${b}, ${a})` || "black";
+    }
+  }],
+  ["--arrowpanel-background", {
+    lwtProperty: "popup"
+  }],
+  ["--arrowpanel-color", {
+    lwtProperty: "popup_text"
+  }],
+  ["--arrowpanel-border-color", {
+    lwtProperty: "popup_border"
+  }],
+];
+
 // Get the theme variables from the app resource directory.
 // This allows per-app variables.
-const toolkitVariableMap = [
-  ["--arrowpanel-background", "popup"],
-  ["--arrowpanel-color", "popup_text"],
-  ["--arrowpanel-border-color", "popup_border"],
-];
 ChromeUtils.import("resource:///modules/ThemeVariableMap.jsm");
 
 ChromeUtils.defineModuleGetter(this, "LightweightThemeImageOptimizer",
   "resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
 
 function LightweightThemeConsumer(aDocument) {
   this._doc = aDocument;
   this._win = aDocument.defaultView;
@@ -88,47 +121,24 @@ LightweightThemeConsumer.prototype = {
     } else {
       this._lastData = aData;
       aData = LightweightThemeImageOptimizer.optimize(aData, this._win.screen);
     }
     if (!this._enabled)
       return;
 
     let root = this._doc.documentElement;
-    let active = !!aData.accentcolor;
-
-    // We need to clear these either way: either because the theme is being removed,
-    // or because we are applying a new theme and the data might be bogus CSS,
-    // so if we don't reset first, it'll keep the old value.
-    root.style.removeProperty("--lwt-text-color");
-    root.style.removeProperty("--lwt-accent-color");
-    let textcolor = aData.textcolor || "black";
-    _setProperty(root, active, "--lwt-text-color", textcolor);
-    _setProperty(root, active, "--lwt-accent-color", this._sanitizeCSSColor(aData.accentcolor) || "white");
-
-    _inferPopupColorsFromText(root, active, this._sanitizeCSSColor(aData.popup_text));
-
-    if (active) {
-      let dummy = this._doc.createElement("dummy");
-      dummy.style.color = textcolor;
-      let [r, g, b] = _parseRGB(this._win.getComputedStyle(dummy).color);
-      let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
-      root.setAttribute("lwthemetextcolor", luminance <= 110 ? "dark" : "bright");
-      root.setAttribute("lwtheme", "true");
-    } else {
-      root.removeAttribute("lwthemetextcolor");
-      root.removeAttribute("lwtheme");
-    }
 
     if (aData.headerURL) {
       root.setAttribute("lwtheme-image", "true");
     } else {
       root.removeAttribute("lwtheme-image");
     }
 
+    let active = !!aData.accentcolor;
     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);
       }
@@ -143,30 +153,16 @@ LightweightThemeConsumer.prototype = {
 
     if (active && aData.footerURL)
       root.setAttribute("lwthemefooter", "true");
     else
       root.removeAttribute("lwthemefooter");
 
     Services.obs.notifyObservers(this._win, "lightweight-theme-window-updated",
                                  JSON.stringify(aData));
-  },
-
-  _sanitizeCSSColor(cssColor) {
-    // style.color normalizes color values and rejects invalid ones, so a
-    // simple round trip gets us a sanitized color value.
-    let span = this._doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-    span.style.color = cssColor;
-    cssColor = this._win.getComputedStyle(span).color;
-    if (cssColor == "rgba(0, 0, 0, 0)" ||
-        !cssColor) {
-      return "";
-    }
-    // Remove alpha channel from color
-    return `rgb(${_parseRGB(cssColor).join(", ")})`;
   }
 };
 
 function _setImage(aRoot, aActive, aVariableName, aURLs) {
   if (aURLs && !Array.isArray(aURLs)) {
     aURLs = [aURLs];
   }
   _setProperty(aRoot, aActive, aVariableName, aURLs && aURLs.map(v => `url("${v.replace(/"/g, '\\"')}")`).join(","));
@@ -175,42 +171,60 @@ function _setImage(aRoot, aActive, aVari
 function _setProperty(elem, active, variableName, value) {
   if (active && value) {
     elem.style.setProperty(variableName, value);
   } else {
     elem.style.removeProperty(variableName);
   }
 }
 
-function _setProperties(root, active, vars) {
+function _setProperties(root, active, themeData) {
   for (let map of [toolkitVariableMap, ThemeVariableMap]) {
-    for (let [cssVarName, varsKey, optionalElementID] of map) {
+    for (let [cssVarName, definition] of map) {
+      const {
+        lwtProperty,
+        optionalElementID,
+        processColor,
+        isColor = true
+      } = definition;
       let elem = optionalElementID ? root.ownerDocument.getElementById(optionalElementID)
                                    : root;
-      _setProperty(elem, active, cssVarName, vars[varsKey]);
+
+      let val = themeData[lwtProperty];
+      if (isColor) {
+        val = _sanitizeCSSColor(root.ownerDocument, val);
+        if (processColor) {
+          val = processColor(_parseRGBA(val), elem);
+        }
+      }
+      _setProperty(elem, active, cssVarName, val);
     }
   }
 }
 
-function _parseRGB(aColorString) {
-  var rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
-  rgb.shift();
-  return rgb.map(x => parseInt(x));
+function _sanitizeCSSColor(doc, cssColor) {
+  if (!cssColor) {
+    return null;
+  }
+  // style.color normalizes color values and rejects invalid ones, so a
+  // simple round trip gets us a sanitized color value.
+  let span = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+  span.style.color = cssColor;
+  cssColor = doc.defaultView.getComputedStyle(span).color;
+  if (cssColor == "rgba(0, 0, 0, 0)") {
+    return null;
+  }
+  return cssColor;
 }
 
-function _inferPopupColorsFromText(element, active, color) {
-  if (!color) {
-    element.removeAttribute("lwt-popup-brighttext");
-    _setProperty(element, active, "--autocomplete-popup-secondary-color");
-    return;
+function _parseRGBA(aColorString) {
+  if (!aColorString) {
+    return null;
   }
-
-  let [r, g, b] = _parseRGB(color);
-  let luminance = 0.2125 * r + 0.7154 * g + 0.0721 * b;
-
-  if (luminance <= 110) {
-    element.removeAttribute("lwt-popup-brighttext");
-  } else {
-    element.setAttribute("lwt-popup-brighttext", "true");
-  }
-
-  _setProperty(element, active, "--autocomplete-popup-secondary-color", `rgba(${r}, ${g}, ${b}, 0.5)`);
+  var rgba = aColorString.replace(/(rgba?\()|(\)$)/g, "").split(",");
+  rgba = rgba.map(x => parseFloat(x));
+  return {
+    r: rgba[0],
+    g: rgba[1],
+    b: rgba[2],
+    a: rgba[3] || 1,
+  };
 }