Ignore keys working on search field focus/blur events draft
authorTylerM <maklebus@msu.edu>
Thu, 13 Oct 2016 00:59:15 -0400
changeset 424928 d37a3064400e333cda4880c928698aa0b740e418
parent 424927 6b66efa9a0eeac155ff1fcb0c4ce9a641a0d91a8
child 424929 11302ddd439eb615b911bbe67d16e014e736203d
push id32293
push userbmo:maklebus@msu.edu
push dateThu, 13 Oct 2016 20:34:31 +0000
milestone52.0a1
Ignore keys working on search field focus/blur events MozReview-Commit-ID: L3gpp3SB4V9
layout/xul/nsXULPopupManager.cpp
layout/xul/nsXULPopupManager.h
toolkit/modules/SelectParentHelper.jsm
--- a/layout/xul/nsXULPopupManager.cpp
+++ b/layout/xul/nsXULPopupManager.cpp
@@ -1978,31 +1978,26 @@ nsXULPopupManager::UpdateKeyboardListene
       mKeyListener = newTarget;
     }
   }
 }
 
 void
 nsXULPopupManager::UpdateSearchListeners()
 {
+  nsIContent* content;
   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 (item && item->PopupType() == ePopupTypeMenu) {
+	  // The search box is assumed to be the first child of the menupopup.
+	  content = item->Content()->GetFirstChild();
+	  if (content->IsXULElement(nsGkAtoms::textbox)) {
+		  newTarget = content;
+	  }
   }
 
   if (mSearchListener != newTarget) {
     if (mSearchListener) {
       mSearchListener->RemoveEventListener(NS_LITERAL_STRING("blur"), this, true);
       mSearchListener->RemoveEventListener(NS_LITERAL_STRING("focus"), this, true);
       mSearchListener = nullptr;
     }
@@ -2605,21 +2600,19 @@ nsXULPopupManager::HandleEvent(nsIDOMEve
 	aEvent->GetIsTrusted(&trustedEvent);
 	if (!trustedEvent) {
 		return NS_OK;
 	}
 
 	nsAutoString eventType;
 	aEvent->GetType(eventType);
 
-	if(eventType.EqualsLiteral("blur")){
-		return UpdateIgnoreKeys();
-	}
-	if(eventType.EqualsLiteral("focus")){
-		return UpdateIgnoreKeys();
+	if (eventType.EqualsLiteral("blur") ||
+      eventType.EqualsLiteral("focus")) {
+      return UpdateIgnoreKeys(eventType.EqualsLiteral("focus"));
 	}
 
   nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
   NS_ENSURE_TRUE(keyEvent, NS_ERROR_UNEXPECTED);
 
   if (eventType.EqualsLiteral("keyup")) {
     return KeyUp(keyEvent);
   }
@@ -2631,59 +2624,23 @@ nsXULPopupManager::HandleEvent(nsIDOMEve
   }
 
   NS_ABORT();
 
   return NS_OK;
 }
 
 nsresult
-nsXULPopupManager::UpdateIgnoreKeys()
-{
-  /*
-  nsMenuFrame* menuFrame = do_QueryFrame(mMenu->GetPrimaryFrame());
-  nsIFrame* frame = menuFrame->GetParent();
-  while (frame) {
-    nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
-    if (popupFrame) {
-      aPopup = popupFrame->GetContent();
-      break;
-    }
-    frame = frame->GetParent();
+nsXULPopupManager::UpdateIgnoreKeys(bool aIgnoreKeys)
+{
+  nsMenuChainItem* item = GetTopVisibleMenu();
+  if (item) {
+	  item->SetIgnoreKeys(aIgnoreKeys ? eIgnoreKeys_True : eIgnoreKeys_False);
   }
-  */
-
-  nsMenuChainItem* item = GetTopVisibleMenu();
-
-  if(item){
-    
-	/*  
-	nsIContent* aPopup = item->Frame()->GetContent();
-
-    nsAutoString ignorekeys;
-    aPopup->GetAttr(kNameSpaceID_None, nsGkAtoms::ignorekeys, ignorekeys);
-
-    if (ignorekeys.EqualsLiteral("true")) {
-	*/
-	if (item->IgnoreKeys() == eIgnoreKeys_Handled) {
-		item->SetIgnoreKeys(eIgnoreKeys_True);
-	}
-	if (item->IgnoreKeys() == eIgnoreKeys_True) {
-		item->SetIgnoreKeys(eIgnoreKeys_True);
-	}
-	/*
-    } else if (ignorekeys.EqualsLiteral("handled")) {
-		item->SetIgnoreKeys(eIgnoreKeys_Handled);
-	}
-	else {
-		item->SetIgnoreKeys(eIgnoreKeys_False);
-	}
-	*/
-  }
-
+  UpdateKeyboardListeners();
   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) {
--- a/layout/xul/nsXULPopupManager.h
+++ b/layout/xul/nsXULPopupManager.h
@@ -655,17 +655,19 @@ public:
 
   /**
    * Handles the keyboard event with keyCode value. Returns true if the event
    * has been handled.
    */
   bool HandleKeyboardEventWithKeyCode(nsIDOMKeyEvent* aKeyEvent,
                                       nsMenuChainItem* aTopVisibleMenuItem);
 
-  nsresult UpdateIgnoreKeys();
+  // Sets mIgnoreKeys of the Top Visible Menu Item
+  nsresult UpdateIgnoreKeys(bool aIgnoreKeys);
+
   nsresult KeyUp(nsIDOMKeyEvent* aKeyEvent);
   nsresult KeyDown(nsIDOMKeyEvent* aKeyEvent);
   nsresult KeyPress(nsIDOMKeyEvent* aKeyEvent);
 
 protected:
   nsXULPopupManager();
   ~nsXULPopupManager();
 
@@ -772,16 +774,21 @@ 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();
+
+  /*
+  * Install listeners for focus and blur events on select dropdown search 
+  * fields. This is used to update the ignore keys value of the menu popup.
+  */
   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
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -149,19 +149,18 @@ this.SelectParentHelper = {
     popup.removeEventListener("mouseout", this);
     browser.ownerDocument.defaultView.removeEventListener("keydown", this, true);
     browser.ownerDocument.defaultView.removeEventListener("fullscreen", this, true);
     browser.messageManager.removeMessageListener("Forms:UpdateDropDown", this);
   },
 
 };
 
-// CPST - added first search parameter
 function populateChildren(menulist, options, selectedIndex, zoom,
-                          parentElement = null, isGroupDisabled = false, adjustedTextSize = -1, firstSearch = true) {
+                          parentElement = null, isGroupDisabled = false, adjustedTextSize = -1, addSearch = true) {
   let element = menulist.menupopup;
 
   // -1 just means we haven't calculated it yet. When we recurse through this function
   // we will pass in adjustedTextSize to save on recalculations.
   if (adjustedTextSize == -1) {
     let win = element.ownerDocument.defaultView;
 
     // Grab the computed text size and multiply it by the remote browser's fullZoom to ensure
@@ -185,17 +184,16 @@ function populateChildren(menulist, opti
     element.appendChild(item);
 
     // A disabled optgroup disables all of its child options.
     let isDisabled = isGroupDisabled || option.disabled;
     if (isDisabled) {
       item.setAttribute("disabled", "true");
     }
 
-    // CPST - added false argument
     if (isOptGroup) {
       populateChildren(menulist, option.children, selectedIndex, zoom,
                        item, isDisabled, adjustedTextSize, false);
     } 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
@@ -214,101 +212,97 @@ function populateChildren(menulist, opti
       item.setAttribute("value", option.index);
 
       if (parentElement) {
         item.classList.add("contentSelectDropdown-ingroup")
       }
     }
   }
 
-  // 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");
+  // Check if this is the first time iterating through the dropdown and if list is
+  //  long enough for a search element to be added.
+  if(addSearch && element.childElementCount > 40){
 
-    // CPST add input event listener to search box
+    // Add a search text field as the first element of the dropdown
+    let searchbox = element.ownerDocument.createElement("textbox");
+    searchbox.setAttribute("type", "search");
     searchbox.addEventListener("input", onSearchInput);
-
-    // CPST insert searchbox element at top of dropdown
     element.insertBefore(searchbox, element.childNodes[0]);
-
-    //element.setAttribute("ignorekeys", "true");
   }
 
 }
 
-// CPST - Search input event
 function onSearchInput(){
   let doc = this.ownerDocument;
   let win = doc.defaultView;
   let selection = doc.defaultView.getSelection();
   selection.removeAllRanges();
+  let searchObj = this;
 
-  let searchObj = this;
   // Get input from search field, set to all lower case for comparison
   let input = searchObj.value.toLowerCase();
 
+  // Get all items in dropdown (could be options or optgroups)
   let menupopup = searchObj.parentElement;
   let menuItems = menupopup.querySelectorAll("menuitem, menucaption");
 
+  // Flag used to detect any group headers with no visible options.
+  //  These group headers should be hidden.
   let allHidden = true;
+
+  // Keep a reference to the previous group header (menucaption) to go back
+  //  and set to hidden if all options within are hidden.
   let prevCaption = null;
 
-  // Iterate through options to show/hide
   for (let currentItem of menuItems) {
-
     // Get label and tooltip (title) from option and change to
     //  lower case for comparison
     let itemLabel = currentItem.getAttribute("label").toLowerCase();
     let itemTooltip = currentItem.getAttribute("title").toLowerCase();
 
+    // If search input is empty, all options should be shown
     if(input==""){
       currentItem.setAttribute("hidden", "false");
 
     } else if(currentItem.localName=="menucaption"){
-
       if(prevCaption!=null){
           prevCaption.setAttribute("hidden", (allHidden ? "true" : "false"));
       }
       prevCaption = currentItem;
       allHidden = true;
 
     } else{
-
       if(!currentItem.classList.contains("contentSelectDropdown-ingroup") && currentItem.previousSibling.classList.contains("contentSelectDropdown-ingroup")){
         if(prevCaption!=null){
             prevCaption.setAttribute("hidden", (allHidden ? "true" : "false"));
         }
         prevCaption = null;
         allHidden = true;
       }
 
       if(itemLabel.includes(input) || itemTooltip.includes(input)){
         currentItem.setAttribute("hidden", "false");
 
+        // Set range and selection for character match underlining
         let start = itemLabel.indexOf(input);
         if(start!=-1){
-          // Getting label this way does not work on OSX? Find better way
+          // Assumes label is first child, not the case in OSX!
           let label = currentItem.boxObject.firstChild;
           let textNode = label.firstChild;
           let range = new win.Range();
           range.setStart(textNode, start);
           range.setEnd(textNode, (start+input.length));
           let selection = doc.defaultView.getSelection();
           selection.addRange(range);
         }
 
         allHidden = false;
       } else{
         currentItem.setAttribute("hidden", "true");
       }
     }
 
   }
-
   if(prevCaption!=null && allHidden){
     prevCaption.setAttribute("hidden", (allHidden ? "true" : "false"));
   }
 
 }