Bug 1417883 - Allow theming autocomplete popups. r=ntim, jaws
authorDylan Stokes <stokesdy@msu.edu>
Sun, 11 Feb 2018 12:32:33 -0500
changeset 410229 2151edc10e9eb1a7bce5515bf9a7cbcae3ba95e2
parent 410228 c4cb5453c3ed8a640392696cc67a5d3d3065990a
child 410230 a1fae6d6cca4301f9c70e7d5a544a2007628c4d4
push id33723
push userebalazs@mozilla.com
push dateTue, 27 Mar 2018 21:48:41 +0000
treeherdermozilla-central@56d6db4ad38c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersntim, jaws
bugs1417883
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1417883 - Allow theming autocomplete popups. r=ntim, jaws
browser/modules/ThemeVariableMap.jsm
browser/themes/linux/browser.css
browser/themes/shared/searchbar.inc.css
browser/themes/shared/urlbar-autocomplete.inc.css
browser/themes/windows/browser.css
toolkit/components/extensions/ext-theme.js
toolkit/components/extensions/schemas/theme.json
toolkit/components/extensions/test/browser/browser.ini
toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
toolkit/modules/LightweightThemeConsumer.jsm
--- a/browser/modules/ThemeVariableMap.jsm
+++ b/browser/modules/ThemeVariableMap.jsm
@@ -20,12 +20,16 @@ const ThemeVariableMap = [
   ["--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-toolbar-field-focus", "toolbar_field_focus"],
   ["--lwt-toolbar-field-focus-color", "toolbar_field_text_focus"],
   ["--lwt-toolbar-field-focus-border-color", "toolbar_field_border_focus"],
 ];
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -40,16 +40,18 @@
   --tab-line-color: highlight;
 }
 
 :root:-moz-lwtheme {
   --toolbar-bgcolor: rgba(255,255,255,.4);
   --toolbar-bgimage: none;
 
   --toolbox-border-bottom-color: rgba(0,0,0,.3);
+
+  --panel-separator-color: hsla(210,4%,10%,.14);
 }
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
--- a/browser/themes/shared/searchbar.inc.css
+++ b/browser/themes/shared/searchbar.inc.css
@@ -44,17 +44,17 @@
 }
 
 .search-panel-header {
   font-weight: normal;
   background-color: var(--arrowpanel-dimmed);
   border-top: 1px solid var(--panel-separator-color);
   margin: 0;
   padding: 3px 6px;
-  color: GrayText;
+  color: var(--autocomplete-popup-secondary-color);
 }
 
 .search-panel-header > label {
   margin-top: 2px !important;
   margin-bottom: 1px !important;
 }
 
 .search-panel-current-input > label {
@@ -79,17 +79,17 @@
   min-width: 48px;
   height: 32px;
   margin: 0;
   padding: 0;
   background: linear-gradient(transparent 15%, var(--panel-separator-color) 15%, var(--panel-separator-color) 85%, transparent 85%);
   background-size: 1px auto;
   background-repeat: no-repeat;
   background-position: right center;
-  color: GrayText;
+  color: var(--autocomplete-popup-secondary-color);
 }
 
 .searchbar-engine-one-off-item:-moz-locale-dir(rtl) {
   background-position-x: left;
 }
 
 .searchbar-engine-one-off-item:not(.last-row) {
   box-sizing: content-box;
--- a/browser/themes/shared/urlbar-autocomplete.inc.css
+++ b/browser/themes/shared/urlbar-autocomplete.inc.css
@@ -4,16 +4,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 
 :root {
   --autocomplete-popup-background: -moz-field;
   --autocomplete-popup-color: -moz-fieldtext;
   --autocomplete-popup-highlight-background: Highlight;
   --autocomplete-popup-highlight-color: HighlightText;
+  --autocomplete-popup-secondary-color: GrayText;
+}
+
+:root:-moz-lwtheme {
+  --autocomplete-popup-background: #fff;
+  --autocomplete-popup-color: #0c0c0d;
+  --autocomplete-popup-secondary-color: #737373;
+  --urlbar-popup-url-color: hsl(210, 77%, 47%);
+  --urlbar-popup-action-color: hsl(178, 100%, 28%);
+}
+
+:root[lwt-popup-brighttext] {
+  --urlbar-popup-url-color: #0a84ff;
+  --urlbar-popup-action-color: #30e60b;
 }
 
 #PopupAutoCompleteRichResult,
 #PopupSearchAutoComplete {
   background: var(--autocomplete-popup-background);
   color: var(--autocomplete-popup-color);
 }
 
