Bug 1047713, e10s optgroup support, r=neil,felipe
authorNeil Deakin <neil@mozilla.com>
Fri, 05 Jun 2015 08:33:28 -0400
changeset 247393 9c8afe52c600eea9eeb2633bfbb13c2d5a0efe47
parent 247392 44986f66ee4b590f1a5d7781828477f06ba400b6
child 247394 45f0d3729d5fabd69476c1e916d10425867f8c87
push id28864
push userkwierso@gmail.com
push dateFri, 05 Jun 2015 21:49:37 +0000
treeherdermozilla-central@97a39c939c51 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, felipe
bugs1047713
milestone41.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 1047713, e10s optgroup support, r=neil,felipe
browser/base/content/browser.css
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_selectpopup.js
toolkit/content/widgets/menu.xml
toolkit/content/xul.css
toolkit/modules/SelectParentHelper.jsm
toolkit/themes/linux/global/menu.css
toolkit/themes/osx/global/menu.css
toolkit/themes/windows/global/menu.css
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1017,24 +1017,19 @@ chatbox:-moz-full-screen-ancestor > .cha
 /* Combobox dropdown renderer */
 #ContentSelectDropdown > menupopup {
   max-height: 350px;
   /* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with
    * the dropdown arrow on the dropdown widget. If a menuitem is RTL, its style will be set accordingly */
   direction: ltr;
 }
 
-.contentSelectDropdown-optgroup {
-  font-weight: bold;
-  /* color: menutext used to overwrite the disabled color */
-  color: menutext;
-}
-
-.contentSelectDropdown-ingroup {
-  -moz-margin-start: 2em;
+/* Indent options in optgroups */
+.contentSelectDropdown-ingroup .menu-iconic-text {
+  -moz-padding-start: 2em;
 }
 
 /* Give this menupopup an arrow panel styling */
 #BMB_bookmarksPopup {
   -moz-appearance: none;
   -moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-arrow");
   background: transparent;
   border: none;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -375,16 +375,18 @@ skip-if = buildapp == 'mulet' || e10s # 
 skip-if = buildapp == 'mulet'
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_searchSuggestionUI.js]
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
   searchSuggestionUI.js
+[browser_selectpopup.js]
+run-if = e10s
 [browser_selectTabAtIndex.js]
 [browser_ssl_error_reports.js]
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
 [browser_syncui.js]
 skip-if = e10s # Bug 1137087 - browser_tabopen_reflows.js fails if this was previously run with e10s
 [browser_tabDrop.js]
 skip-if = buildapp == 'mulet' || e10s
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks that a <select> with an <optgroup> opens and can be navigated
+// in a child process. This is different than single-process as a <menulist> is used
+// to implement the dropdown list.
+
+const PAGECONTENT =
+  "<html><body onload='document.body.firstChild.focus()'><select>" +
+  "  <optgroup label='First Group'>" +
+  "    <option value=One>One" +
+  "    <option value=Two>Two" +
+  "  </optgroup>" +
+  "  <option value=Three>Three" +
+  "  <optgroup label='Second Group'>" +
+  "    <option value=Four>Four" +
+  "    <option value=Five>Five" +
+  "  </optgroup>" +
+  "</body></html>";
+
+function openSelectPopup(selectPopup)
+{
+  return new Promise((resolve, reject) => {
+    selectPopup.addEventListener("popupshown", function popupListener(event) {
+      selectPopup.removeEventListener("popupshown", popupListener, false)
+      resolve();
+    }, false);
+    setTimeout(() => EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }), 1500);
+  });
+}
+
+function hideSelectPopup(selectPopup)
+{
+  return new Promise((resolve, reject) => {
+    selectPopup.addEventListener("popuphidden", function popupListener(event) {
+      selectPopup.removeEventListener("popuphidden", popupListener, false)
+      resolve();
+    }, false);
+    EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" });
+  });
+}
+
+add_task(function*() {
+  let tab = gBrowser.selectedTab = gBrowser.addTab();
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield promiseTabLoadEvent(tab, "data:text/html," + escape(PAGECONTENT));
+
+  yield SimpleTest.promiseFocus(browser.contentWindow);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  yield openSelectPopup(selectPopup);
+
+  is(menulist.selectedIndex, 1, "Initial selection");
+  is(selectPopup.firstChild.localName, "menucaption", "optgroup is caption");
+  is(selectPopup.firstChild.getAttribute("label"), "First Group", "optgroup label");
+  is(selectPopup.childNodes[1].localName, "menuitem", "option is menuitem");
+  is(selectPopup.childNodes[1].getAttribute("label"), "One", "option label");
+
+  EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+  is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(2), "Select item 2");
+
+  EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+  is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3");
+
+  EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+  is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(5), "Skip optgroup header and select item 4");
+
+  yield hideSelectPopup(selectPopup);
+
+  is(menulist.selectedIndex, 5, "Item 4 still selected selectedIndex");
+
+  gBrowser.removeCurrentTab();
+});
+
--- a/toolkit/content/widgets/menu.xml
+++ b/toolkit/content/widgets/menu.xml
@@ -182,16 +182,22 @@
     <content>
       <xul:label class="menu-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
       <xul:hbox class="menu-accel-container" anonid="accel">
         <xul:label class="menu-accel" xbl:inherits="value=acceltext"/>
       </xul:hbox>
     </content>
   </binding>
 
