Ignore keys listener work in XULPopupManager, removed ignorekeys from SelectParentHelper (must reenable for search to work for now) draft
authorTylerM <maklebus@msu.edu>
Thu, 06 Oct 2016 19:55:43 -0400
changeset 424925 3ce9cd2afc0feec705b0b966b13f1c3796e1532b
parent 424924 7a699d16c98d34e9fcc28bd088012e3bbcc48572
child 424926 46e6794eaee52e387327e53841fef7c6bfaab56b
push id32293
push userbmo:maklebus@msu.edu
push dateThu, 13 Oct 2016 20:34:31 +0000
milestone52.0a1
Ignore keys listener work in XULPopupManager, removed ignorekeys from SelectParentHelper (must reenable for search to work for now) MozReview-Commit-ID: FaukxFwWiVn
layout/xul/nsXULPopupManager.cpp
layout/xul/nsXULPopupManager.h
toolkit/modules/SelectParentHelper.jsm
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -132,17 +132,17 @@ nsXULPopupManager::nsXULPopupManager() :
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->AddObserver(this, "xpcom-shutdown", false);
   }
   Preferences::AddBoolVarCache(&sDevtoolsDisableAutoHide,
                                kPrefDevtoolsDisableAutoHide, false);
 }
 
-nsXULPopupManager::~nsXULPopupManager() 
+nsXULPopupManager::~nsXULPopupManager()
 {
   NS_ASSERTION(!mPopups && !mNoHidePanels, "XUL popups still open");
 }
 
 nsresult
 nsXULPopupManager::Init()
 {
   sInstance = new nsXULPopupManager();
@@ -164,16 +164,21 @@ nsXULPopupManager::Observe(nsISupports *
 {
   if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
     if (mKeyListener) {
       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keypress"), this, true);
       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keydown"), this, true);
       mKeyListener->RemoveEventListener(NS_LITERAL_STRING("keyup"), this, true);
       mKeyListener = nullptr;
     }
+    if (mSearchListener) {
+      mSearchListener->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
+      mSearchListener->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true);
+      mSearchListener = nullptr;
+    }
     mRangeParent = nullptr;
     // mOpeningPopup is cleared explicitly soon after using it.
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->RemoveObserver(this, "xpcom-shutdown");
     }
   }
 
