Bug 1375476 - Support font-style / font-weight and font-size on <option> elements. r=mconley
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 28 Feb 2019 01:44:52 +0000
changeset 519464 1d1443507be4aedaa6a868acf08e5086d607a9e0
parent 519463 bb37a041dbebaf73746e0ba8822eadd27945ec67
child 519465 56d817208b35b3b1c16833b3dcd7ad80a7297371
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1375476
milestone67.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 1375476 - Support font-style / font-weight and font-size on <option> elements. r=mconley And cleanup / make the code a bit more generic while at it. Differential Revision: https://phabricator.services.mozilla.com/D20463
toolkit/actors/SelectChild.jsm
toolkit/content/widgets/browser-custom-element.js
toolkit/modules/SelectParentHelper.jsm
--- a/toolkit/actors/SelectChild.jsm
+++ b/toolkit/actors/SelectChild.jsm
@@ -15,35 +15,38 @@ ChromeUtils.defineModuleGetter(this, "De
                                "resource://gre/modules/DeferredTask.jsm");
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["InspectorUtils"]);
 
 const kStateActive = 0x00000001; // NS_EVENT_STATE_ACTIVE
 const kStateHover = 0x00000004; // NS_EVENT_STATE_HOVER
 
 const SUPPORTED_PROPERTIES = [
+  "direction",
   "color",
   "background-color",
   "text-shadow",
+  "font-family",
+  "font-weight",
+  "font-size",
+  "font-style",
 ];
 
 // A process global state for whether or not content thinks
 // that a <select> dropdown is open or not. This is managed
 // entirely within this module, and is read-only accessible
 // via SelectContentHelper.open.
 var gOpen = false;
 
 var SelectContentHelper = function(aElement, aOptions, aGlobal) {
   this.element = aElement;
   this.initialSelection = aElement[aElement.selectedIndex] || null;
   this.global = aGlobal;
   this.closedWithClickOn = false;
   this.isOpenedViaTouch = aOptions.isOpenedViaTouch;
-  this._selectBackgroundColor = null;
-  this._selectColor = null;
   this._closeAfterBlur = true;
   this._pseudoStylesSetup = false;
   this._lockedDescendants = null;
   this.init();
   this.showDropDown();
   this._updateTimer = new DeferredTask(this._update.bind(this), 0);
 };
 