@@ -43,17 +57,17 @@
 
 :root[uidensity=touch] #PopupAutoCompleteRichResult .autocomplete-richlistitem {
   min-height: 40px;
 }
 
 /* Awesomebar popup items */
 
 .ac-separator:not([selected=true]) {
-  color: GrayText;
+  color: var(--autocomplete-popup-secondary-color);
 }
 
 .ac-url:not([selected=true]) {
   color: var(--urlbar-popup-url-color);
 }
 
 .ac-action:not([selected=true]) {
   color: var(--urlbar-popup-action-color);
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -62,16 +62,18 @@
   }
 }
 
 :root:-moz-lwtheme {
   --toolbar-bgcolor: rgba(255,255,255,.4);
   --toolbar-bgimage: none;
 
   --toolbox-border-bottom-color: rgba(0,0,0,.3);
+
+  --panel-separator-color: hsla(210,4%,10%,.14);
 }
 
 #navigator-toolbox:-moz-lwtheme {
   --tabs-border-color: rgba(0,0,0,.3);
 }
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
--- a/toolkit/components/extensions/ext-theme.js
+++ b/toolkit/components/extensions/ext-theme.js
@@ -151,27 +151,29 @@ class Theme {
         case "tab_loading":
         case "tab_text":
         case "tab_line":
         case "tab_selected":
         case "toolbar_field":
         case "toolbar_field_text":
         case "toolbar_field_border":
         case "toolbar_field_separator":
+        case "toolbar_field_focus":
+        case "toolbar_field_text_focus":
+        case "toolbar_field_border_focus":
         case "toolbar_top_separator":
         case "toolbar_bottom_separator":
         case "toolbar_vertical_separator":
         case "button_background_hover":
         case "button_background_active":
         case "popup":
         case "popup_text":
         case "popup_border":
-        case "toolbar_field_focus":
-        case "toolbar_field_text_focus":
-        case "toolbar_field_border_focus":
+        case "popup_highlight":
+        case "popup_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
@@ -179,16 +179,24 @@
               },
               "toolbar_field_text_focus": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "toolbar_field_border_focus": {
                 "$ref": "ThemeColor",
                 "optional": true
+              },
+              "popup_highlight": {
+                "$ref": "ThemeColor",
+                "optional": true
+              },
+              "popup_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
@@ -20,8 +20,9 @@ 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_autocomplete_popup.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_autocomplete_popup.js
@@ -0,0 +1,233 @@
+"use strict";
+
+// This test checks whether applied WebExtension themes that attempt to change
+// popup properties are applied correctly to the autocomplete bar.
+const POPUP_COLOR = "#85A400";
+const POPUP_TEXT_COLOR_DARK = "#000000";
+const POPUP_TEXT_COLOR_BRIGHT = "#ffffff";
+const POPUP_SELECTED_COLOR = "#9400ff";
+const POPUP_SELECTED_TEXT_COLOR = "#09b9a6";
+
+const POPUP_URL_COLOR_DARK = "#1c78d4";
+const POPUP_ACTION_COLOR_DARK = "#008f8a";
+const POPUP_URL_COLOR_BRIGHT = "#0a84ff";
+const POPUP_ACTION_COLOR_BRIGHT = "#30e60b";
+
+const SEARCH_TERM = "urlbar-reflows-" + Date.now();
+const ONEOFF_URLBAR_PREF = "browser.urlbar.oneOffSearches";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+});
+
+function promisePopupShown(popup) {
+  return new Promise(resolve => {
+    if (popup.state == "open") {
+      resolve();
+    } else {
+      popup.addEventListener("popupshown", function(event) {
+        resolve();
+      }, {once: true});
+    }
+  });
+}
+
+async function promiseAutocompleteResultPopup(inputText) {
+  gURLBar.focus();
+  gURLBar.value = inputText;
+  gURLBar.controller.startSearch(inputText);
+  await promisePopupShown(gURLBar.popup);
+  await BrowserTestUtils.waitForCondition(() => {
+    return gURLBar.controller.searchStatus >=
+      Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
+  });
+}
+
+async function waitForAutocompleteResultAt(index) {
+  let searchString = gURLBar.controller.searchString;
+  await BrowserTestUtils.waitForCondition(
+    () => gURLBar.popup.richlistbox.children.length > index &&
+          gURLBar.popup.richlistbox.children[index].getAttribute("ac-text") == searchString,
+    `Waiting for the autocomplete result for "${searchString}" at [${index}] to appear`);
+  // Ensure the addition is complete, for proper mouse events on the entries.
+  await new Promise(resolve => window.requestIdleCallback(resolve, {timeout: 1000}));
+  return gURLBar.popup.richlistbox.children[index];
+}
+
+add_task(async function setup() {
+  await PlacesUtils.history.clear();
+  const NUM_VISITS = 10;
+  let visits = [];
+
+  for (let i = 0; i < NUM_VISITS; ++i) {
+    visits.push({
+      uri: `http://example.com/urlbar-reflows-${i}`,
+      title: `Reflow test for URL bar entry #${i} - ${SEARCH_TERM}`,
+    });
+  }
+
+  await PlacesTestUtils.addVisits(visits);
+
+  registerCleanupFunction(async function() {
+    await PlacesUtils.history.clear();
+  });
+});
+
+add_task(async function test_popup_url() {
+  // Load extension with brighttext not set
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": "image1.png",
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
+          "popup": POPUP_COLOR,
+          "popup_text": POPUP_TEXT_COLOR_DARK,
+          "popup_highlight": POPUP_SELECTED_COLOR,
+          "popup_highlight_text": POPUP_SELECTED_TEXT_COLOR,
+        },
+      },
+    },
+    files: {
+      "image1.png": BACKGROUND,
+    },
+  });
+
+  await extension.startup();
+
+  let maxResults = Services.prefs.getIntPref("browser.urlbar.maxRichResults");
+  Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
+  registerCleanupFunction(async function() {
+    await PlacesUtils.history.clear();
+    Services.prefs.clearUserPref(ONEOFF_URLBAR_PREF);
+    await BrowserTestUtils.removeTab(tab);
+  });
+
+  let visits = [];
+
+  for (let i = 0; i < maxResults; i++) {
+    visits.push({uri: makeURI("http://example.com/autocomplete/?" + i)});
+  }
+
+  await PlacesTestUtils.addVisits(visits);
+  await promiseAutocompleteResultPopup("example.com/autocomplete");
+  await waitForAutocompleteResultAt(maxResults - 1);
+
+  let popup = gURLBar.popup;
+  let results = popup.richlistbox.children;
+  is(results.length, maxResults,
+     "Should get maxResults=" + maxResults + " results");
+
+  let popupCS = window.getComputedStyle(popup);
+
+  Assert.equal(popupCS.backgroundColor,
+               `rgb(${hexToRGB(POPUP_COLOR).join(", ")})`,
+               `Popup background color should be set to ${POPUP_COLOR}`);
+
+  Assert.equal(popupCS.color,
+               `rgb(${hexToRGB(POPUP_TEXT_COLOR_DARK).join(", ")})`,
+               `Popup color should be set to ${POPUP_TEXT_COLOR_DARK}`);
+
+  // Set the selected attribute to true to test the highlight popup properties
+  results[1].setAttribute("selected", "true");
+  let resultCS = window.getComputedStyle(results[1]);
+
+  Assert.equal(resultCS.backgroundColor,
+               `rgb(${hexToRGB(POPUP_SELECTED_COLOR).join(", ")})`,
+               `Popup highlight background color should be set to ${POPUP_SELECTED_COLOR}`);
+
+  Assert.equal(resultCS.color,
+               `rgb(${hexToRGB(POPUP_SELECTED_TEXT_COLOR).join(", ")})`,
+               `Popup highlight color should be set to ${POPUP_SELECTED_TEXT_COLOR}`);
+
+  results[1].removeAttribute("selected");
+
+  let urlText = document.getAnonymousElementByAttribute(results[1], "anonid", "url-text");
+  Assert.equal(window.getComputedStyle(urlText).color,
+               `rgb(${hexToRGB(POPUP_URL_COLOR_DARK).join(", ")})`,
+               `Urlbar popup url color should be set to ${POPUP_URL_COLOR_DARK}`);
+
+  let actionText = document.getAnonymousElementByAttribute(results[1], "anonid", "action-text");
+  Assert.equal(window.getComputedStyle(actionText).color,
+               `rgb(${hexToRGB(POPUP_ACTION_COLOR_DARK).join(", ")})`,
+               `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_DARK}`);
+
+  let root = document.documentElement;
+  Assert.equal(root.getAttribute("lwt-popup-brighttext"),
+               "",
+               "brighttext should not be set!");
+
+  await extension.unload();
+
+  // Load a manifest with popup_text being bright. Test for bright text properties.
+  extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": "image1.png",
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
+          "popup": POPUP_COLOR,
+          "popup_text": POPUP_TEXT_COLOR_BRIGHT,
+          "popup_highlight": POPUP_SELECTED_COLOR,
+          "popup_highlight_text": POPUP_SELECTED_TEXT_COLOR,
+        },
+      },
+    },
+    files: {
+      "image1.png": BACKGROUND,
+    },
+  });
+
+  await extension.startup();
+
+  popupCS = window.getComputedStyle(popup);
+  Assert.equal(popupCS.color,
+               `rgb(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")})`,
+               `Popup color should be set to ${POPUP_TEXT_COLOR_BRIGHT}`);
+
+  urlText = document.getAnonymousElementByAttribute(results[1], "anonid", "url-text");
+  Assert.equal(window.getComputedStyle(urlText).color,
+               `rgb(${hexToRGB(POPUP_URL_COLOR_BRIGHT).join(", ")})`,
+               `Urlbar popup url color should be set to ${POPUP_URL_COLOR_BRIGHT}`);
+
+  actionText = document.getAnonymousElementByAttribute(results[1], "anonid", "action-text");
+  Assert.equal(window.getComputedStyle(actionText).color,
+               `rgb(${hexToRGB(POPUP_ACTION_COLOR_BRIGHT).join(", ")})`,
+               `Urlbar popup action color should be set to ${POPUP_ACTION_COLOR_BRIGHT}`);
+
+  // Since brighttext is enabled, the seperator color should be
+  // POPUP_TEXT_COLOR_BRIGHT with added alpha.
+  let separator = document.getAnonymousElementByAttribute(results[1], "anonid", "separator");
+  Assert.equal(window.getComputedStyle(separator).color,
+               `rgba(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")}, 0.5)`,
+               `Urlbar popup separator color should be set to ${POPUP_TEXT_COLOR_BRIGHT} with alpha`);
+
+  Assert.equal(root.getAttribute("lwt-popup-brighttext"),
+               "true",
+               "brighttext should be set to true!");
+
+  await extension.unload();
+
+  // Check to see if popup-brighttext and secondary color are not set after
+  // unload of theme
+  Assert.equal(root.getAttribute("lwt-popup-brighttext"),
+               "",
+               "brighttext should not be set!");
+
+  // Calculate what GrayText should be. May differ between platforms.
+  let span = document.createElement("span");
+  span.style.color = "GrayText";
+  let GRAY_TEXT = window.getComputedStyle(span).color;
+
+  separator = document.getAnonymousElementByAttribute(results[1], "anonid", "separator");
+  Assert.equal(window.getComputedStyle(separator).color,
+               GRAY_TEXT,
+               `Urlbar popup separator color should be set to ${GRAY_TEXT}`);
+});
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -99,16 +99,18 @@ LightweightThemeConsumer.prototype = {
     // 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 {
@@ -188,8 +190,27 @@ function _setProperties(root, active, va
   }
 }
 
 function _parseRGB(aColorString) {
   var rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
   rgb.shift();
   return rgb.map(x => parseInt(x));
 }
+
+function _inferPopupColorsFromText(element, active, color) {
+  if (!color) {
+    element.removeAttribute("lwt-popup-brighttext");
+    _setProperty(element, active, "--autocomplete-popup-secondary-color");
+    return;
+  }
+
+  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)`);
+}