@@ -617,17 +622,17 @@ nsXULPopupManager::InitTriggerEvent(nsID
         nsPresContext* presContext;
         if (presShell && (presContext = presShell->GetPresContext())) {
           nsPresContext* rootDocPresContext =
             presContext->GetRootPresContext();
           if (!rootDocPresContext)
             return;
           nsIFrame* rootDocumentRootFrame = rootDocPresContext->
               PresShell()->FrameManager()->GetRootFrame();
-          if ((event->mClass == eMouseEventClass || 
+          if ((event->mClass == eMouseEventClass ||
                event->mClass == eMouseScrollEventClass ||
                event->mClass == eWheelEventClass) &&
                !event->AsGUIEvent()->mWidget) {
             // no widget, so just use the client point if available
             nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent);
             nsIntPoint clientPt;
             mouseEvent->GetClientX(&clientPt.x);
             mouseEvent->GetClientY(&clientPt.y);
@@ -909,16 +914,18 @@ nsXULPopupManager::ShowPopupCallback(nsI
   nsAutoString ignorekeys;
   aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
   if (ignorekeys.EqualsLiteral("true")) {
     item->SetIgnoreKeys(eIgnoreKeys_True);
   } else if (ignorekeys.EqualsLiteral("handled")) {
     item->SetIgnoreKeys(eIgnoreKeys_Handled);
   }
 
+  UpdateSearchListeners();
+
   if (ismenu) {
     // if the menu is on a menubar, use the menubar's listener instead
     nsMenuFrame* menuFrame = do_QueryFrame(aPopupFrame->GetParent());
     if (menuFrame) {
       item->SetOnMenuBar(menuFrame->IsOnMenuBar());
     }
   }
 
@@ -1016,17 +1023,17 @@ nsXULPopupManager::HidePopup(nsIContent*
     // scan and just close up this menu.
     if (foundMenu->IsMenu()) {
       item = topMenu->GetChild();
       while (item && item->IsMenu()) {
         topMenu = item;
         item = item->GetChild();
       }
     }
-    
+
     deselectMenu = aDeselectMenu;
     popupToHide = topMenu->Content();
     popupFrame = topMenu->Frame();
     type = popupFrame->PopupType();
 
     nsMenuChainItem* parent = topMenu->GetParent();
 
     // close up another popup if there is one, and we are either hiding the
@@ -1969,16 +1976,51 @@ nsXULPopupManager::UpdateKeyboardListene
       newTarget->AddEventListener(NS_LITERAL_STRING("keyup"), this, true);
       nsContentUtils::NotifyInstalledMenuKeyboardListener(isForMenu);
       mKeyListener = newTarget;
     }
   }
 }
 
 void
+nsXULPopupManager::UpdateSearchListeners()
+{
+  nsCOMPtr<EventTarget> newTarget;
+  bool isForMenu = false;
+
+  nsMenuChainItem* item = GetTopVisibleMenu();
+  if (item) {
+    if (item->IgnoreKeys() != eIgnoreKeys_True) {
+      newTarget = item->Content()->GetFirstChild();
+    }
+    // CPST Debug
+    isForMenu = item->PopupType() == ePopupTypeMenu;
+  }
+  else if (mActiveMenuBar) {
+    newTarget = mActiveMenuBar->GetContent()->GetFirstChild();
+    // CPST Debug
+    isForMenu = true;
+  }
+
+  if (mSearchListener != newTarget) {
+    if (mSearchListener) {
+      mSearchListener->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
+      mSearchListener->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true);
+      mSearchListener = nullptr;
+    }
+
+    if (newTarget) {
+      newTarget->AddEventListener(NS_LITERAL_STRING("blur"), this, true);
+      newTarget->AddEventListener(NS_LITERAL_STRING("focus"), this, true);
+      mSearchListener = newTarget;
+    }
+  }
+}
+
+void
 nsXULPopupManager::UpdateMenuItems(nsIContent* aPopup)
 {
   // Walk all of the menu's children, checking to see if any of them has a
   // command attribute. If so, then several attributes must potentially be updated.
 
   nsCOMPtr<nsIDocument> document = aPopup->GetUncomposedDoc();
   if (!document) {
     return;
@@ -2030,29 +2072,29 @@ nsXULPopupManager::UpdateMenuItems(nsICo
         grandChild->GetParent()->IsXULElement(nsGkAtoms::menugroup)) {
       grandChild = grandChild->GetParent();
     }
   }
 }
 
 // Notify
 //
-// The item selection timer has fired, we might have to readjust the 
+// The item selection timer has fired, we might have to readjust the
 // selected item. There are two cases here that we are trying to deal with:
 //   (1) diagonal movement from a parent menu to a submenu passing briefly over
 //       other items, and
 //   (2) moving out from a submenu to a parent or grandparent menu.
 // In both cases, |mTimerMenu| is the menu item that might have an open submenu and
 // the first item in |mPopups| is the item the mouse is currently over, which could be
 // none of them.
 //
 // case (1):
 //  As the mouse moves from the parent item of a submenu (we'll call 'A') diagonally into the
 //  submenu, it probably passes through one or more sibilings (B). As the mouse passes
-//  through B, it becomes the current menu item and the timer is set and mTimerMenu is 
+//  through B, it becomes the current menu item and the timer is set and mTimerMenu is
 //  set to A. Before the timer fires, the mouse leaves the menu containing A and B and
 //  enters the submenus. Now when the timer fires, |mPopups| is null (!= |mTimerMenu|)
 //  so we have to see if anything in A's children is selected (recall that even disabled
 //  items are selected, the style just doesn't show it). If that is the case, we need to
 //  set the selected item back to A.
 //
 // case (2);
 //  Item A has an open submenu, and in it there is an item (B) which also has an open
@@ -2061,20 +2103,20 @@ nsXULPopupManager::UpdateMenuItems(nsICo
 //  the timer is set and |mTimerMenu| is A and |mPopups| is C. As the timer fires,
 //  the mouse is still within C. The correct behavior is to set the current item to C
 //  and close up the chain parented at A.
 //
 //  This brings up the question of is the logic of case (1) enough? The answer is no,
 //  and is discussed in bugzilla bug 29400. Case (1) asks if A's submenu has a selected
 //  child, and if it does, set the selected item to A. Because B has a submenu open, it
 //  is selected and as a result, A is set to be the selected item even though the mouse
-//  rests in C -- very wrong. 
+//  rests in C -- very wrong.
 //
-//  The solution is to use the same idea, but instead of only checking one level, 
-//  drill all the way down to the deepest open submenu and check if it has something 
+//  The solution is to use the same idea, but instead of only checking one level,
+//  drill all the way down to the deepest open submenu and check if it has something
 //  selected. Since the mouse is in a grandparent, it won't, and we know that we can
 //  safely close up A and all its children.
 //
 // The code below melds the two cases together.
 //
 nsresult
 nsXULPopupManager::Notify(nsITimer* aTimer)
 {
@@ -2199,20 +2241,20 @@ nsXULPopupManager::HandleKeyboardNavigat
 
   // if a popup is open, first check for navigation within the popup
   if (item && HandleKeyboardNavigationInPopup(item, theDirection))
     return true;
 
   // no popup handled the key, so check the active menubar, if any
   if (mActiveMenuBar) {
     nsMenuFrame* currentMenu = mActiveMenuBar->GetCurrentMenuItem();
-  
+
     if (NS_DIRECTION_IS_INLINE(theDirection)) {
       nsMenuFrame* nextItem = (theDirection == eNavigationDirection_End) ?
-                              GetNextMenuItem(mActiveMenuBar, currentMenu, false) : 
+                              GetNextMenuItem(mActiveMenuBar, currentMenu, false) :
                               GetPreviousMenuItem(mActiveMenuBar, currentMenu, false);
       mActiveMenuBar->ChangeMenuItem(nextItem, true, true);
       return true;
     }
     else if (NS_DIRECTION_IS_BLOCK(theDirection)) {
       // Open the menu and select its first item.
       if (currentMenu) {
         nsCOMPtr<nsIContent> content = currentMenu->GetContent();
@@ -2565,32 +2607,80 @@ nsXULPopupManager::HandleEvent(nsIDOMEve
   bool trustedEvent = false;
   aEvent->GetIsTrusted(&trustedEvent);
   if (!trustedEvent) {
     return NS_OK;
   }
 
   nsAutoString eventType;
   aEvent->GetType(eventType);
+
+  if (eventType.EqualsLiteral("blur") || eventType.EqualsLiteral("focus")) {
+    return UpdateIgnoreKeys();
+  }
   if (eventType.EqualsLiteral("keyup")) {
     return KeyUp(keyEvent);
   }
   if (eventType.EqualsLiteral("keydown")) {
     return KeyDown(keyEvent);
   }
   if (eventType.EqualsLiteral("keypress")) {
     return KeyPress(keyEvent);
   }
 
   NS_ABORT();
 
   return NS_OK;
 }
 
 nsresult
+nsXULPopupManager::UpdateIgnoreKeys()
+{
+  // TODO update ignore keys here
+
+
+  /*
+  nsMenuFrame* menuFrame = do_QueryFrame(GetTopVisibleMenu()->GetPrimaryFrame());
+
+  // Find the popup that the menu is inside. Below, this popup will
+  // need to be hidden.
+  nsIFrame* frame = menuFrame->GetParent();
+  while (frame) {
+    nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
+    if (popupFrame) {
+      aPopup = popupFrame->GetContent();
+      break;
+    }
+    frame = frame->GetParent();
+  }
+  */
+
+  nsMenuChainItem* item = GetTopVisibleMenu();
+  if (!item)
+    return NS_OK;
+
+  nsIContent* aPopup = item->Frame()->GetContent();
+
+  // install keyboard event listeners for navigating menus. For panels, the
+  // escape key may be used to close the panel. However, the ignorekeys
+  // attribute may be used to disable adding these event listeners for popups
+  // that want to handle their own keyboard events.
+  nsAutoString ignorekeys;
+  aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
+  if (ignorekeys.EqualsLiteral("true")) {
+    item->SetIgnoreKeys(eIgnoreKeys_True);
+  } else if (ignorekeys.EqualsLiteral("handled")) {
+    item->SetIgnoreKeys(eIgnoreKeys_Handled);
+  }
+
+
+  return NS_OK;
+}
+
+nsresult
 nsXULPopupManager::KeyUp(nsIDOMKeyEvent* aKeyEvent)
 {
   // don't do anything if a menu isn't open or a menubar isn't active
   if (!mActiveMenuBar) {
     nsMenuChainItem* item = GetTopVisibleMenu();
     if (!item || item->PopupType() != ePopupTypeMenu)
       return NS_OK;
 
@@ -2657,17 +2747,17 @@ nsXULPopupManager::KeyDown(nsIDOMKeyEven
         nsMenuChainItem* item = GetTopVisibleMenu();
         if (mPopups && item && !item->Frame()->IsMenuList()) {
           Rollup(0, false, nullptr, nullptr);
         } else if (mActiveMenuBar) {
           mActiveMenuBar->MenuClosed();
         }
 
         // Clear the item to avoid bugs as it may have been deleted during rollup.
-        item = nullptr; 
+        item = nullptr;
       }
       aKeyEvent->AsEvent()->StopPropagation();
       aKeyEvent->AsEvent()->PreventDefault();
     }
   }
 
   aKeyEvent->AsEvent()->StopCrossProcessForwarding();
   return NS_OK;
--- a/layout/xul/nsXULPopupManager.h
+++ b/layout/xul/nsXULPopupManager.h
@@ -62,17 +62,17 @@ enum CloseMenuMode {
   CloseMenuMode_Auto, // close up the chain of menus, default value
   CloseMenuMode_None, // don't close up any menus
   CloseMenuMode_Single // close up only the menu the command is inside
 };
 
 /**
  * nsNavigationDirection: an enum expressing navigation through the menus in
  * terms which are independent of the directionality of the chrome. The
- * terminology, derived from XSL-FO and CSS3 (e.g. 
+ * terminology, derived from XSL-FO and CSS3 (e.g.
  * http://www.w3.org/TR/css3-text/#TextLayout), is BASE (Before, After, Start,
  * End), with the addition of First and Last (mapped to Home and End
  * respectively).
  *
  * In languages such as English where the inline progression is left-to-right
  * and the block progression is top-to-bottom (lr-tb), these terms will map out
  * as in the following diagram
  *
@@ -82,17 +82,17 @@ enum CloseMenuMode {
  *           ...                |
  *           Before             |
  *         +--------+         block
  *   Start |        | End  progression
  *         +--------+           |
  *           After              |
  *           ...                |
  *           Last               V
- * 
+ *
  */
 
 enum nsNavigationDirection {
   eNavigationDirection_Last,
   eNavigationDirection_First,
   eNavigationDirection_Start,
   eNavigationDirection_Before,
   eNavigationDirection_End,
@@ -424,17 +424,17 @@ public:
                  bool aSelectFirstItem,
                  nsIDOMEvent* aTriggerEvent);
 
   /**
    * Open a popup at a specific screen position specified by aXPos and aYPos,
    * measured in CSS pixels.
    *
    * This fires the popupshowing event synchronously.
-   * 
+   *
    * If aIsContextMenu is true, the popup is positioned at a slight
    * offset from aXPos/aYPos to ensure that it is not under the mouse
    * cursor.
    */
   void ShowPopupAtScreen(nsIContent* aPopup,
                          int32_t aXPos, int32_t aYPos,
                          bool aIsContextMenu,
                          nsIDOMEvent* aTriggerEvent);
@@ -655,16 +655,17 @@ public:
 
   /**
    * Handles the keyboard event with keyCode value. Returns true if the event
    * has been handled.
    */
   bool HandleKeyboardEventWithKeyCode(nsIDOMKeyEvent* aKeyEvent,
                                       nsMenuChainItem* aTopVisibleMenuItem);
 
+  nsresult UpdateIgnoreKeys();
   nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent);
   nsresult KeyDown(nsIDOMKeyEvent* aKeyEvent);
   nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent);
 
 protected:
   nsXULPopupManager();
   ~nsXULPopupManager();
 
@@ -715,17 +716,17 @@ protected:
    * within.
    *
    * The caller must keep a strong reference to aPopup, aNextPopup and aLastPopup.
    *
    * aPopup - the popup to hide
    * aNextPopup - the next popup to hide
    * aLastPopup - the last popup in the chain to hide
    * aPresContext - nsPresContext for the popup's frame
-   * aPopupType - the PopupType of the frame. 
+   * aPopupType - the PopupType of the frame.
    * aDeselectMenu - true to unhighlight the menu when hiding it
    * aIsCancel - true if this popup is hiding due to being cancelled.
    */
   void FirePopupHidingEvent(nsIContent* aPopup,
                             nsIContent* aNextPopup,
                             nsIContent* aLastPopup,
                             nsPresContext *aPresContext,
                             nsPopupType aPopupType,
@@ -771,25 +772,28 @@ protected:
    * Having menus in different documents is very rare, so the listeners will
    * usually only be attached when the first menu opens and removed when all
    * menus have closed.
    *
    * This is also used when only a menubar is active without any open menus,
    * so that keyboard navigation between menus on the menubar may be done.
    */
   void UpdateKeyboardListeners();
+  void UpdateSearchListeners();
 
   /*
    * Returns true if the docshell for aDoc is aExpected or a child of aExpected.
    */
   bool IsChildOfDocShell(nsIDocument* aDoc, nsIDocShellTreeItem* aExpected);
 
   // the document the key event listener is attached to
   nsCOMPtr<mozilla::dom::EventTarget> mKeyListener;
 
+  nsCOMPtr<mozilla::dom::EventTarget> mSearchListener;
+
   // widget that is currently listening to rollup events
   nsCOMPtr<nsIWidget> mWidget;
 
   // range parent and offset set in SetTriggerEvent
   nsCOMPtr<nsIDOMNode> mRangeParent;
   int32_t mRangeOffset;
   // Device pixels relative to the showing popup's presshell's
   // root prescontext's root frame.
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -222,50 +222,27 @@ function populateChildren(menulist, opti
   // CPST Check if first iteration through list and if list is long enough for
   //  a search element to be added
   if(firstSearch && element.childElementCount > 40){
 
     // CPST Add a search field to top of list
     let searchbox = element.ownerDocument.createElement('textbox');
     searchbox.setAttribute("type", "search");
 
-    // CPST add focus event listener to search box
-    searchbox.addEventListener("focus", onSearchFocus);
-    // CPST add blur event listener to search box
-    searchbox.addEventListener("blur", onSearchBlur);
     // CPST add input event listener to search box
     searchbox.addEventListener("input", onSearchInput);
 
     // CPST insert searchbox element at top of dropdown
     element.insertBefore(searchbox, element.childNodes[0]);
 
-    // CPST ignore key list navigation so text goes to search
-    element.setAttribute("ignorekeys", "true");
+    //element.setAttribute("ignorekeys", "true");
   }
 
 }
 
-// CPST - Search focus event
-function onSearchFocus(){
-  dump("CPST - Search Focus\n");
-  let searchObj = this;
-  let menupopup = searchObj.parentElement;
-  menupopup.setAttribute("ignorekeys", "true");
-  dump("CPST - Ignore keys true");
-}
-
-// CPST - Search blur event
-function onSearchBlur(){
-  dump("CPST - Search Blur\n");
-  let searchObj = this;
-  let menupopup = searchObj.parentElement;
-  menupopup.setAttribute("ignorekeys", "false");
-  dump("CPST - Ignore keys false");
-}
-
 // CPST - Search input event
 function onSearchInput(){
   let doc = this.ownerDocument;
   let win = doc.defaultView;
   let selection = doc.defaultView.getSelection();
   selection.removeAllRanges();
 
   let searchObj = this;