Ignore keys working on search field focus/blur events
MozReview-Commit-ID: L3gpp3SB4V9
--- 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"));
}
}