--- 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,
+ };
}