Bug 1386015 - Do not generate styling for each element with inherited color. r=jaws

This patch does a minor refactor of the code used to style popup menu for
the <select> element.
It improves the custom styling experience on MacOS, preserves the functionality
on Windows and removes the unnecessary per-item CSS rules significantly
improving the performance of opening the <select> list.


MozReview-Commit-ID: 7myXq8aDAWr

--HG--
extra : rebase_source : 3ff52f832ec471cca0942e5d8a39961dbc84cff4
This commit is contained in:
Zibi Braniecki 2017-08-11 17:38:14 -07:00
parent e1b628b3c2
commit a7b00209a5
2 changed files with 155 additions and 13 deletions

View File

@ -169,6 +169,19 @@ SELECT_LONG_WITH_TRANSITION +=
' <option selected="true">{"end": "true"}</option>' +
"</select></body></html>";
const SELECT_INHERITED_COLORS_ON_OPTIONS_DONT_GET_UNIQUE_RULES_IF_RULE_SET_ON_SELECT = `
<html><head><style>
select { color: blue; text-shadow: 1px 1px 2px blue; }
.redColor { color: red; }
.textShadow { text-shadow: 1px 1px 2px black; }
</style></head><body><select id='one'>
<option>{"color": "rgb(0, 0, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>
<option class="redColor">{"color": "rgb(255, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>
<option class="textShadow">{"color": "rgb(0, 0, 255)", "textShadow": "rgb(0, 0, 0) 1px 1px 2px", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>
<option selected="true">{"end": "true"}</option>
</select></body></html>
`;
function getSystemColor(color) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
@ -472,3 +485,48 @@ add_task(async function test_select_with_transition_doesnt_lose_scroll_position(
await hideSelectPopup(selectPopup, "escape");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
add_task(async function test_select_inherited_colors_on_options_dont_get_unique_rules_if_rule_set_on_select() {
let options = {
selectColor: "rgb(0, 0, 255)",
selectBgColor: "rgb(255, 255, 255)",
selectTextShadow: "rgb(0, 0, 255) 1px 1px 2px",
leaveOpen: true
};
await testSelectColors(SELECT_INHERITED_COLORS_ON_OPTIONS_DONT_GET_UNIQUE_RULES_IF_RULE_SET_ON_SELECT, 4, options);
let stylesheetEl = document.getElementById("ContentSelectDropdownStylesheet");
let sheet = stylesheetEl.sheet;
/* Check that there are no rulesets for the first option, but that
one exists for the second option and sets the color of that
option to "rgb(255, 0, 0)" */
function hasMatchingRuleForOption(cssRules, index, styles = {}) {
for (let rule of cssRules) {
if (rule.selectorText.includes(`:nth-child(${index})`)) {
if (Object.keys(styles).some(key => rule.style[key] !== styles[key])) {
continue;
}
return true;
}
}
return false;
}
is(hasMatchingRuleForOption(sheet.cssRules, 1), false,
"There should be no rules specific to option1");
is(hasMatchingRuleForOption(sheet.cssRules, 2, {
color: "rgb(255, 0, 0)"
}), true, "There should be a rule specific to option2 and it should have color: red");
is(hasMatchingRuleForOption(sheet.cssRules, 3, {
"text-shadow": "rgb(0, 0, 0) 1px 1px 2px"
}), true, "There should be a rule specific to option3 and it should have text-shadow: rgb(0, 0, 0) 1px 1px 2px");
let menulist = document.getElementById("ContentSelectDropdown");
let selectPopup = menulist.menupopup;
await hideSelectPopup(selectPopup, "escape");
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

View File

@ -26,12 +26,40 @@ var selectRect = null;
var currentZoom = 1;
var closedWithEnter = false;
var customStylingEnabled = Services.prefs.getBoolPref("dom.forms.select.customstyling");
var usedSelectBackgroundColor;
this.SelectParentHelper = {
/**
* `populate` takes the `menulist` element and a list of `items` and generates
* a popup list of options.
*
* If `customStylingEnabled` is set to `true`, the function will alse
* style the select and its popup trying to prevent the text
* and background to end up in the same color.
*
* All `ua*` variables represent the color values for the default colors
* for their respective form elements used by the user agent.
* The `select*` variables represent the color values defined for the
* particular <select> element.
*
* The `customoptionstyling` attribute controls the application of
* `-moz-appearance` on the elements and is disabled if the element is
* defining its own background-color.
*
* @param {Element} menulist
* @param {Array<Element>} items
* @param {Number} selectedIndex
* @param {Number} zoom
* @param {String} uaBackgroundColor
* @param {String} uaColor
* @param {String} uaSelectBackgroundColor
* @param {String} uaSelectColor
* @param {String} selectBackgroundColor
* @param {String} selectColor
* @param {String} selectTextShadow
*/
populate(menulist, items, selectedIndex, zoom, uaBackgroundColor, uaColor,
uaSelectBackgroundColor, uaSelectColor, selectBackgroundColor, selectColor,
selectTextShadow) {
uaSelectBackgroundColor, uaSelectColor, selectBackgroundColor,
selectColor, selectTextShadow) {
// Clear the current contents of the popup
menulist.menupopup.textContent = "";
let stylesheet = menulist.querySelector("#ContentSelectDropdownStylesheet");
@ -50,6 +78,9 @@ this.SelectParentHelper = {
}
let ruleBody = "";
let usedSelectBackgroundColor;
let usedSelectColor;
let selectBackgroundSet = false;
// Some webpages set the <select> backgroundColor to transparent,
// but they don't intend to change the popup to transparent.
@ -58,27 +89,41 @@ this.SelectParentHelper = {
selectBackgroundColor != "rgba(0, 0, 0, 0)") {
ruleBody = `background-image: linear-gradient(${selectBackgroundColor}, ${selectBackgroundColor});`;
usedSelectBackgroundColor = selectBackgroundColor;
selectBackgroundSet = true;
} else {
usedSelectBackgroundColor = uaSelectBackgroundColor;
}
if (customStylingEnabled &&
selectColor != uaSelectColor &&
selectColor != selectBackgroundColor &&
(selectBackgroundColor != "rgba(0, 0, 0, 0)" ||
selectColor != uaSelectBackgroundColor)) {
selectColor != usedSelectBackgroundColor) {
ruleBody += `color: ${selectColor};`;
usedSelectColor = selectColor;
} else {
usedSelectColor = uaColor;
}
if (customStylingEnabled &&
selectTextShadow != "none") {
ruleBody += `text-shadow: ${selectTextShadow};`;
sheet.insertRule(`#ContentSelectDropdown > menupopup > [_moz-menuactive="true"] {
text-shadow: none;
}`, 0);
}
if (ruleBody) {
sheet.insertRule(`#ContentSelectDropdown > menupopup {
${ruleBody}
}`, 0);
sheet.insertRule(`#ContentSelectDropdown > menupopup > :not([_moz-menuactive="true"]) {
color: inherit;
}`, 0);
}
// We only set the `customoptionstyling` if the background has been
// manually set. This prevents the overlap between moz-appearance and
// background-color. `color` and `text-shadow` do not interfere with it.
if (selectBackgroundSet) {
menulist.menupopup.setAttribute("customoptionstyling", "true");
} else {
menulist.menupopup.removeAttribute("customoptionstyling");
@ -87,7 +132,7 @@ this.SelectParentHelper = {
currentZoom = zoom;
currentMenulist = menulist;
populateChildren(menulist, items, selectedIndex, zoom,
uaBackgroundColor, uaColor, sheet);
usedSelectBackgroundColor, usedSelectColor, selectTextShadow, selectBackgroundSet, sheet);
},
open(browser, menulist, rect, isOpenedViaTouch) {
@ -253,10 +298,36 @@ this.SelectParentHelper = {
};
/**
* `populateChildren` creates all <menuitem> elements for the popup menu
* based on the list of <option> elements from the <select> element.
*
* It attempts to intelligently add per-item CSS rules if the single
* item values differ from the parent menu values and attempting to avoid
* ending up with the same color of text and background.
*
* @param {Element} menulist
* @param {Array<Element>} options
* @param {Number} selectedIndex
* @param {Number} zoom
* @param {String} usedSelectBackgroundColor
* @param {String} usedSelectColor
* @param {String} selectTextShadow
* @param {String} selectBackgroundSet
* @param {CSSStyleSheet} sheet
* @param {Element} parentElement
* @param {Boolean} isGroupDisabled
* @param {Number} adjustedTextSize
* @param {Boolean} addSearch
* @param {Number} nthChildIndex
* @returns {Number}
*/
function populateChildren(menulist, options, selectedIndex, zoom,
uaBackgroundColor, uaColor, sheet,
usedSelectBackgroundColor, usedSelectColor,
selectTextShadow, selectBackgroundSet, sheet,
parentElement = null, isGroupDisabled = false,
adjustedTextSize = -1, addSearch = true, nthChildIndex = 1) {
adjustedTextSize = -1, addSearch = true,
nthChildIndex = 1) {
let element = menulist.menupopup;
let win = element.ownerGlobal;
@ -284,21 +355,30 @@ function populateChildren(menulist, options, selectedIndex, zoom,
item.setAttribute("tooltiptext", option.tooltip);
let ruleBody = "";
let usedBackgroundColor;
let optionBackgroundSet = false;
if (customStylingEnabled &&
option.backgroundColor &&
option.backgroundColor != "rgba(0, 0, 0, 0)" &&
option.backgroundColor != usedSelectBackgroundColor) {
ruleBody = `background-color: ${option.backgroundColor};`;
usedBackgroundColor = option.backgroundColor;
optionBackgroundSet = true;
} else {
usedBackgroundColor = usedSelectBackgroundColor;
}
if (customStylingEnabled &&
option.color &&
option.color != uaColor) {
option.color != usedBackgroundColor &&
option.color != usedSelectColor) {
ruleBody += `color: ${option.color};`;
}
if (customStylingEnabled &&
option.textShadow) {
option.textShadow &&
option.textShadow != selectTextShadow) {
ruleBody += `text-shadow: ${option.textShadow};`;
}
@ -307,7 +387,7 @@ function populateChildren(menulist, options, selectedIndex, zoom,
${ruleBody}
}`, 0);
if (option.textShadow) {
if (option.textShadow && option.textShadow != selectTextShadow) {
// Need to explicitly disable the possibly inherited
// text-shadow rule when _moz-menuactive=true since
// _moz-menuactive=true disables custom option styling.
@ -315,7 +395,10 @@ function populateChildren(menulist, options, selectedIndex, zoom,
text-shadow: none;
}`, 0);
}
}
if (customStylingEnabled &&
(optionBackgroundSet || selectBackgroundSet)) {
item.setAttribute("customoptionstyling", "true");
} else {
item.removeAttribute("customoptionstyling");
@ -333,7 +416,8 @@ function populateChildren(menulist, options, selectedIndex, zoom,
if (isOptGroup) {
nthChildIndex =
populateChildren(menulist, option.children, selectedIndex, zoom,
uaBackgroundColor, uaColor, sheet,
usedSelectBackgroundColor, usedSelectColor,
selectTextShadow, selectBackgroundSet, sheet,
item, isDisabled, adjustedTextSize, false, nthChildIndex);
} else {
if (option.index == selectedIndex) {