Bug 126189, pass modifiers on to command events in menus when enter was pressed, r=neil
authorNeil Deakin <neil@mozilla.com>
Wed, 13 Apr 2011 13:53:24 -0400
changeset 68061 fdcb15cbbaed2954d32a427bb0f56a7882a55330
parent 68060 3de0857498634bb9b2b4c7027cd502b7084a664b
child 68062 77a7b2f27c5874093b12b22ba9f5e487084898ea
push idunknown
push userunknown
push dateunknown
reviewersneil
bugs126189
milestone6.0a1
Bug 126189, pass modifiers on to command events in menus when enter was pressed, r=neil
layout/xul/base/src/nsMenuBarFrame.cpp
layout/xul/base/src/nsMenuBarFrame.h
layout/xul/base/src/nsMenuFrame.cpp
layout/xul/base/src/nsMenuFrame.h
layout/xul/base/src/nsMenuPopupFrame.cpp
layout/xul/base/src/nsMenuPopupFrame.h
layout/xul/base/src/nsXULPopupManager.cpp
toolkit/content/tests/widgets/test_menulist_keynav.xul
--- a/layout/xul/base/src/nsMenuBarFrame.cpp
+++ b/layout/xul/base/src/nsMenuBarFrame.cpp
@@ -409,23 +409,23 @@ nsMenuBarFrame::ChangeMenuItem(nsMenuFra
   // use an event so that hiding and showing can be done synchronously, which
   // avoids flickering
   nsCOMPtr<nsIRunnable> event =
     new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem);
   return NS_DispatchToCurrentThread(event);
 }
 
 nsMenuFrame*
