Bug 1417883 - Allow theming autocomplete popups. r=jaws,ntim
☠☠ backed out by afe65b05e3e9 ☠ ☠
authorDylan Stokes <stokesdy@msu.edu>
Sun, 11 Feb 2018 12:32:33 -0500
changeset 461775 b03fd1004cacad2e79713f85ab055099ae4b0da2
parent 461774 3c3ae4a1a7ede1ed97de25bd6206fcd0fb38a3b6
child 461776 d41993e69c7e73369c68205ea93aab77794c0f3e
push id1683
push usersfraser@mozilla.com
push dateThu, 26 Apr 2018 16:43:40 +0000
treeherdermozilla-release@5af6cb21869d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, ntim
bugs1417883
milestone60.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=jaws,ntim MozReview-Commit-ID: GITgsY3yYqN
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,9 +20,13 @@ 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"],
 ];
--- 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,22 @@
  * 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[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 +49,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
@@ -64,16 +64,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
@@ -159,16 +159,18 @@ class Theme {
         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 "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
@@ -167,16 +167,24 @@
               },
               "popup_text": {
                 "$ref": "ThemeColor",
                 "optional": true
               },
               "popup_border": {
                 "$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
@@ -18,8 +18,9 @@ support-files =
 [browser_ext_themes_tab_loading.js]
 [browser_ext_themes_tab_text.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_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("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.4)`,
+               `Urlbar popup separator color should be set to ${POPUP_TEXT_COLOR_BRIGHT} with alpha`);
+
+  Assert.equal(root.getAttribute("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("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("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("popup-brighttext");
+  } else {
+    element.setAttribute("popup-brighttext", "true");
+  }
+
+  _setProperty(element, active, "--autocomplete-popup-secondary-color", `rgba(${r}, ${g}, ${b}, 0.4)`);
+}