Bug 467360, support buttons with child panels, r+sr=neil
authorNeil Deakin <neil@mozilla.com>
Tue, 20 Jan 2009 12:46:55 -0500
changeset 23975 31d411bd26bc291d010e9e4a079b6c3e1de5156d
parent 23974 f706be55329ee8b87af69793c3d75a4fe1fe322b
child 23976 2cda622c9bd9fa2f4a83c13b584d20bdf190c7a9
push id4820
push userneil@mozilla.com
push dateTue, 20 Jan 2009 17:47:59 +0000
treeherdermozilla-central@31d411bd26bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs467360
milestone1.9.2a1pre
Bug 467360, support buttons with child panels, r+sr=neil
layout/xul/base/public/nsXULPopupManager.h
layout/xul/base/src/nsXULPopupManager.cpp
toolkit/content/tests/widgets/Makefile.in
toolkit/content/tests/widgets/test_button.xul
toolkit/content/tests/widgets/test_panelfrommenu.xul
toolkit/content/widgets/button.xml
toolkit/content/widgets/toolbarbutton.xml
toolkit/content/xul.css
--- a/layout/xul/base/public/nsXULPopupManager.h
+++ b/layout/xul/base/public/nsXULPopupManager.h
@@ -205,32 +205,35 @@ public:
 };
 
 // this class is used for dispatching popupshowing events asynchronously.
 class nsXULPopupShowingEvent : public nsRunnable
 {
 public:
   nsXULPopupShowingEvent(nsIContent *aPopup,
                          nsIContent *aMenu,
+                         nsPopupType aPopupType,
                          PRBool aIsContextMenu,
                          PRBool aSelectFirstItem)
     : mPopup(aPopup),
       mMenu(aMenu),
+      mPopupType(aPopupType),
       mIsContextMenu(aIsContextMenu),
       mSelectFirstItem(aSelectFirstItem)
   {
     NS_ASSERTION(aPopup, "null popup supplied to nsXULPopupShowingEvent constructor");
     NS_ASSERTION(aMenu, "null menu supplied to nsXULPopupShowingEvent constructor");
   }
 
   NS_IMETHOD Run();
 
 private:
   nsCOMPtr<nsIContent> mPopup;
   nsCOMPtr<nsIContent> mMenu;
+  nsPopupType mPopupType;
   PRBool mIsContextMenu;
   PRBool mSelectFirstItem;
 };
 
 // this class is used for dispatching popuphiding events asynchronously.
 class nsXULPopupHidingEvent : public nsRunnable
 {
 public:
--- a/layout/xul/base/src/nsXULPopupManager.cpp
+++ b/layout/xul/base/src/nsXULPopupManager.cpp
@@ -416,17 +416,17 @@ nsXULPopupManager::ShowMenu(nsIContent *
     position.AssignLiteral("after_start");
   else
     position.AssignLiteral("end_before");
   popupFrame->InitializePopup(aMenu, position, 0, 0, PR_TRUE);
 
   if (aAsynchronous) {
     SetTriggerEvent(nsnull, nsnull);
     nsCOMPtr<nsIRunnable> event =
-      new nsXULPopupShowingEvent(popupFrame->GetContent(), aMenu,
+      new nsXULPopupShowingEvent(popupFrame->GetContent(), aMenu, popupFrame->PopupType(),
                                  parentIsContextMenu, aSelectFirstItem);
     NS_DispatchToCurrentThread(event);
   }
   else {
     nsCOMPtr<nsIContent> popupContent = popupFrame->GetContent();
     FirePopupShowingEvent(popupContent, aMenu,
                           popupFrame->PresContext(), popupFrame->PopupType(),
                           parentIsContextMenu, aSelectFirstItem);
@@ -2005,19 +2005,17 @@ GetPresContextFor(nsIContent* aContent)
 }
 
 NS_IMETHODIMP
 nsXULPopupShowingEvent::Run()
 {
   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
   nsPresContext* context = GetPresContextFor(mPopup);
   if (pm && context) {
-    // the popupshowing event should only be fired asynchronously
-    // for menus, so just use ePopupTypeMenu as the type
-    pm->FirePopupShowingEvent(mPopup, mMenu, context, ePopupTypeMenu,
+    pm->FirePopupShowingEvent(mPopup, mMenu, context, mPopupType,
                               mIsContextMenu, mSelectFirstItem);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULPopupHidingEvent::Run()
--- a/toolkit/content/tests/widgets/Makefile.in
+++ b/toolkit/content/tests/widgets/Makefile.in
@@ -44,16 +44,17 @@ relativesrcdir  = toolkit/content/tests/
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = 	test_bug360220.xul \
 		test_bug359754.xul \
 		test_bug365773.xul \
 		test_bug382990.xul \
 		test_bug457632.xul \
+		test_button.xul \
 		test_closemenu_attribute.xul \
 		test_colorpicker_popup.xul \
 		test_deck.xul \
 		test_menulist_keynav.xul \
 		test_menulist_null_value.xul \
 		test_popup_coords.xul \
 		test_popup_recreate.xul \
 		test_popup_button.xul \
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/widgets/test_button.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+  XUL Widget Test for button
+  -->
+<window title="Button Test"
+        onload="setTimeout(test_button, 0);"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" 
+          src="/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="/tests/SimpleTest/SimpleTest.js"></script>      
+  <script type="application/javascript"
+          src="/tests/SimpleTest/EventUtils.js"></script>      
+
+<button id="one" label="One"/>
+<button id="two" label="Two"/>
+<hbox>
+  <button id="three" label="Three" open="true"/>
+</hbox>
+<hbox>
+  <button id="four" type="menu" label="Four"/>
+  <button id="five" type="panel" label="Five"/>
+  <button id="six" label="Six"/>
+</hbox>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function test_button()
+{
+  synthesizeMouseExpectEvent($("one"), 2, 2, {}, $("one"), "command", "button press");
+  $("one").focus();
+  synthesizeKeyExpectEvent("VK_SPACE", { }, $("one"), "command", "key press");
+  $("two").disabled = true;
+  synthesizeMouseExpectEvent($("two"), 2, 2, {}, $("two"), "!command", "button press command when disabled");
+  synthesizeMouseExpectEvent($("two"), 2, 2, {}, $("two"), "click", "button press click when disabled");
+
+  $("one").focus();
+  synthesizeKey("VK_DOWN", { });
+  is(document.activeElement, $("three"), "key cursor down on button");
+
+  synthesizeKey("VK_RIGHT", { });
+  is(document.activeElement, $("four"), "key cursor right on button");
+  synthesizeKey("VK_DOWN", { });
+  is(document.activeElement, $("four"), "key cursor down on menu button");
+  $("five").focus();
+  synthesizeKey("VK_DOWN", { });
+  is(document.activeElement, $("five"), "key cursor down on panel button");
+
+  $("three").focus();
+  synthesizeKey("VK_UP", { });
+  is(document.activeElement, $("one"), "key cursor up on button");
+
+  $("two").focus();
+  is(document.activeElement, $("one"), "focus disabled button");
+
+  SimpleTest.finish();
+}
+
+]]>
+</script>
+
+</window>
--- a/toolkit/content/tests/widgets/test_panelfrommenu.xul
+++ b/toolkit/content/tests/widgets/test_panelfrommenu.xul
@@ -14,30 +14,46 @@
 <!--
   This test does the following:
    1. Opens the menu, causing the popupshown event to fire, which will call menuOpened.
    2. Keyboard events are fired to cause the first item on the menu to be executed.
    3. The command event handler for the first menuitem opens the panel.
    4. As a menuitem was executed, the menu will roll up, hiding it.
    5. The popuphidden event for the menu calls menuClosed which tests the popup states.
    6. The panelOpened function tests the popup states again and hides the popup.
-   7. Once the panel's popuphidden event fires, the tests are complete.
+   7. Once the panel's popuphidden event fires, tests are performed to see if
+      panels inside buttons and toolbarbuttons work. Each is opened and the closed.
   -->
 
 <menu id="menu" onpopupshown="menuOpened()" onpopuphidden="menuClosed();">
   <menupopup>
     <menuitem id="i1" label="One" oncommand="$('panel').openPopup($('menu'), 'after_start');"/>
     <menuitem id="i2" label="Two"/>
   </menupopup>
 </menu>
 
-<panel id="panel" onpopupshown="panelOpened()" onpopuphidden="SimpleTest.finish()">
+<panel id="panel" onpopupshown="panelOpened()"
+                  onpopuphidden="$('button').focus(); $('button').open = true">
   <textbox/>
 </panel>
 
+<button id="button" type="panel" label="Button">
+  <panel onpopupshown="panelOnButtonOpened(this)"
+         onpopuphidden="$('tbutton').open = true;">
+    <button label="OK" oncommand="this.parentNode.parentNode.open = false"/>
+  </panel>
+</button>
+
+<toolbarbutton id="tbutton" type="panel" label="Toolbarbutton">
+  <panel onpopupshown="panelOnToolbarbuttonOpened(this)"
+         onpopuphidden="SimpleTest.finish()">
+    <textbox/>
+  </panel>
+</toolbarbutton>
+
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function runTests()
 {
   var menu = $("menu");
@@ -60,16 +76,35 @@ function menuClosed()
 
 function panelOpened()
 {
   is($("panel").state, "open", "panel is open");
   is($("menu").firstChild.state, "closed", "menu is closed");
   $("panel").hidePopup();
 }
 
+function panelOnButtonOpened(panel)
+{
+  is(panel.state, 'open', 'button panel is open');
+  is(document.activeElement, document.documentElement, "focus blurred on panel from button open");
+  synthesizeKey("VK_DOWN", { });
+  is(document.activeElement, document.documentElement, "focus not modified on cursor down from button");
+  panel.firstChild.doCommand()
+}
+
+function panelOnToolbarbuttonOpened(panel)
+{
+  is(panel.state, 'open', 'toolbarbutton panel is open');
+  is(document.activeElement, document.documentElement, "focus blurred on panel from toolbarbutton open");
+  panel.firstChild.focus();
+  synthesizeKey("VK_DOWN", { });
+  is(document.activeElement, panel.firstChild.inputField, "focus not modified on cursor down from toolbarbutton");
+  panel.parentNode.open = false;
+}
+
 ]]>
 </script>
 
 <body xmlns="http://www.w3.org/1999/xhtml">
 <p id="display">
 </p>
 <div id="content" style="display: none">
 </div>
--- a/toolkit/content/widgets/button.xml
+++ b/toolkit/content/widgets/button.xml
@@ -137,38 +137,43 @@
            because any external oncommand handlers might get called before ours,
            and then they would see the incorrect value of checked. Additionally
            a command attribute would redirect the command events anyway.-->
       <handler event="click" button="0" action="this._handleClick();"/>
       <handler event="keypress" key=" " action="this._handleClick();"/>
 
       <handler event="keypress">
       <![CDATA[
-        if (event.keyCode == KeyEvent.DOM_VK_UP ||
-            (event.keyCode == KeyEvent.DOM_VK_LEFT &&
-              document.defaultView.getComputedStyle(this.parentNode, "")
-                      .direction == "ltr") ||
-            (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
-              document.defaultView.getComputedStyle(this.parentNode, "")
-                      .direction == "rtl")) {
-          event.preventDefault();
-          window.document.commandDispatcher.rewindFocus();
-          return;
-        }
+        if (this.boxObject instanceof Components.interfaces.nsIMenuBoxObject) {
+          if (this.open)
+            return;
+        } else {
+          if (event.keyCode == KeyEvent.DOM_VK_UP ||
+              (event.keyCode == KeyEvent.DOM_VK_LEFT &&
+                document.defaultView.getComputedStyle(this.parentNode, "")
+                        .direction == "ltr") ||
+              (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+                document.defaultView.getComputedStyle(this.parentNode, "")
+                        .direction == "rtl")) {
+            event.preventDefault();
+            window.document.commandDispatcher.rewindFocus();
+            return;
+          }
 
-        if (event.keyCode == KeyEvent.DOM_VK_DOWN ||
-            (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
-              document.defaultView.getComputedStyle(this.parentNode, "")
-                      .direction == "ltr") ||
-            (event.keyCode == KeyEvent.DOM_VK_LEFT &&
-              document.defaultView.getComputedStyle(this.parentNode, "")
-                      .direction == "rtl")) {
-          event.preventDefault();
-          window.document.commandDispatcher.advanceFocus();
-          return;
+          if (event.keyCode == KeyEvent.DOM_VK_DOWN ||
+              (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
+                document.defaultView.getComputedStyle(this.parentNode, "")
+                        .direction == "ltr") ||
+              (event.keyCode == KeyEvent.DOM_VK_LEFT &&
+                document.defaultView.getComputedStyle(this.parentNode, "")
+                        .direction == "rtl")) {
+            event.preventDefault();
+            window.document.commandDispatcher.advanceFocus();
+            return;
+          }
         }
 
         if (event.keyCode || event.charCode <= 32 || event.altKey || 
             event.ctrlKey || event.metaKey)
           return;  // No printable char pressed, not a potential accesskey
 
         // Possible accesskey pressed
         var charPressedLower = String.fromCharCode(event.charCode).toLowerCase();
@@ -201,17 +206,17 @@
 
   <binding id="button" display="xul:button"
            extends="chrome://global/content/bindings/button.xml#button-base">
     <resources>
       <stylesheet src="chrome://global/skin/button.css"/>
     </resources>
 
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
                 align="center" pack="center" flex="1" anonid="button-box">
         <children>
           <xul:image class="button-icon" xbl:inherits="src=image"/>
           <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
         </children>
       </xul:hbox>
     </content>
@@ -246,17 +251,17 @@
       </method>
       <constructor>this._init();</constructor>
     </implementation>
   </binding>
 
   <binding id="menu" display="xul:menu"
            extends="chrome://global/content/bindings/button.xml#button">
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:hbox class="box-inherit button-box" xbl:inherits="align,dir,pack,orient"
                 align="center" pack="center" flex="1">
         <children>
           <xul:hbox class="box-inherit" xbl:inherits="align,dir,pack,orient"
                     align="center" pack="center" flex="1">
             <xul:image class="button-icon" xbl:inherits="src=image"/>
             <xul:label class="button-text" xbl:inherits="value=label,accesskey,crop"/>
           </xul:hbox>
@@ -357,17 +362,17 @@
 
   <binding id="menu-button" display="xul:menu"
            extends="chrome://global/content/bindings/button.xml#menu-button-base">
     <resources>
       <stylesheet src="chrome://global/skin/button.css"/>
     </resources>
 
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:button class="box-inherit button-menubutton-button"
                   anonid="button" flex="1" allowevents="true"
                   xbl:inherits="disabled,crop,image,label,accessKey,command,
                                 buttonover,buttondown,align,dir,pack,orient">
         <children/>
       </xul:button>
       <xul:dropmarker class="button-menubutton-dropmarker" xbl:inherits="open,disabled,label"/>
     </content>
--- a/toolkit/content/widgets/toolbarbutton.xml
+++ b/toolkit/content/widgets/toolbarbutton.xml
@@ -7,17 +7,17 @@
 
   <binding id="toolbarbutton" display="xul:button"
            extends="chrome://global/content/bindings/button.xml#button-base">
     <resources>
       <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
     </resources>
     
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,toolbarmode,buttonstyle"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,toolbarmode,buttonstyle"/>
     </content>
     
     <implementation implements="nsIAccessibleProvider">
       <property name="accessibleType" readonly="true">
         <getter>
@@ -25,28 +25,28 @@
         </getter>
       </property>
     </implementation>
   </binding>
 
   <binding id="menu" display="xul:menu" 
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,toolbarmode,buttonstyle"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,dragover-top,toolbarmode,buttonstyle"/>
       <xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
     </content>
   </binding>
   
   <binding id="menu-vertical" display="xul:menu"
            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:hbox flex="1" align="center">
         <xul:vbox flex="1" align="center">
           <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,toolbarmode,buttonstyle"/>
           <xul:label class="toolbarbutton-text" crop="right" flex="1"
                     xbl:inherits="value=label,accesskey,crop,dragover-top,toolbarmode,buttonstyle"/>
         </xul:vbox>
         <xul:dropmarker type="menu" class="toolbarbutton-menu-dropmarker" xbl:inherits="disabled,label"/>
       </xul:hbox>
@@ -55,17 +55,17 @@
   
   <binding id="menu-button" display="xul:menu" 
            extends="chrome://global/content/bindings/button.xml#menu-button-base">
     <resources>
       <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
     </resources>
 
     <content>
-      <children includes="observes|template|menupopup|tooltip"/>
+      <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:toolbarbutton class="box-inherit toolbarbutton-menubutton-button"
                          anonid="button" flex="1" allowevents="true"
                          xbl:inherits="disabled,crop,image,label,accesskey,command,
                                        align,dir,pack,orient,toolbarmode,buttonstyle"/>
       <xul:dropmarker type="menu-button" class="toolbarbutton-menubutton-dropmarker"
                       xbl:inherits="align,dir,pack,orient,disabled,toolbarmode,buttonstyle,label"/>
     </content>
   </binding>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -117,31 +117,31 @@ button[default="true"] {
   -moz-binding: url("chrome://global/content/bindings/button.xml#button-periodic-redraw");
 }
 %endif
 
 button[type="repeat"] {
   -moz-binding: url("chrome://global/content/bindings/button.xml#button-repeat");
 }
 
-button[type="menu"] {
+button[type="menu"], button[type="panel"] {
   -moz-binding: url("chrome://global/content/bindings/button.xml#menu");
 }
 
 button[type="menu-button"] {
   -moz-binding: url("chrome://global/content/bindings/button.xml#menu-button");
 }
 
 /********** toolbarbutton **********/
 
 toolbarbutton {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton");
 }
 
-toolbarbutton[type="menu"] {
+toolbarbutton[type="menu"], toolbarbutton[type="panel"] {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu");
 }
 
 toolbarbutton[type="menu-button"] {
   -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#menu-button");
 }
 
 /******** browser, editor, iframe ********/