-nsMenuBarFrame::Enter()
+nsMenuBarFrame::Enter(nsGUIEvent* aEvent)
 {
   if (!mCurrentMenu)
     return nsnull;
 
   if (mCurrentMenu->IsOpen())
-    return mCurrentMenu->Enter();
+    return mCurrentMenu->Enter(aEvent);
 
   return mCurrentMenu;
 }
 
 PRBool
 nsMenuBarFrame::MenuClosed()
 {
   SetActive(PR_FALSE);
--- a/layout/xul/base/src/nsMenuBarFrame.h
+++ b/layout/xul/base/src/nsMenuBarFrame.h
@@ -107,17 +107,17 @@ public:
   void SetActiveByKeyboard() { mActiveByKeyboard = PR_TRUE; }
 
   // indicate that a menu on the menubar was closed. Returns true if the caller
   // may deselect the menuitem.
   virtual PRBool MenuClosed();
 
   // Called when Enter is pressed while the menubar is focused. If the current
   // menu is open, let the child handle the key.
-  nsMenuFrame* Enter();
+  nsMenuFrame* Enter(nsGUIEvent* aEvent);
 
   // Used to handle ALT+key combos
   nsMenuFrame* FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent);
 
   virtual PRBool IsFrameOfType(PRUint32 aFlags) const
   {
     // Override bogus IsFrameOfType in nsBoxFrame.
     if (aFlags & (nsIFrame::eReplacedContainsBlock | nsIFrame::eReplaced))
--- a/layout/xul/base/src/nsMenuFrame.cpp
+++ b/layout/xul/base/src/nsMenuFrame.cpp
@@ -827,17 +827,17 @@ nsMenuFrame::SetDebug(nsBoxLayoutState& 
 //
 // Enter
 //
 // Called when the user hits the <Enter>/<Return> keys or presses the
 // shortcut key. If this is a leaf item, the item's action will be executed.
 // In either case, do nothing if the item is disabled.
 //
 nsMenuFrame*
-nsMenuFrame::Enter()
+nsMenuFrame::Enter(nsGUIEvent *aEvent)
 {
   if (IsDisabled()) {
 #ifdef XP_WIN
     // behavior on Windows - close the popup chain
     if (mMenuParent) {
       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
       if (pm) {
         nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
@@ -848,17 +848,17 @@ nsMenuFrame::Enter()
 #endif   // #ifdef XP_WIN
     // this menu item was disabled - exit
     return nsnull;
   }
 
   if (!IsOpen()) {
     // The enter key press applies to us.
     if (!IsMenu() && mMenuParent)
-      Execute(0);          // Execute our event handler
+      Execute(aEvent);          // Execute our event handler
     else
       return this;
   }
 
   return nsnull;
 }
 
 PRBool
--- a/layout/xul/base/src/nsMenuFrame.h
+++ b/layout/xul/base/src/nsMenuFrame.h
@@ -169,17 +169,17 @@ public:
 
   NS_IMETHOD GetActiveChild(nsIDOMElement** aResult);
   NS_IMETHOD SetActiveChild(nsIDOMElement* aChild);
 
   // called when the Enter key is pressed while the menuitem is the current
   // one in its parent popup. This will carry out the command attached to
   // the menuitem. If the menu should be opened, this frame will be returned,
   // otherwise null will be returned.
-  nsMenuFrame* Enter();
+  nsMenuFrame* Enter(nsGUIEvent* aEvent);
 
   virtual void SetParent(nsIFrame* aParent);
 
   virtual nsMenuParent *GetMenuParent() { return mMenuParent; }
   const nsAString& GetRadioGroupName() { return mGroupName; }
   nsMenuType GetMenuType() { return mType; }
   nsMenuPopupFrame* GetPopup() { return mPopupFrame; }
 
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp
+++ b/layout/xul/base/src/nsMenuPopupFrame.cpp
@@ -1532,23 +1532,23 @@ nsMenuPopupFrame::ChangeMenuItem(nsMenuF
   }
 
   mCurrentMenu = aMenuItem;
 
   return NS_OK;
 }
 
 nsMenuFrame*
-nsMenuPopupFrame::Enter()
+nsMenuPopupFrame::Enter(nsGUIEvent* aEvent)
 {
   mIncrementalString.Truncate();
 
   // Give it to the child.
   if (mCurrentMenu)
-    return mCurrentMenu->Enter();
+    return mCurrentMenu->Enter(aEvent);
 
   return nsnull;
 }
 
 nsMenuFrame*
 nsMenuPopupFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent, PRBool& doAction)
 {
   PRUint32 charCode, keyCode;
--- a/layout/xul/base/src/nsMenuPopupFrame.h
+++ b/layout/xul/base/src/nsMenuPopupFrame.h
@@ -229,17 +229,17 @@ public:
   void SetGeneratedChildren() { mGeneratedChildren = PR_TRUE; }
 
   // called when the Enter key is pressed while the popup is open. This will
   // just pass the call down to the current menu, if any. If a current menu
   // should be opened as a result, this method should return the frame for
   // that menu, or null if no menu should be opened. Also, calling Enter will
   // reset the current incremental search string, calculated in
   // FindMenuWithShortcut.
-  nsMenuFrame* Enter();
+  nsMenuFrame* Enter(nsGUIEvent* aEvent);
 
   nsPopupType PopupType() const { return mPopupType; }
   PRBool IsMenu() { return mPopupType == ePopupTypeMenu; }
   PRBool IsOpen() { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; }
 
   // returns the parent menupopup, if any
   nsMenuFrame* GetParentMenu() {
     nsIFrame* parent = GetParent();
--- a/layout/xul/base/src/nsXULPopupManager.cpp
+++ b/layout/xul/base/src/nsXULPopupManager.cpp
@@ -1726,31 +1726,39 @@ nsXULPopupManager::CancelMenuTimer(nsMen
 {
   if (mCloseTimer && mTimerMenu == aMenuParent) {
     mCloseTimer->Cancel();
     mCloseTimer = nsnull;
     mTimerMenu = nsnull;
   }
 }
 
+static nsGUIEvent* DOMKeyEventToGUIEvent(nsIDOMEvent* aEvent)
+{
+  nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(aEvent));
+  nsEvent* evt = privateEvent ? privateEvent->GetInternalNSEvent() : nsnull;
+  return (evt->eventStructType == NS_KEY_EVENT) ? static_cast<nsGUIEvent *>(evt) : nsnull;
+}
+
 PRBool
 nsXULPopupManager::HandleShortcutNavigation(nsIDOMKeyEvent* aKeyEvent,
                                             nsMenuPopupFrame* aFrame)
 {
   nsMenuChainItem* item = GetTopVisibleMenu();
   if (!aFrame && item)
     aFrame = item->Frame();
 
   if (aFrame) {
     PRBool action;
     nsMenuFrame* result = aFrame->FindMenuWithShortcut(aKeyEvent, action);
     if (result) {
       aFrame->ChangeMenuItem(result, PR_FALSE);
       if (action) {
-        nsMenuFrame* menuToOpen = result->Enter();
+        nsGUIEvent* evt = DOMKeyEventToGUIEvent(aKeyEvent);
+        nsMenuFrame* menuToOpen = result->Enter(evt);
         if (menuToOpen) {
           nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
           ShowMenu(content, PR_TRUE, PR_FALSE);
         }
       }
       return PR_TRUE;
     }
 
@@ -2173,20 +2181,21 @@ nsXULPopupManager::KeyPress(nsIDOMEvent*
   }
   else if (theChar == NS_VK_ENTER ||
            theChar == NS_VK_RETURN) {
     // If there is a popup open, check if the current item needs to be opened.
     // Otherwise, tell the active menubar, if any, to activate the menu. The
     // Enter method will return a menu if one needs to be opened as a result.
     nsMenuFrame* menuToOpen = nsnull;
     nsMenuChainItem* item = GetTopVisibleMenu();
+    nsGUIEvent* evt = DOMKeyEventToGUIEvent(aKeyEvent);
     if (item)
-      menuToOpen = item->Frame()->Enter();
+      menuToOpen = item->Frame()->Enter(evt);
     else if (mActiveMenuBar)
-      menuToOpen = mActiveMenuBar->Enter();
+      menuToOpen = mActiveMenuBar->Enter(evt);
     if (menuToOpen) {
       nsCOMPtr<nsIContent> content = menuToOpen->GetContent();
       ShowMenu(content, PR_TRUE, PR_FALSE);
     }
   }
   else {
     HandleShortcutNavigation(keyEvent, nsnull);
   }
--- a/toolkit/content/tests/widgets/test_menulist_keynav.xul
+++ b/toolkit/content/tests/widgets/test_menulist_keynav.xul
@@ -22,16 +22,17 @@
 <button id="button2" label="Two"/>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 var gShowPopup = false;
+var gModifiers = 0;
 
 var iswin = (navigator.platform.indexOf("Win") == 0);
 
 function runTests()
 {
   var list = $("list");
   list.focus();
   // on Mac, up and cursor keys open the menu, but on other platforms, the
@@ -107,26 +108,70 @@ function differentPressed()
 
   originalPosition = item.getBoundingClientRect().top;
 
   synthesizeKey("VK_DOWN", { });
   is(item.getBoundingClientRect().top, originalPosition - rowdiff, "position of item 10");
 
   list.open = false;
 
-  SimpleTest.finish();
+  checkEnter();
 }
 
 function keyCheck(list, key, index, testname)
 {
   var item = $("i" + index);
   synthesizeKeyExpectEvent(key, { }, item, "command", testname);
   is(list.selectedItem, item, testname + " selectedItem");
 }
 
+function checkModifiers(event)
+{
+  var expectedModifiers = (gModifiers == 1);
+  is(event.shiftKey, expectedModifiers, "shift key pressed");
+  is(event.ctrlKey, expectedModifiers, "ctrl key pressed");
+  is(event.altKey, expectedModifiers, "alt key pressed");
+  is(event.metaKey, expectedModifiers, "meta key pressed");
+  gModifiers++;
+}
+
+function checkEnter()
+{
+  var list = $("list");
+  list.addEventListener("popuphidden", checkEnterWithModifiers, false);
+  list.addEventListener("command", checkModifiers, false);
+  list.open = true;
+  synthesizeKey("VK_ENTER", { });
+}
+
+function checkEnterWithModifiers()
+{
+  is(gModifiers, 1, "modifiers checked when not set");
+
+  var list = $("list");
+  ok(!list.open, "list closed on enter press");
+  list.removeEventListener("popuphidden", checkEnterWithModifiers, false);
+
+  list.addEventListener("popuphidden", done, false);
+  list.open = true;
+
+  synthesizeKey("VK_ENTER", { shiftKey: true, ctrlKey: true, altKey: true, metaKey: true });
+}
+
+function done()
+{
+  is(gModifiers, 2, "modifiers checked when set");
+
+  var list = $("list");
+  ok(!list.open, "list closed on enter press with modifiers");
+  list.removeEventListener("popuphidden", done, false);
+
+  SimpleTest.finish();
+}
+
 SimpleTest.waitForFocus(runTests);
 
 ]]>
 </script>
 
 <body xmlns="http://www.w3.org/1999/xhtml">
 <p id="display">
 </p>