--- a/browser/base/content/contentTheme.js
+++ b/browser/base/content/contentTheme.js
@@ -27,16 +27,53 @@ const inContentVariableMap = [
const {r, g, b, a} = rgbaChannels;
if (!_isTextColorDark(r, g, b)) {
element.setAttribute("lwt-newtab-brighttext", "true");
}
return `rgba(${r}, ${g}, ${b}, ${a})`;
},
}],
+ ["--lwt-sidebar-background-color", {
+ lwtProperty: "sidebar",
+ }],
+ ["--lwt-sidebar-text-color", {
+ lwtProperty: "sidebar_text",
+ processColor(rgbaChannels, element) {
+ if (!rgbaChannels) {
+ element.removeAttribute("lwt-sidebar");
+ element.removeAttribute("lwt-sidebar-brighttext");
+ return null;
+ }
+
+ element.setAttribute("lwt-sidebar", "true");
+ const {r, g, b, a} = rgbaChannels;
+ if (!_isTextColorDark(r, g, b)) {
+ element.setAttribute("lwt-sidebar-brighttext", "true");
+ }
+
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+ },
+ }],
+ ["--lwt-sidebar-highlight-background-color", {
+ lwtProperty: "sidebar_highlight",
+ }],
+ ["--lwt-sidebar-highlight-text-color", {
+ lwtProperty: "sidebar_highlight_text",
+ processColor(rgbaChannels, element) {
+ if (!rgbaChannels) {
+ element.removeAttribute("lwt-sidebar-highlight");
+ return null;
+ }
+ element.setAttribute("lwt-sidebar-highlight", "true");
+
+ const {r, g, b, a} = rgbaChannels;
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+ },
+ }],
];
/**
* ContentThemeController handles theme updates sent by the frame script.
* To be able to use ContentThemeController, you must add your page to the whitelist
* in LightweightThemeChildListener.jsm
*/
const ContentThemeController = {
@@ -53,17 +90,19 @@ const ContentThemeController = {
* @param {Object} event object containing the theme update.
*/
handleEvent({ type, detail }) {
if (type == "LightweightTheme:Set") {
let {data} = detail;
if (!data) {
data = {};
}
- this._setProperties(document.body, data);
+ // XUL documents don't have a body
+ const element = document.body ? document.body : document.documentElement;
+ this._setProperties(element, data);
}
},
/**
* Set a CSS variable to a given value
* @param {Element} elem The element where the CSS variable should be added.
* @param {string} variableName The CSS variable to set.
* @param {string} value The new value of the CSS variable.
@@ -85,17 +124,17 @@ const ContentThemeController = {
for (let [cssVarName, definition] of inContentVariableMap) {
const {
lwtProperty,
processColor,
} = definition;
let value = themeData[lwtProperty];
if (processColor) {
- value = processColor(value, document.body);
+ value = processColor(value, elem);
} else if (value) {
const {r, g, b, a} = value;
value = `rgba(${r}, ${g}, ${b}, ${a})`;
}
this._setProperty(elem, cssVarName, value);
}
},
--- a/browser/components/places/content/bookmarksSidebar.js
+++ b/browser/components/places/content/bookmarksSidebar.js
@@ -2,16 +2,17 @@
/* 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/. */
/* Shared Places Import - change other consumers if you change this: */
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
+ LightweightThemeChild: "resource:///actors/LightweightThemeChild.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
});
XPCOMUtils.defineLazyScriptGetter(this, "PlacesTreeView",
"chrome://browser/content/places/treeView.js");
XPCOMUtils.defineLazyScriptGetter(this, ["PlacesInsertionPoint", "PlacesController",
@@ -20,16 +21,22 @@ XPCOMUtils.defineLazyScriptGetter(this,
/* End Shared Places Import */
function init() {
let uidensity = window.top.document.documentElement.getAttribute("uidensity");
if (uidensity) {
document.documentElement.setAttribute("uidensity", uidensity);
}
+ /* Listen for sidebar theme changes */
+ new LightweightThemeChild({
+ content: window,
+ chromeOuterWindowID: window.top.windowUtils.outerWindowID,
+ });
+
document.getElementById("bookmarks-view").place =
"place:type=" + Ci.nsINavHistoryQueryOptions.RESULTS_AS_ROOTS_QUERY;
}
function searchBookmarks(aSearchString) {
var tree = document.getElementById("bookmarks-view");
if (!aSearchString) {
// eslint-disable-next-line no-self-assign
--- a/browser/components/places/content/bookmarksSidebar.xul
+++ b/browser/components/places/content/bookmarksSidebar.xul
@@ -25,16 +25,18 @@
aria-label="&bookmarksButton.label;">
<script type="application/javascript"
src="chrome://browser/content/places/bookmarksSidebar.js"/>
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/contentTheme.js"/>
#include placesCommands.inc.xul
#include ../../../../toolkit/content/editMenuCommands.inc.xul
#include placesContextMenu.inc.xul
#include bookmarksHistoryTooltip.inc.xul
<hbox id="sidebar-search-container" align="center">
<textbox id="search-box" flex="1" type="search"
--- a/browser/components/places/content/historySidebar.js
+++ b/browser/components/places/content/historySidebar.js
@@ -2,16 +2,17 @@
/* 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/. */
/* Shared Places Import - change other consumers if you change this: */
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
+ LightweightThemeChild: "resource:///actors/LightweightThemeChild.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
});
XPCOMUtils.defineLazyScriptGetter(this, "PlacesTreeView",
"chrome://browser/content/places/treeView.js");
XPCOMUtils.defineLazyScriptGetter(this, ["PlacesInsertionPoint", "PlacesController",
@@ -27,16 +28,22 @@ var gHistoryGrouping = "";
var gSearching = false;
function HistorySidebarInit() {
let uidensity = window.top.document.documentElement.getAttribute("uidensity");
if (uidensity) {
document.documentElement.setAttribute("uidensity", uidensity);
}
+ /* Listen for sidebar theme changes */
+ new LightweightThemeChild({
+ content: window,
+ chromeOuterWindowID: window.top.windowUtils.outerWindowID,
+ });
+
gHistoryTree = document.getElementById("historyTree");
gSearchBox = document.getElementById("search-box");
gHistoryGrouping = document.getElementById("viewButton").
getAttribute("selectedsort");
if (gHistoryGrouping == "site")
document.getElementById("bysite").setAttribute("checked", "true");
--- a/browser/components/places/content/historySidebar.xul
+++ b/browser/components/places/content/historySidebar.xul
@@ -25,16 +25,18 @@
aria-label="&historyButton.label;">
<script type="application/javascript"
src="chrome://browser/content/places/historySidebar.js"/>
<script type="application/javascript"
src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript"
src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/contentTheme.js"/>
#include ../../../../toolkit/content/editMenuCommands.inc.xul
#include placesCommands.inc.xul
#include ../../../../toolkit/content/editMenuKeys.inc.xul
#ifdef XP_MACOSX
<keyset id="editMenuKeysExtra">
--- a/browser/modules/ThemeVariableMap.jsm
+++ b/browser/modules/ThemeVariableMap.jsm
@@ -79,9 +79,13 @@ const ThemeVariableMap = [
["--autocomplete-popup-highlight-color", {
lwtProperty: "popup_highlight_text"
}],
];
const ThemeContentPropertyList = [
"ntp_background",
"ntp_text",
+ "sidebar",
+ "sidebar_highlight",
+ "sidebar_highlight_text",
+ "sidebar_text",
];
--- a/browser/themes/linux/places/places.css
+++ b/browser/themes/linux/places/places.css
@@ -18,23 +18,16 @@
margin: 1px 0;
margin-inline-start: 4px;
}
#viewButton:-moz-focusring:not(:hover):not([open]) {
outline: 1px dotted -moz-DialogText;
}
-.sidebar-placesTree {
- margin: 0;
- color: inherit;
- -moz-appearance: none;
- background: transparent;
-}
-
:root[uidensity=touch] #search-box,
:root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
min-height: 32px;
}
.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
.sidebar-placesTreechildren::-moz-tree-image(leaf) {
cursor: pointer;
--- a/browser/themes/osx/places/places.css
+++ b/browser/themes/osx/places/places.css
@@ -5,16 +5,19 @@
/* Sidebars */
%include ../../shared/places/places.inc.css
.sidebar-placesTree {
margin: 0;
/* Default font size is 11px on mac, so this is 12px */
font-size: 1.0909rem;
+}
+
+.sidebar-panel:not([lwt-sidebar]) .sidebar-placesTree {
-moz-appearance: -moz-mac-source-list;
-moz-font-smoothing-background-color: -moz-mac-source-list;
}
:root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
min-height: 32px;
}
--- a/browser/themes/shared/places/places.inc.css
+++ b/browser/themes/shared/places/places.inc.css
@@ -2,16 +2,61 @@
* 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-panel {
-moz-appearance: none;
background-color: transparent;
}
+/* Themed sidebars */
+
+.sidebar-panel[lwt-sidebar] {
+ background-color: var(--lwt-sidebar-background-color);
+ color: var(--lwt-sidebar-text-color);
+}
+
+.sidebar-panel[lwt-sidebar] .sidebar-placesTreechildren::-moz-tree-row(selected) {
+ background-color: hsla(0,0%,80%,.3);
+}
+
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-row(selected) {
+ -moz-appearance: none;
+ background-color: rgba(249,249,250,.1);
+}
+
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-image(selected),
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-twisty(selected),
+.sidebar-panel[lwt-sidebar-brighttext] .sidebar-placesTreechildren::-moz-tree-cell-text(selected) {
+ color: var(--lwt-sidebar-text-color);
+}
+
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-row(selected,focus) {
+ -moz-appearance: none;
+ background-color: var(--lwt-sidebar-highlight-background-color);
+}
+
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-image(selected, focus),
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-twisty(selected, focus),
+.sidebar-panel[lwt-sidebar-highlight] .sidebar-placesTreechildren::-moz-tree-cell-text(selected, focus) {
+ color: var(--lwt-sidebar-highlight-text-color);
+}
+
+/* Sidebar tree */
+
+.sidebar-placesTree {
+ -moz-appearance: none;
+ background-color: transparent;
+ color: inherit;
+ border: 0;
+ margin: 0;
+}
+
+/* View button */
+
#viewButton {
-moz-appearance: none;
border-radius: 4px;
padding: 2px 4px;
color: inherit;
}
#viewButton:hover {
--- a/browser/themes/windows/places/places.css
+++ b/browser/themes/windows/places/places.css
@@ -1,24 +1,16 @@
/* 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/. */
/* Sidebars */
%include ../../shared/places/places.inc.css
-.sidebar-placesTree {
- -moz-appearance: none;
- background-color: transparent;
- color: inherit;
- border: 0;
- margin: 0;
-}
-
:root[uidensity=touch] #search-box,
:root[uidensity=touch] .sidebar-placesTreechildren::-moz-tree-row {
min-height: 32px;
}
.sidebar-placesTreechildren::-moz-tree-cell,
.sidebar-placesTreechildren::-moz-tree-twisty {
padding: 0 4px;
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -179,16 +179,20 @@ class Theme {
case "button_background_active":
case "popup":
case "popup_text":
case "popup_border":
case "popup_highlight":
case "popup_highlight_text":
case "ntp_background":
case "ntp_text":
+ case "sidebar":
+ case "sidebar_text":
+ case "sidebar_highlight":
+ case "sidebar_highlight_text":
this.lwtStyles[color] = cssColor;
break;
default:
if (this.experiment && this.experiment.colors && color in this.experiment.colors) {
this.lwtStyles.experimental.colors[color] = cssColor;
} else {
const {logger} = this.extension;
logger.warn(`Unrecognized theme property found: colors.${color}`);
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -231,16 +231,32 @@
},
"ntp_background": {
"$ref": "ThemeColor",
"optional": true
},
"ntp_text": {
"$ref": "ThemeColor",
"optional": true
+ },
+ "sidebar": {
+ "$ref": "ThemeColor",
+ "optional": true
+ },
+ "sidebar_text": {
+ "$ref": "ThemeColor",
+ "optional": true
+ },
+ "sidebar_highlight": {
+ "$ref": "ThemeColor",
+ "optional": true
+ },
+ "sidebar_highlight_text": {
+ "$ref": "ThemeColor",
+ "optional": true
}
},
"additionalProperties": { "$ref": "ThemeColor" }
},
"icons": {
"type": "object",
"optional": true,
"properties": {
--- a/toolkit/components/extensions/test/browser/browser.ini
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -27,10 +27,11 @@ skip-if = verify
[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_autocomplete_popup.js]
[browser_ext_themes_sanitization.js]
+[browser_ext_themes_sidebars.js]
[browser_ext_themes_findbar.js]
[browser_ext_themes_warnings.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_sidebars.js
@@ -0,0 +1,133 @@
+"use strict";
+
+// This test checks whether the sidebar color properties work.
+
+/**
+ * Test whether the selected browser has the sidebar theme applied
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ */
+async function test_sidebar_theme(theme, isBrightText) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme,
+ },
+ });
+
+ const content = SidebarUI.browser.contentWindow;
+ const root = content.document.documentElement;
+
+ ok(!root.hasAttribute("lwt-sidebar"),
+ "Sidebar should not have lwt-sidebar attribute");
+ ok(!root.hasAttribute("lwt-sidebar-brighttext"),
+ "Sidebar should not have lwt-sidebar-brighttext attribute");
+ ok(!root.hasAttribute("lwt-sidebar-highlight"),
+ "Sidebar should not have lwt-sidebar-highlight attribute");
+
+ const rootCS = content.getComputedStyle(root);
+ const originalBackground = rootCS.backgroundColor;
+ const originalColor = rootCS.color;
+
+ const treeChildren = content.document.querySelector(".sidebar-placesTreechildren");
+ // ::-moz-tree-row(selected, focus) computed style can't be accessed, so we create a fake one.
+ const highlightCS = {
+ get backgroundColor() {
+ // Standardize to rgb like other computed style.
+ let color = rootCS.getPropertyValue("--lwt-sidebar-highlight-background-color");
+ let [r, g, b, a] = color.replace("rgba(", "").split(",").map(channel => parseInt(channel));
+ return `rgb(${r}, ${g}, ${b})`;
+ },
+
+ get color() {
+ let color = rootCS.getPropertyValue("--lwt-sidebar-highlight-text-color");
+ let [r, g, b, a] = color.replace("rgba(", "").split(",").map(channel => parseInt(channel));
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+ };
+ const originalHighlightBackground = highlightCS.backgroundColor;
+ const originalHighlightColor = highlightCS.color;
+
+ await extension.startup();
+
+ Services.ppmm.sharedData.flush();
+
+ const actualBackground = hexToCSS(theme.colors.sidebar) || originalBackground;
+ const actualColor = hexToCSS(theme.colors.sidebar_text) || originalColor;
+ const actualHighlightBackground = hexToCSS(theme.colors.sidebar_highlight) || originalHighlightBackground;
+ const actualHighlightColor = hexToCSS(theme.colors.sidebar_highlight_text) || originalHighlightColor;
+ const isCustomHighlight = !!theme.colors.sidebar_highlight_text;
+ const isCustomSidebar = !!theme.colors.sidebar_text;
+
+ is(root.hasAttribute("lwt-sidebar"), isCustomSidebar,
+ `Sidebar should${!isCustomSidebar ? " not" : ""} have lwt-sidebar attribute`);
+ is(root.hasAttribute("lwt-sidebar-brighttext"), isBrightText,
+ `Sidebar should${!isBrightText ? " not" : ""} have lwt-sidebar-brighttext attribute`);
+ is(root.hasAttribute("lwt-sidebar-highlight"), isCustomHighlight,
+ `Sidebar should${!isCustomHighlight ? " not" : ""} have lwt-sidebar-highlight attribute`);
+
+ is(rootCS.backgroundColor, actualBackground, "Sidebar background should be set.");
+ is(rootCS.color, actualColor, "Sidebar text color should be set.");
+
+ is(highlightCS.backgroundColor, actualHighlightBackground,
+ "Sidebar highlight background color should be set.");
+ is(highlightCS.color, actualHighlightColor,
+ "Sidebar highlight text color should be set.");
+
+ await extension.unload();
+
+ Services.ppmm.sharedData.flush();
+
+ ok(!root.hasAttribute("lwt-sidebar"),
+ "Sidebar should not have lwt-sidebar attribute");
+ ok(!root.hasAttribute("lwt-sidebar-brighttext"),
+ "Sidebar should not have lwt-sidebar-brighttext attribute");
+ ok(!root.hasAttribute("lwt-sidebar-highlight"),
+ "Sidebar should not have lwt-sidebar-highlight attribute");
+
+ is(rootCS.backgroundColor, originalBackground,
+ "Sidebar background should be reset.");
+ is(rootCS.color, originalColor,
+ "Sidebar text color should be reset.");
+ is(highlightCS.backgroundColor, originalHighlightBackground,
+ "Sidebar highlight background color should be reset.");
+ is(highlightCS.color, originalHighlightColor,
+ "Sidebar highlight text color should be reset.");
+}
+
+add_task(async function test_support_sidebar_colors() {
+ for (let command of ["viewBookmarksSidebar", "viewHistorySidebar"]) {
+ info("Executing command: " + command);
+
+ await SidebarUI.show(command);
+
+ await test_sidebar_theme({
+ colors: {
+ sidebar: "#fafad2", // lightgoldenrodyellow
+ sidebar_text: "#2f4f4f", // darkslategrey
+ },
+ }, false);
+
+ await test_sidebar_theme({
+ colors: {
+ sidebar: "#8b4513", // saddlebrown
+ sidebar_text: "#ffa07a" // lightsalmon
+ },
+ }, true);
+
+ await test_sidebar_theme({
+ colors: {
+ sidebar: "#fffafa", // snow
+ sidebar_text: "#663399", // rebeccapurple
+ sidebar_highlight: "#7cfc00", // lawngreen
+ sidebar_highlight_text: "#ffefd5", // papayawhip
+ },
+ }, false);
+
+ await test_sidebar_theme({
+ colors: {
+ sidebar_highlight: "#a0522d", // sienna
+ sidebar_highlight_text: "#fff5ee", // seashell
+ },
+ }, false);
+ }
+});
--- a/toolkit/components/extensions/test/browser/head.js
+++ b/toolkit/components/extensions/test/browser/head.js
@@ -33,25 +33,31 @@ const ENCODED_IMAGE_DATA = "iVBORw0KGgoA
"eG+mgfrcLHh3C79bx6wttGEqERiH/AjPohWMouv2ZAAAAAElFTkSuQmCC";
const ACCENT_COLOR = "#a14040";
const TEXT_COLOR = "#fac96e";
// For testing aliases of the colors above:
const FRAME_COLOR = [71, 105, 91];
const TAB_BACKGROUND_TEXT_COLOR = [207, 221, 192, .9];
function hexToRGB(hex) {
+ if (!hex) {
+ return null;
+ }
hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
return [hex >> 16, (hex & 0x00FF00) >> 8, (hex & 0x0000FF)];
}
function rgbToCSS(rgb) {
return `rgb(${rgb.join(", ")})`;
}
function hexToCSS(hex) {
+ if (!hex) {
+ return null;
+ }
return rgbToCSS(hexToRGB(hex));
}
function imageBufferFromDataURI(encodedImageData) {
let decodedImageData = atob(encodedImageData);
return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
}