@@ -97,32 +100,25 @@ this.SelectContentHelper.prototype = {
     gOpen = false;
   },
 
   showDropDown() {
     this.element.openInParentProcess = true;
     this._setupPseudoClassStyles();
     let rect = this._getBoundingContentRect();
     let computedStyles = getComputedStyles(this.element);
-    this._selectBackgroundColor = computedStyles.backgroundColor;
-    this._selectColor = computedStyles.color;
-    this._selectTextShadow = computedStyles.textShadow;
     let options = this._buildOptionList();
     let defaultStyles = this.element.ownerGlobal.getDefaultComputedStyle(this.element);
     this.global.sendAsyncMessage("Forms:ShowDropDown", {
-      direction: computedStyles.direction,
       isOpenedViaTouch: this.isOpenedViaTouch,
       options,
       rect,
       selectedIndex: this.element.selectedIndex,
-      selectBackgroundColor: this._selectBackgroundColor,
-      selectColor: this._selectColor,
-      selectTextShadow: this._selectTextShadow,
-      uaSelectBackgroundColor: defaultStyles.backgroundColor,
-      uaSelectColor: defaultStyles.color,
+      style: supportedStyles(computedStyles),
+      defaultStyle: supportedStyles(defaultStyles),
     });
     this._clearPseudoClassStyles();
     gOpen = true;
   },
 
   _setupPseudoClassStyles() {
     if (this._pseudoStylesSetup) {
       throw new Error("pseudo styles must not be set up yet");
@@ -159,40 +155,35 @@ this.SelectContentHelper.prototype = {
   _getBoundingContentRect() {
     return BrowserUtils.getElementBoundingScreenRect(this.element);
   },
 
   _buildOptionList() {
     if (!this._pseudoStylesSetup) {
       throw new Error("pseudo styles must be set up");
     }
-    return buildOptionListForChildren(this.element);
+    let uniqueStyles = [];
+    let options = buildOptionListForChildren(this.element, uniqueStyles);
+    return { options, uniqueStyles };
   },
 
   _update() {
     // The <select> was updated while the dropdown was open.
     // Let's send up a new list of options.
     // Technically we might not need to set this pseudo-class
     // during _update() since the element should organically
     // have :focus, though it is here for belt-and-suspenders.
     this._setupPseudoClassStyles();
     let computedStyles = getComputedStyles(this.element);
-    this._selectBackgroundColor = computedStyles.backgroundColor;
-    this._selectColor = computedStyles.color;
-    this._selectTextShadow = computedStyles.textShadow;
-
     let defaultStyles = this.element.ownerGlobal.getDefaultComputedStyle(this.element);
     this.global.sendAsyncMessage("Forms:UpdateDropDown", {
       options: this._buildOptionList(),
       selectedIndex: this.element.selectedIndex,
-      selectBackgroundColor: this._selectBackgroundColor,
-      selectColor: this._selectColor,
-      selectTextShadow: this._selectTextShadow,
-      uaSelectBackgroundColor: defaultStyles.backgroundColor,
-      uaSelectColor: defaultStyles.color,
+      style: supportedStyles(computedStyles),
+      defaultStyle: supportedStyles(defaultStyles),
     });
     this._clearPseudoClassStyles();
   },
 
   dispatchMouseEvent(win, target, eventName) {
     let mouseEvent = new win.MouseEvent(eventName, {
       view: win,
       bubbles: true,
@@ -311,24 +302,51 @@ this.SelectContentHelper.prototype = {
         break;
       case "transitionend":
         if (SUPPORTED_PROPERTIES.includes(event.propertyName)) {
           this._updateTimer.arm();
         }
         break;
     }
   },
-
 };
 
 function getComputedStyles(element) {
   return element.ownerGlobal.getComputedStyle(element);
 }
 
-function buildOptionListForChildren(node) {
+function supportedStyles(cs) {
+  let styles = {};
+  for (let property of SUPPORTED_PROPERTIES) {
+    styles[property] = cs.getPropertyValue(property);
+  }
+  return styles;
+}
+
+function supportedStylesEqual(styles, otherStyles) {
+  for (let property of SUPPORTED_PROPERTIES) {
+    if (styles[property] !== otherStyles[property]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function uniqueStylesIndex(cs, uniqueStyles) {
+  let styles = supportedStyles(cs);
+  for (let i = uniqueStyles.length; i--; ) {
+    if (supportedStylesEqual(uniqueStyles[i], styles)) {
+      return i;
+    }
+  }
+  uniqueStyles.push(styles);
+  return uniqueStyles.length - 1;
+}
+
+function buildOptionListForChildren(node, uniqueStyles) {
   let result = [];
 
   for (let child of node.children) {
     let tagName = child.tagName.toUpperCase();
 
     if (tagName == "OPTION" || tagName == "OPTGROUP") {
       if (child.hidden) {
         continue;
@@ -337,40 +355,31 @@ function buildOptionListForChildren(node
       let textContent =
         tagName == "OPTGROUP" ? child.getAttribute("label")
                               : child.text;
       if (textContent == null) {
         textContent = "";
       }
 
       let cs = getComputedStyles(child);
-
-      // Note: If you add any more CSS properties support here,
-      // please add the property name to the SUPPORTED_PROPERTIES
-      // list so that the menu can be correctly updated when CSS
-      // transitions are used.
       let info = {
         index: child.index,
         tagName,
         textContent,
         disabled: child.disabled,
         display: cs.display,
-        // We need to do this for every option element as each one can have
-        // an individual style set for direction
-        textDirection: cs.direction,
         tooltip: child.title,
-        backgroundColor: cs.backgroundColor,
-        color: cs.color,
-        children: tagName == "OPTGROUP" ? buildOptionListForChildren(child) : [],
+        children: tagName == "OPTGROUP"
+          ? buildOptionListForChildren(child, uniqueStyles)
+          : [],
+        // Most options have the same style. In order to reduce the size of the
+        // IPC message, coalesce them in uniqueStyles.
+        styleIndex: uniqueStylesIndex(cs, uniqueStyles),
       };
 
-      if (cs.textShadow != "none") {
-        info.textShadow = cs.textShadow;
-      }
-
       result.push(info);
     }
   }
   return result;
 }
 
 class SelectChild extends ActorChild {
   handleEvent(event) {
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -1255,20 +1255,20 @@ class MozBrowser extends MozElementMixin
       case "Forms:ShowDropDown":
         {
           if (!this._selectParentHelper) {
             this._selectParentHelper =
               ChromeUtils.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
           }
 
           let menulist = document.getElementById(this.getAttribute("selectmenulist"));
-          menulist.menupopup.style.direction = data.direction;
-          this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom,
-            data.uaSelectBackgroundColor, data.uaSelectColor,
-            data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
+          menulist.menupopup.style.direction = data.style.direction;
+          this._selectParentHelper.populate(menulist, data.options.options,
+            data.options.uniqueStyles, data.selectedIndex, this._fullZoom,
+            data.defaultStyle, data.style);
           this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
           break;
         }
 
       case "Forms:HideDropDown":
         {
           if (this._selectParentHelper) {
             let menulist = document.getElementById(this.getAttribute("selectmenulist"));
@@ -1308,20 +1308,19 @@ class MozBrowser extends MozElementMixin
               ChromeUtils.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
           }
 
           let menulist = document.getElementById(this.getAttribute("selectmenulist"));
           menulist.menupopup.style.direction = data.direction;
 
           let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
             this.isSyntheticDocument ? this._fullZoom : this._textZoom;
-          this._selectParentHelper.populate(menulist, data.options, data.selectedIndex,
-            zoom,
-            data.uaSelectBackgroundColor, data.uaSelectColor,
-            data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
+          this._selectParentHelper.populate(menulist, data.options.options,
+            data.options.uniqueStyles, data.selectedIndex, zoom,
+            data.defaultStyle, data.style);
           this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
           break;
         }
 
       case "FullZoomChange":
         {
           this._fullZoom = data.value;
           let event = document.createEvent("Events");
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -41,100 +41,106 @@ var SelectParentHelper = {
    * 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 {Array<Object>}  uniqueItemStyles
    * @param {Number}         selectedIndex
    * @param {Number}         zoom
-   * @param {String}         uaSelectBackgroundColor
-   * @param {String}         uaSelectColor
-   * @param {String}         selectBackgroundColor
-   * @param {String}         selectColor
-   * @param {String}         selectTextShadow
+   * @param {Object}         uaStyle
+   * @param {Object}         selectStyle
+   *
+   * FIXME(emilio): injecting a stylesheet is a somewhat inefficient way to do
+   * this, can we use more style attributes?
+   *
+   * FIXME(emilio, bug 1530709): At the very least we should use CSSOM to avoid
+   * trusting the IPC message too much.
    */
-  populate(menulist, items, selectedIndex, zoom,
-           uaSelectBackgroundColor, uaSelectColor, selectBackgroundColor,
-           selectColor, selectTextShadow) {
+  populate(menulist, items, uniqueItemStyles, selectedIndex, zoom,
+           uaStyle, selectStyle) {
     // Clear the current contents of the popup
     menulist.menupopup.textContent = "";
     let stylesheet = menulist.querySelector("#ContentSelectDropdownStylesheet");
     if (stylesheet) {
       stylesheet.remove();
     }
 
     let doc = menulist.ownerDocument;
     let sheet;
     if (customStylingEnabled) {
       stylesheet = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
       stylesheet.setAttribute("id", "ContentSelectDropdownStylesheet");
       stylesheet.hidden = true;
       stylesheet = menulist.appendChild(stylesheet);
       sheet = stylesheet.sheet;
+    } else {
+      selectStyle = uaStyle;
     }
 
-    let ruleBody = "";
-    let usedSelectBackgroundColor;
-    let usedSelectColor;
     let selectBackgroundSet = false;
 
+    if (selectStyle["background-color"] == "rgba(0, 0, 0, 0)") {
+      selectStyle["background-color"] = uaStyle["background-color"];
+    }
+
     // Some webpages set the <select> backgroundColor to transparent,
     // but they don't intend to change the popup to transparent.
     if (customStylingEnabled &&
-        selectBackgroundColor != uaSelectBackgroundColor &&
-        selectBackgroundColor != "rgba(0, 0, 0, 0)") {
-      ruleBody = `background-image: linear-gradient(${selectBackgroundColor}, ${selectBackgroundColor});`;
-      usedSelectBackgroundColor = selectBackgroundColor;
+        selectStyle["background-color"] != uaStyle["background-color"]) {
+      let color = selectStyle["background-color"];
+      selectStyle["background-image"] = `linear-gradient(${color}, ${color});`;
       selectBackgroundSet = true;
-    } else {
-      usedSelectBackgroundColor = uaSelectBackgroundColor;
+    }
+
+    if (selectStyle.color == selectStyle["background-color"]) {
+      selectStyle.color = uaStyle.color;
     }
 
-    if (customStylingEnabled &&
-        selectColor != uaSelectColor &&
-        selectColor != usedSelectBackgroundColor) {
-      ruleBody += `color: ${selectColor};`;
-      usedSelectColor = selectColor;
-    } else {
-      usedSelectColor = uaSelectColor;
-    }
+    if (customStylingEnabled) {
+      if (selectStyle["text-shadow"] != "none") {
+        sheet.insertRule(`#ContentSelectDropdown > menupopup > [_moz-menuactive="true"] {
+          text-shadow: none;
+        }`, 0);
+      }
 
-    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);
+      let ruleBody = "";
+      for (let property in selectStyle) {
+        if (property == "background-color" || property == "direction")
+          continue; // Handled above, or before.
+        if (selectStyle[property] != uaStyle[property]) {
+          ruleBody += `${property}: ${selectStyle[property]};`;
+        }
+      }
+      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");
     }
 
     currentZoom = zoom;
     currentMenulist = menulist;
-    populateChildren(menulist, items, selectedIndex, zoom,
-                     usedSelectBackgroundColor, usedSelectColor, selectTextShadow, selectBackgroundSet, sheet);
+    populateChildren(menulist, items, uniqueItemStyles, selectedIndex, zoom,
+                     selectStyle, selectBackgroundSet, sheet);
   },
 
   open(browser, menulist, rect, isOpenedViaTouch) {
     menulist.hidden = false;
     currentBrowser = browser;
     closedWithEnter = false;
     selectRect = rect;
     this._registerListeners(browser, menulist.menupopup);
@@ -245,24 +251,19 @@ var SelectParentHelper = {
         return;
       }
 
       let scrollBox = currentMenulist.menupopup.scrollBox.scrollbox;
       let scrollTop = scrollBox.scrollTop;
 
       let options = msg.data.options;
       let selectedIndex = msg.data.selectedIndex;
-      let uaSelectBackgroundColor = msg.data.uaSelectBackgroundColor;
-      let uaSelectColor = msg.data.uaSelectColor;
-      let selectBackgroundColor = msg.data.selectBackgroundColor;
-      let selectColor = msg.data.selectColor;
-      let selectTextShadow = msg.data.selectTextShadow;
-      this.populate(currentMenulist, options, selectedIndex,
-                    currentZoom, uaSelectBackgroundColor, uaSelectColor,
-                    selectBackgroundColor, selectColor, selectTextShadow);
+      this.populate(currentMenulist, options.options, options.uniqueStyles,
+                    selectedIndex, currentZoom, msg.data.defaultStyle,
+                    msg.data.style);
 
       // Restore scroll position to what it was prior to the update.
       scrollBox.scrollTop = scrollTop;
     } else if (msg.name == "Forms:BlurDropDown-Ping") {
       currentBrowser.messageManager.sendAsyncMessage("Forms:BlurDropDown-Pong", {});
     }
   },
 
@@ -297,127 +298,108 @@ var SelectParentHelper = {
  * 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 {Array<Object>}  uniqueOptionStyles
  * @param {Number}         selectedIndex
  * @param {Number}         zoom
- * @param {String}         usedSelectBackgroundColor
- * @param {String}         usedSelectColor
- * @param {String}         selectTextShadow
- * @param {String}         selectBackgroundSet
+ * @param {Object}         selectStyle
+ * @param {Boolean}        selectBackgroundSet
  * @param {CSSStyleSheet}  sheet
  * @param {Element}        parentElement
  * @param {Boolean}        isGroupDisabled
- * @param {Number}         adjustedTextSize
  * @param {Boolean}        addSearch
  * @param {Number}         nthChildIndex
  * @returns {Number}
+ *
+ * FIXME(emilio): Again, using a stylesheet + :nth-child is not really efficient.
  */
-function populateChildren(menulist, options, selectedIndex, zoom,
-                          usedSelectBackgroundColor, usedSelectColor,
-                          selectTextShadow, selectBackgroundSet, sheet,
+function populateChildren(menulist, options, uniqueOptionStyles, selectedIndex,
+                          zoom, selectStyle, selectBackgroundSet, sheet,
                           parentElement = null, isGroupDisabled = false,
-                          adjustedTextSize = -1, addSearch = true,
-                          nthChildIndex = 1) {
+                          addSearch = true, nthChildIndex = 1) {
   let element = menulist.menupopup;
-  let win = element.ownerGlobal;
-
-  // -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");
-    adjustedTextSize = (zoom * parseFloat(textSize, 10)) + "px";
-  }
 
   for (let option of options) {
     let isOptGroup = (option.tagName == "OPTGROUP");
     let item = element.ownerDocument.createXULElement(isOptGroup ? "menucaption" : "menuitem");
+    let style = uniqueOptionStyles[option.styleIndex];
 
     item.setAttribute("label", option.textContent);
-    item.style.direction = option.textDirection;
-    item.style.fontSize = adjustedTextSize;
+    item.style.direction = style.direction;
+    item.style.fontSize = (zoom * parseFloat(style["font-size"], 10)) + "px";
     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 ruleBody = "";
-    let usedBackgroundColor;
-    let optionBackgroundSet = false;
+    if (style["background-color"] == "rgba(0, 0, 0, 0)") {
+      style["background-color"] = selectStyle["background-color"];
+    }
 
-    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;
+    let optionBackgroundSet = style["background-color"] != selectStyle["background-color"];
+
+    if (style.color == style["background-color"]) {
+      style.color = selectStyle.color;
     }
 
-    if (customStylingEnabled &&
-        option.color &&
-        option.color != usedBackgroundColor &&
-        option.color != usedSelectColor) {
-      ruleBody += `color: ${option.color};`;
-    }
-
-    if (customStylingEnabled &&
-        option.textShadow &&
-        option.textShadow != selectTextShadow) {
-      ruleBody += `text-shadow: ${option.textShadow};`;
-    }
+    if (customStylingEnabled) {
+      let ruleBody = "";
+      for (let property in style) {
+        if (property == "direction" || property == "font-size")
+          continue; // handled above
+        if (style[property] != selectStyle[property]) {
+          ruleBody += `${property}: ${style[property]};`;
+        }
+      }
 
-    if (ruleBody) {
-      sheet.insertRule(`#ContentSelectDropdown > menupopup > :nth-child(${nthChildIndex}):not([_moz-menuactive="true"]) {
-        ${ruleBody}
-      }`, 0);
+      if (ruleBody) {
+        sheet.insertRule(`#ContentSelectDropdown > menupopup > :nth-child(${nthChildIndex}):not([_moz-menuactive="true"]) {
+          ${ruleBody}
+        }`, 0);
 
-      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.
-        sheet.insertRule(`#ContentSelectDropdown > menupopup > :nth-child(${nthChildIndex})[_moz-menuactive="true"] {
-          text-shadow: none;
-        }`, 0);
+        if (style["text-shadow"] != "none" &&
+            style["text-shadow"] != selectStyle["text-shadow"]) {
+          // Need to explicitly disable the possibly inherited
+          // text-shadow rule when _moz-menuactive=true since
+          // _moz-menuactive=true disables custom option styling.
+          sheet.insertRule(`#ContentSelectDropdown > menupopup > :nth-child(${nthChildIndex})[_moz-menuactive="true"] {
+            text-shadow: none;
+          }`, 0);
+        }
       }
     }
 
-    if (customStylingEnabled &&
-        (optionBackgroundSet || selectBackgroundSet)) {
+    if (customStylingEnabled && (optionBackgroundSet || selectBackgroundSet)) {
       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) {
       nthChildIndex =
-        populateChildren(menulist, option.children, selectedIndex, zoom,
-                         usedSelectBackgroundColor, usedSelectColor,
-                         selectTextShadow, selectBackgroundSet, sheet,
-                         item, isDisabled, adjustedTextSize, false, nthChildIndex);
+        populateChildren(menulist, option.children, uniqueOptionStyles,
+                         selectedIndex, zoom, selectStyle,
+                         selectBackgroundSet, sheet, item, isDisabled, false,
+                         nthChildIndex);
     } 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;