+  <binding id="menucaption" extends="chrome://global/content/bindings/menu.xml#menu-base">
+    <content>
+      <xul:label class="menu-text" xbl:inherits="value=label,crop" crop="right"/>
+    </content>
+  </binding>
+
   <binding id="menu-menubar"
            extends="chrome://global/content/bindings/menu.xml#menu-base">
     <content>
       <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
       <children includes="menupopup"/>
     </content>
   </binding>
 
@@ -222,16 +228,26 @@
       <xul:hbox class="menu-iconic-left" align="center" pack="center"
                 xbl:inherits="selected,disabled,checked">
         <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
       </xul:hbox>
       <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,accesskey,crop" crop="right"/>
     </content>
   </binding>
 
+  <binding id="menucaption-inmenulist" extends="chrome://global/content/bindings/menu.xml#menucaption">
+    <content>
+      <xul:hbox class="menu-iconic-left" align="center" pack="center"
+                xbl:inherits="selected,disabled,checked">
+        <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
+      </xul:hbox>
+      <xul:label class="menu-iconic-text" flex="1" xbl:inherits="value=label,crop" crop="right"/>
+    </content>
+  </binding>
+
   <binding id="menuitem-iconic-desc-noaccel" extends="chrome://global/content/bindings/menu.xml#menuitem">
     <content>
       <xul:hbox class="menu-iconic-left" align="center" pack="center"
                 xbl:inherits="selected,disabled,checked">
         <xul:image class="menu-iconic-icon" xbl:inherits="src=image,validate,src"/>
       </xul:hbox>
       <xul:label class="menu-iconic-text" xbl:inherits="value=label,accesskey,crop" crop="right" flex="1"/>
       <xul:label class="menu-iconic-text menu-description" xbl:inherits="value=description" crop="right" flex="10000"/>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -377,16 +377,20 @@ menuitem[type="checkbox"],
 menuitem[type="radio"] {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
 }
 
 menuitem.menuitem-non-iconic {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menubutton-item");
 }
 
