Bug 1347329 - Make sure that the nth-child index gets updated when recursing so that colors get applied correctly when optgroups are used. r?mconley draft
authorJared Wein <jwein@mozilla.com>
Tue, 14 Mar 2017 19:06:57 -0400
changeset 498540 daad56332d93cfb1877f9c41841c1ad2298ac863
parent 498506 663fda14bd2e6bbf9c53e4a0affbe3295137beb9
child 498541 c887f8244194281cd711ed44540b471fc5250178
child 498550 710e989852cb924c9ffd166d76af42258db4cb3d
push id49236
push userbmo:jaws@mozilla.com
push dateTue, 14 Mar 2017 23:31:13 +0000
reviewersmconley
bugs1347329
milestone55.0a1
Bug 1347329 - Make sure that the nth-child index gets updated when recursing so that colors get applied correctly when optgroups are used. r?mconley This problem only occurs when optgroups are involved since they introduce recursion in to the menu-building code and we weren't correctly passing state through the recursion. MozReview-Commit-ID: 514zcjgXbIY
browser/base/content/test/general/browser_selectpopup.js
toolkit/modules/SelectParentHelper.jsm
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -136,16 +136,40 @@ const GENERIC_OPTION_STYLED_AS_IMPORTANT
 
 const TRANSLUCENT_SELECT_BECOMES_OPAQUE =
   "<html><head>" +
   "<body><select id='one' style='background-color: rgba(255,255,255,.55);'>" +
   '  <option value="One">{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
   '  <option value="Two" selected="true">{"end": "true"}</option>' +
   "</select></body></html>";
 
+const DISABLED_OPTGROUP_AND_OPTIONS =
+  "<html><head>" +
+  "<body><select id='one'>" +
+  "  <optgroup label='{\"color\": \"rgb(0, 0, 0)\", \"backgroundColor\": \"buttonface\"}'>" +
+  '    <option disabled="">{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option disabled="">{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "rgb(0, 0, 0)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  "  </optgroup>" +
+  "  <optgroup label='{\"color\": \"GrayText\", \"backgroundColor\": \"buttonface\"}' disabled=''>" +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '    <option>{"color": "GrayText", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  "  </optgroup>" +
+  '  <option value="Two" selected="true">{"end": "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") {
       mousePromise = BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser);
     } else {
@@ -200,17 +224,25 @@ function getSystemColor(color) {
   let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
   textarea.style.color = color;
   return getComputedStyle(textarea).color;
 }
 
 function testOptionColors(index, item, menulist) {
   // The label contains a JSON string of the expected colors for
   // `color` and `background-color`.
-  let expected = JSON.parse(item.label);
+  let expected;
+  try {
+    expected = JSON.parse(item.label);
+  } catch (ex) {
+    info(`Failed parsing: ${item.label}`);
+    info(`Failed parsing element with outerHTML: ${item.outerHTML}`);
+    ok(false, ex.message);
+    return;
+  }
 
   for (let color of Object.keys(expected)) {
     if (color.toLowerCase().includes("color") &&
         !expected[color].startsWith("rgb")) {
       expected[color] = getSystemColor(expected[color]);
     }
   }
 
@@ -948,8 +980,18 @@ add_task(function* test_translucent_sele
   // The popup is requested to show a translucent background
   // but we force the alpha channel to 1 on the popup.
   let options = {
     selectColor: "rgb(0, 0, 0)",
     selectBgColor: "rgb(255, 255, 255)"
   };
   yield testSelectColors(TRANSLUCENT_SELECT_BECOMES_OPAQUE, 2, options);
 });
+
+add_task(function* test_disabled_optgroup_and_options() {
+  // The colors used by this test are platform-specific.
+  if (AppConstants.platform != "win") {
+    return;
+  }
+
+  yield testSelectColors(DISABLED_OPTGROUP_AND_OPTIONS, 17,
+                         {skipSelectColorTest: true});
+});
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -270,20 +270,19 @@ function populateChildren(menulist, opti
 
     if (customStylingEnabled &&
         option.color &&
         option.color != uaColor) {
       ruleBody += `color: ${option.color};`;
     }
 
     if (ruleBody) {
-      sheet.insertRule(`${item.localName}:nth-child(${nthChildIndex}):not([_moz-menuactive="true"]) {
+      sheet.insertRule(`menupopup > :nth-child(${nthChildIndex}):not([_moz-menuactive="true"]) {
         ${ruleBody}
       }`, 0);
-
       item.setAttribute("customoptionstyling", "true");
     } else {
       item.removeAttribute("customoptionstyling");
     }
 
     element.appendChild(item);
     nthChildIndex++;
 
@@ -292,17 +291,17 @@ function populateChildren(menulist, opti
     if (isDisabled) {
       item.setAttribute("disabled", "true");
     }
 
     if (isOptGroup) {
       nthChildIndex =
         populateChildren(menulist, option.children, selectedIndex, zoom,
                          uaBackgroundColor, uaColor, sheet,
-                         item, isDisabled, adjustedTextSize, false);
+                         item, isDisabled, adjustedTextSize, 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;