Bug 1336125 - Apply option styles using a scoped stylesheet to allow for disabling custom styling on the active item. r?mconley
MozReview-Commit-ID: 1dZ1rbKbNY9
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -89,16 +89,17 @@ const PAGECONTENT_COLORS =
"</style>" +
"<body><select id='one'>" +
' <option value="One" style="color: #fff; background-color: #f00;">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 0, 0)"}</option>' +
' <option value="Two" class="blue">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(0, 0, 255)"}</option>' +
' <option value="Three" class="green">{"color": "rgb(128, 0, 128)", "backgroundColor": "rgb(0, 128, 0)"}</option>' +
' <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
+ ' <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
"</select></body></html>";
function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
if (mode == "click" || mode == "mousedown") {
let mousePromise;
if (mode == "click") {
@@ -744,29 +745,30 @@ add_task(function* test_somehidden() {
yield hideSelectPopup(selectPopup, "escape");
yield BrowserTestUtils.removeTab(tab);
});
add_task(function* test_colors_applied_to_popup() {
const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
- let selectPopup = document.getElementById("ContentSelectDropdown").menupopup;
+ let menulist = document.getElementById("ContentSelectDropdown");
+ let selectPopup = menulist.menupopup;
let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
yield popupShownPromise;
// The label contains a JSON string of the expected colors for
// `color` and `background-color`.
- is(selectPopup.parentNode.itemCount, 6, "Correct number of items");
+ is(selectPopup.parentNode.itemCount, 7, "Correct number of items");
let child = selectPopup.firstChild;
let idx = 1;
- ok(child.selected, "The first child should be selected");
+ ok(!child.selected, "The first child should not be selected");
while (child) {
let expected = JSON.parse(child.label);
for (let color of Object.keys(expected)) {
if (color.toLowerCase().includes("color") &&
!expected[color].startsWith("rgb")) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -23,16 +23,21 @@ var currentMenulist = null;
var currentZoom = 1;
var closedWithEnter = false;
var selectRect;
this.SelectParentHelper = {
populate(menulist, items, selectedIndex, zoom, uaBackgroundColor, uaColor) {
// Clear the current contents of the popup
menulist.menupopup.textContent = "";
+ let stylesheet = menulist.querySelector("#ContentSelectDropdownScopedStylesheet");
+ if (stylesheet) {
+ stylesheet.remove();
+ }
+
currentZoom = zoom;
currentMenulist = menulist;
populateChildren(menulist, items, selectedIndex, zoom,
uaBackgroundColor, uaColor);
},
open(browser, menulist, rect, isOpenedViaTouch) {
menulist.hidden = false;
@@ -169,19 +174,28 @@ this.SelectParentHelper = {
browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
},
};
function populateChildren(menulist, options, selectedIndex, zoom,
uaBackgroundColor, uaColor,
parentElement = null, isGroupDisabled = false,
- adjustedTextSize = -1, addSearch = true) {
+ adjustedTextSize = -1, addSearch = true, nthChildIndex = 1) {
let element = menulist.menupopup;
let win = element.ownerGlobal;
+ let scopedStyleSheet = menulist.querySelector("#ContentSelectDropdownScopedStylesheet");
+ if (!scopedStyleSheet) {
+ let doc = element.ownerDocument;
+ scopedStyleSheet = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ scopedStyleSheet.setAttribute("id", "ContentSelectDropdownScopedStylesheet");
+ scopedStyleSheet.scoped = true;
+ scopedStyleSheet.hidden = true;
+ scopedStyleSheet = menulist.appendChild(scopedStyleSheet);
+ }
// -1 just means we haven't calculated it yet. When we recurse through this function
// we will pass in adjustedTextSize to save on recalculations.
if (adjustedTextSize == -1) {
// Grab the computed text size and multiply it by the remote browser's fullZoom to ensure
// the popup's text size is matched with the content's. We can't just apply a CSS transform
// here as the popup's preferred size is calculated pre-transform.
let textSize = win.getComputedStyle(element).getPropertyValue("font-size");
@@ -196,48 +210,53 @@ function populateChildren(menulist, opti
item.style.direction = option.textDirection;
item.style.fontSize = adjustedTextSize;
item.hidden = option.display == "none" || (parentElement && parentElement.hidden);
// Keep track of which options are hidden by page content, so we can avoid showing
// them on search input
item.hiddenByContent = item.hidden;
item.setAttribute("tooltiptext", option.tooltip);
- let customOptionStylingUsed = false;
+ let ruleBody = "";
if (option.backgroundColor &&
option.backgroundColor != "transparent" &&
option.backgroundColor != uaBackgroundColor) {
- item.style.backgroundColor = option.backgroundColor;
- customOptionStylingUsed = true;
+ ruleBody = `background-color: ${option.backgroundColor};`;
}
if (option.color &&
option.color != uaColor) {
- item.style.color = option.color;
- customOptionStylingUsed = true;
+ ruleBody += `color: ${option.color};`;
}
- if (customOptionStylingUsed) {
+ if (ruleBody) {
+ let sheet = scopedStyleSheet.sheet;
+ sheet.insertRule(`${item.localName}:nth-child(${nthChildIndex}):not([_moz-menuactive="true"]) {
+ ${ruleBody}
+ }`, 0);
+
item.setAttribute("customoptionstyling", "true");
} else {
item.removeAttribute("customoptionstyling");
}
element.appendChild(item);
+ nthChildIndex++;
// A disabled optgroup disables all of its child options.
let isDisabled = isGroupDisabled || option.disabled;
if (isDisabled) {
item.setAttribute("disabled", "true");
}
if (isOptGroup) {
- populateChildren(menulist, option.children, selectedIndex, zoom,
- uaBackgroundColor, uaColor,
- item, isDisabled, adjustedTextSize, false);
+ nthChildIndex =
+ populateChildren(menulist, option.children, selectedIndex, zoom,
+ uaBackgroundColor, uaColor,
+ item, isDisabled, adjustedTextSize, false);
} else {
if (option.index == selectedIndex) {
// We expect the parent element of the popup to be a <xul:menulist> that
// has the popuponly attribute set to "true". This is necessary in order
// for a <xul:menupopup> to act like a proper <html:select> dropdown, as
// the <xul:menulist> does things like remember state and set the
// _moz-menuactive attribute on the selected <xul:menuitem>.
menulist.selectedItem = item;
@@ -303,16 +322,17 @@ function populateChildren(menulist, opti
return;
}
event.preventDefault();
}, true);
element.insertBefore(searchbox, element.childNodes[0]);
}
+ return nthChildIndex;
}
function onSearchInput() {
let searchObj = this;
// Get input from search field, set to all lower case for comparison
let input = searchObj.value.toLowerCase();
// Get all items in dropdown (could be options or optgroups)
--- a/toolkit/themes/linux/global/menu.css
+++ b/toolkit/themes/linux/global/menu.css
@@ -31,20 +31,16 @@ menuitem[_moz-menuactive="true"] {
color: -moz-menuhovertext;
background-color: -moz-menuhover;
}
menuitem[customoptionstyling="true"] {
-moz-appearance: none;
}
-menuitem[_moz-menuactive="true"][customoptionstyling="true"] {
- filter: invert(100%);
-}
-
menu[disabled="true"],
menuitem[disabled="true"],
menucaption[disabled="true"] {
color: GrayText;
}
menubar > menu {
padding: 0px 4px;
--- a/toolkit/themes/osx/global/menu.css
+++ b/toolkit/themes/osx/global/menu.css
@@ -136,20 +136,16 @@ menuitem[_moz-menuactive="true"] {
}
menuitem[customoptionstyling="true"] {
-moz-appearance: none;
padding-top: 0;
padding-bottom: 0;
}
-menuitem[_moz-menuactive="true"][customoptionstyling="true"] {
- filter: invert(100%);
-}
-
/* ::::: menu/menuitems in menulist popups ::::: */
menulist > menupopup > menuitem,
menulist > menupopup > menucaption,
menulist > menupopup > menu {
max-width: none;
font: inherit;
color: -moz-FieldText;
--- a/toolkit/themes/windows/global/menu.css
+++ b/toolkit/themes/windows/global/menu.css
@@ -199,20 +199,16 @@ menulist > menupopup > menu {
}
menulist > menupopup > menuitem[_moz-menuactive="true"],
menulist > menupopup > menu[_moz-menuactive="true"] {
background-color: highlight;
color: highlighttext;
}
-menulist > menupopup > menuitem[_moz-menuactive="true"][customoptionstyling="true"] {
- filter: invert(100%);
-}
-
menulist > menupopup > menuitem > .menu-iconic-left,
menulist > menupopup > menucaption > .menu-iconic-left,
menulist > menupopup > menu > .menu-iconic-left {
display: none;
}
menulist > menupopup > menuitem > label,
menulist > menupopup > menucaption > label,