+menucaption {
+  -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption");
+}
+
 .menu-text {
   -moz-box-flex: 1;
 }
 
 %ifdef MOZ_WIDGET_GTK
 /********* detection of system setting to use icons in menus ***********/
 @media not all and (-moz-images-in-menus) {
   .menu-iconic-left {
@@ -948,16 +952,20 @@ menulist[editable="true"] {
 menulist[type="description"] {
   -moz-binding: url("chrome://global/content/bindings/menulist.xml#menulist-description");
 }
 
 menulist > menupopup > menuitem {
   -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic-noaccel");
 }
 
+menulist > menupopup > menucaption {
+  -moz-binding: url("chrome://global/content/bindings/menu.xml#menucaption-inmenulist");
+}
+
 dropmarker {
   -moz-binding: url("chrome://global/content/bindings/general.xml#dropmarker");
 }
 
 /********** splitter **********/
 
 splitter {
   -moz-binding: url("chrome://global/content/bindings/splitter.xml#splitter");
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -60,42 +60,43 @@ this.SelectParentHelper = {
 
   _unregisterListeners: function(popup) {
     popup.removeEventListener("command", this);
     popup.removeEventListener("popuphidden", this);
   },
 
 };
 
-function populateChildren(menulist, options, selectedIndex, startIndex = 0, isGroup = false) {
+function populateChildren(menulist, options, selectedIndex, startIndex = 0, isInGroup = false) {
   let index = startIndex;
   let element = menulist.menupopup;
 
   for (let option of options) {
-    let item = element.ownerDocument.createElement("menuitem");
+    let isOptGroup = (option.tagName == 'OPTGROUP');
+    let item = element.ownerDocument.createElement(isOptGroup ? "menucaption" : "menuitem");
+
     item.setAttribute("label", option.textContent);
     item.style.direction = option.textDirection;
 
     element.appendChild(item);
 
-    if (option.children.length > 0) {
-      item.classList.add("contentSelectDropdown-optgroup");
-      item.setAttribute("disabled", "true");
+    if (isOptGroup) {
       index = populateChildren(menulist, option.children, selectedIndex, index, true);
     } else {
       if (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;
       }
+
       item.setAttribute("value", index++);
 
-      if (isGroup) {
+      if (isInGroup) {
         item.classList.add("contentSelectDropdown-ingroup")
       }
     }
   }
 
   return index;
 }
--- a/toolkit/themes/linux/global/menu.css
+++ b/toolkit/themes/linux/global/menu.css
@@ -7,16 +7,17 @@
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* ::::: menu/menuitem ::::: */
 
 menu,
 menuitem,
+menucaption,
 .splitmenu-menuitem {
   -moz-appearance: menuitem;
   -moz-box-align: center;
   max-width: 42em;
   color: MenuText;
   font: menu;
   list-style-image: none;
   -moz-image-region: auto;
@@ -63,16 +64,17 @@ menubar > menu[open] {
 }
 menuitem.spell-suggestion {
   font-weight:bold;
 }
 
 /* ::::: menu/menuitems in menulist popups ::::: */
 
 menulist > menupopup > menuitem,
+menulist > menupopup > menucaption,
 menulist > menupopup > menu {
   padding: 1px 5px;
   max-width: none;
   font: message-box;
 }
 
 /* ..... internal content .... */
 
@@ -87,16 +89,22 @@ menulist > menupopup > menu {
 }
 
 .menu-text {
   /* This is (18 + the size of end-padding on .menu-iconic-left)px */
   -moz-margin-start: 21px !important;
   font-weight: inherit;
 }
 
+menucaption > .menu-text,
+menucaption > .menu-iconic-text {
+  -moz-margin-start: 0 !important;
+  font-weight: bold;
+}
+
 .menu-description {
   font-style: italic;
   color: GrayText;
   -moz-margin-start: 1ex !important;
 }
 
 .menu-accel,
 .menu-iconic-accel {
@@ -144,16 +152,17 @@ menulist > menupopup > menu {
 
 .menubar-text {
   margin: 0 1px !important;
   color: inherit;
 }
 
 
 menulist > menupopup > menuitem > .menu-iconic-left,
+menulist > menupopup > menucaption > .menu-iconic-left,
 menulist > menupopup > menu > .menu-iconic-left {
   display: none;
 }
 
 /* ::::: checkbox menuitem ::::: */
 
 menuitem[checked="true"] {
   -moz-appearance: checkmenuitem !important;
--- a/toolkit/themes/osx/global/menu.css
+++ b/toolkit/themes/osx/global/menu.css
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 menu,
-menuitem {
+menuitem,
+menucaption {
   -moz-appearance: menuitem;
   -moz-box-align: center;
   color: MenuText;
   font: -moz-pull-down-menu;
   list-style-image: none;
   -moz-image-region: auto;
   padding: 0 21px 2px;
 }
@@ -31,16 +32,21 @@ menuitem[_moz-menuactive="true"][disable
 }
 
 .menu-text,
 .menu-iconic-text {
   font-weight: inherit;
   color: inherit;
 }
 
+menucaption > .menu-text,
+menucaption > .menu-iconic-text {
+  font-weight: bold;
+}
+
 .menu-description {
   font-style: italic;
   color: -moz-mac-menutextdisable;
   -moz-margin-start: 1ex !important;
 }
 
 .menu-iconic-icon {
   height: 16px;
@@ -122,42 +128,45 @@ menubar > menu[_moz-menuactive="true"][o
 .menubar-text {
   margin: 0 1px !important;
   color: inherit;
 }
 
 /* ::::: menu/menuitems in popups ::::: */
 
 menupopup > menu,
-menupopup > menuitem {
+menupopup > menuitem,
+menupopup > menucaption {
   max-width: 42em;
 }
 
 menu[_moz-menuactive="true"],
 menuitem[_moz-menuactive="true"] {
   color: -moz-mac-menutextselect;
   background-color: Highlight;
 }
 
 /* ::::: menu/menuitems in menulist popups ::::: */
 
 menulist > menupopup > menuitem,
+menulist > menupopup > menucaption,
 menulist > menupopup > menu {
   max-width: none;
   font: inherit;
   color: -moz-FieldText;
 }
 
 /* ::::: menuitems in editable menulist popups ::::: */
 
-menulist[editable="true"] > menupopup > menuitem {
+menulist[editable="true"] > menupopup > menuitem,
+menulist[editable="true"] > menupopup > menucaption {
   -moz-appearance: none;
 }
 
-menulist[editable="true"] > menupopup > menuitem > .menu-iconic-left {
+menulist[editable="true"] > menupopup > :moz-any(menuitem, menucaption) > .menu-iconic-left
   display: none;
 }
 
 /* ::::: checked menuitems ::::: */
 
 :not(menulist) > menupopup > menuitem[checked="true"],
 :not(menulist) > menupopup > menuitem[selected="true"] {
   -moz-appearance: checkmenuitem;
--- a/toolkit/themes/windows/global/menu.css
+++ b/toolkit/themes/windows/global/menu.css
@@ -7,16 +7,17 @@
   ======================================================================= */
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 /* ::::: menu/menuitem ::::: */
 
 menu,
 menuitem,
+menucaption,
 .splitmenu-menuitem {
   -moz-appearance: menuitem;
   -moz-box-align: center;
   color: MenuText;
   font: menu;
   list-style-image: none;
   -moz-image-region: auto;
 }
@@ -67,16 +68,22 @@ menuitem.spell-suggestion {
 
 .menu-text,
 .menu-iconic-text {
   font-weight: inherit;
   -moz-margin-start: 2px !important;
   -moz-padding-end: 2px;
 }
 
+menucaption > .menu-text,
+menucaption > .menu-iconic-text {
+  font-weight: bold;
+  -moz-padding-start: 0 !important;
+}
+
 .menu-description {
   font-style: italic;
   color: GrayText;
   -moz-margin-start: 1ex !important;
 }
 
 .menu-accel,
 .menu-iconic-accel {
@@ -168,30 +175,32 @@ menubar > menu:-moz-window-inactive {
 .menubar-text {
   margin: 1px 6px 2px 6px !important;
   color: inherit;
 }
 
 /* ::::: menu/menuitems in popups ::::: */
 
 menupopup > menu,
-menupopup > menuitem {
+menupopup > menuitem,
+menupopup > menucaption {
   max-width: 42em;
 }
 
 menu[_moz-menuactive="true"],
 menuitem[_moz-menuactive="true"],
 .splitmenu-menuitem[_moz-menuactive="true"] {
   background-color: -moz-menuhover;
   color: -moz-menuhovertext;
 }
 
 /* ::::: menu/menuitems in menulist popups ::::: */
 
 menulist > menupopup > menuitem,
+menulist > menupopup > menucaption,
 menulist > menupopup > menu {
   -moz-appearance: none !important;
   border: 1px solid transparent;
   -moz-padding-start: 5px;
   -moz-padding-end: 5px;
   max-width: none;
   font: message-box;
   color: -moz-FieldText;
@@ -199,22 +208,24 @@ menulist > menupopup > menu {
 
 menulist > menupopup > menuitem[_moz-menuactive="true"],
 menulist > menupopup > menu[_moz-menuactive="true"] {
   background-color: highlight;
   color: highlighttext;
 }
 
 menulist > menupopup > menuitem > .menu-iconic-left,
+menulist > menupopup > menucaption > .menu-iconic-left,
 menulist > menupopup > menu > .menu-iconic-left {
   display: none;
   padding-top: 0px;
 }
 
 menulist > menupopup > menuitem > label,
+menulist > menupopup > menucaption > label,
 menulist > menupopup > menu > label {
   padding-top: 0px;
   padding-bottom: 0px;
 }
 
 menulist:-moz-focusring > menupopup > menuitem[_moz-menuactive="true"] {
   border: 1px dotted #F5DB95;
 }
@@ -223,17 +234,17 @@ menulist > menupopup > menuitem[_moz-men
   color: GrayText;
 }
 
 menulist > menupopup > menuitem[disabled="true"]:not([_moz-menuactive="true"]):-moz-system-metric(windows-classic) {
   color: GrayText;
   text-shadow: none;
 }
 
-menulist > menupopup > menuitem:not(.menuitem-iconic) > .menu-iconic-text {
+menulist > menupopup > :moz-any(menuitem, menucaption):not(.menuitem-iconic) > .menu-iconic-text,
   margin: 0 !important;
 }
 
 /* ::::: checkbox and radio menuitems ::::: */
 
 menuitem[type="checkbox"],
 menuitem[checked="true"] {
   -moz-appearance: checkmenuitem;