Bug 1527260 - Integrate one off buttons into keyboard navigation. r=Standard8
authorDão Gottwald <dao@mozilla.com>
Wed, 13 Feb 2019 15:06:54 +0000
changeset 458899 6540e3aba359
parent 458898 23a24796db2b
child 458900 c0004cf7d1d1
push id35551
push usershindli@mozilla.com
push dateWed, 13 Feb 2019 21:34:09 +0000
treeherdermozilla-central@08f794a4928e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1527260
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1527260 - Integrate one off buttons into keyboard navigation. r=Standard8 Differential Revision: https://phabricator.services.mozilla.com/D19656
browser/components/search/content/search-one-offs.js
browser/components/urlbar/UrlbarController.jsm
browser/components/urlbar/UrlbarView.jsm
--- a/browser/components/search/content/search-one-offs.js
+++ b/browser/components/search/content/search-one-offs.js
@@ -293,17 +293,17 @@ class SearchOneOffs {
       val.setAttribute("selected", "true");
     }
     this._selectedButton = val;
     this._updateStateForButton(null);
     if (val && !val.engine) {
       // If the button doesn't have an engine, then clear the popup's
       // selection to indicate that pressing Return while the button is
       // selected will do the button's command, not search.
-      this.popup.selectedIndex = -1;
+      this.selectedAutocompleteIndex = -1;
     }
     let event = new CustomEvent("SelectedOneOffButtonChanged", {
       previousSelectedButton: previousButton,
     });
     this.dispatchEvent(event);
     return val;
   }
 
@@ -329,16 +329,24 @@ class SearchOneOffs {
     for (let i = 0; i < buttons.length; i++) {
       if (buttons[i] == this._selectedButton) {
         return i;
       }
     }
     return -1;
   }
 
+  get selectedAutocompleteIndex() {
+    return (this._view || this.popup).selectedIndex;
+  }
+
+  set selectedAutocompleteIndex(val) {
+    return (this._view || this.popup).selectedIndex = val;
+  }
+
   get compact() {
     return this.getAttribute("compact") == "true";
   }
 
   get bundle() {
     if (!this._bundle) {
       const kBundleURI = "chrome://browser/locale/search.properties";
       this._bundle = Services.strings.createBundle(kBundleURI);
@@ -882,40 +890,40 @@ class SearchOneOffs {
         !event.getModifierState("Control") &&
         !event.getModifierState("Meta")) {
       if (this.getAttribute("disabletab") == "true" ||
           (event.shiftKey && this.selectedButtonIndex <= 0) ||
           (!event.shiftKey && this.selectedButtonIndex == this.getSelectableButtons(true).length - 1)) {
         this.selectedButton = null;
         return false;
       }
-      this.popup.selectedIndex = -1;
+      this.selectedAutocompleteIndex = -1;
       this.advanceSelection(!event.shiftKey, true, false);
       return !!this.selectedButton;
     }
 
     if (event.keyCode == KeyboardEvent.DOM_VK_UP) {
       if (event.altKey) {
         // Keep the currently selected result in the list (if any) as a
         // secondary "alt" selection and move the selection up within the
         // buttons.
         this.advanceSelection(false, false, false);
         return true;
       }
       if (numListItems == 0) {
         this.advanceSelection(false, true, false);
         return true;
       }
-      if (this.popup.selectedIndex > 0) {
+      if (this.selectedAutocompleteIndex > 0) {
         // Moving up within the list.  The autocomplete controller should
         // handle this case.  A button may be selected, so null it.
         this.selectedButton = null;
         return false;
       }
-      if (this.popup.selectedIndex == 0) {
+      if (this.selectedAutocompleteIndex == 0) {
         // Moving up from the top of the list.
         if (allowEmptySelection) {
           // Let the autocomplete controller remove selection in the list
           // and revert the typed text in the textbox.
           return false;
         }
         // Wrap selection around to the last button.
         if (this.textbox && typeof textboxUserValue == "string") {
@@ -947,35 +955,35 @@ class SearchOneOffs {
         // the buttons.
         this.advanceSelection(true, false, false);
         return true;
       }
       if (numListItems == 0) {
         this.advanceSelection(true, true, false);
         return true;
       }
-      if (this.popup.selectedIndex >= 0 &&
-          this.popup.selectedIndex < numListItems - 1) {
+      if (this.selectedAutocompleteIndex >= 0 &&
+          this.selectedAutocompleteIndex < numListItems - 1) {
         // Moving down within the list.  The autocomplete controller
         // should handle this case.  A button may be selected, so null it.
         this.selectedButton = null;
         return false;
       }
-      if (this.popup.selectedIndex == numListItems - 1) {
+      if (this.selectedAutocompleteIndex == numListItems - 1) {
         // Moving down from the last item in the list to the buttons.
         this.selectedButtonIndex = 0;
         if (allowEmptySelection) {
           // Let the autocomplete controller remove selection in the list
           // and revert the typed text in the textbox.
           return false;
         }
         if (this.textbox && typeof textboxUserValue == "string") {
           this.textbox.value = textboxUserValue;
         }
-        this.popup.selectedIndex = -1;
+        this.selectedAutocompleteIndex = -1;
         return true;
       }
       if (this.selectedButton) {
         let buttons = this.getSelectableButtons(true);
         if (this.selectedButtonIndex == buttons.length - 1) {
           // Moving down from the buttons back up to the top of the list.
           this.selectedButton = null;
           if (allowEmptySelection) {
--- a/browser/components/urlbar/UrlbarController.jsm
+++ b/browser/components/urlbar/UrlbarController.jsm
@@ -193,16 +193,30 @@ class UrlbarController {
         this.view.isOpen &&
         event.ctrlKey &&
         (event.key == "n" || event.key == "p")) {
       this.view.selectNextItem({ reverse: event.key == "p" });
       event.preventDefault();
       return;
     }
 
+    if (this.view.isOpen) {
+      let queryContext = this._lastQueryContext;
+      if (queryContext) {
+        this.view.oneOffSearchButtons.handleKeyPress(
+          event,
+          queryContext.results.length,
+          this.view.allowEmptySelection,
+          queryContext.searchString);
+        if (event.defaultPrevented) {
+          return;
+        }
+      }
+    }
+
     switch (event.keyCode) {
       case KeyEvent.DOM_VK_ESCAPE:
         this.input.handleRevert();
         event.preventDefault();
         break;
       case KeyEvent.DOM_VK_RETURN:
         if (isMac &&
             event.metaKey) {
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -59,16 +59,22 @@ class UrlbarView {
   /**
    * @returns {boolean}
    *   Whether the panel is open.
    */
   get isOpen() {
     return this.panel.state == "open" || this.panel.state == "showing";
   }
 
+  get allowEmptySelection() {
+    return !(this._queryContext &&
+             this._queryContext.results[0] &&
+             this._queryContext.results[0].heuristic);
+  }
+
   get selectedIndex() {
     if (!this.isOpen || !this._selected) {
       return -1;
     }
     return parseInt(this._selected.getAttribute("resultIndex"));
   }
 
   set selectedIndex(val) {
@@ -110,25 +116,23 @@ class UrlbarView {
    *   Set to true to select the previous item. By default the next item
    *   will be selected.
    */
   selectNextItem({reverse = false} = {}) {
     if (!this.isOpen) {
       throw new Error("UrlbarView: Cannot select an item if the view isn't open.");
     }
 
-    // TODO bug 1527260: handle one-off search buttons
-
     let row;
     if (reverse) {
       row = (this._selected && this._selected.previousElementSibling) ||
-            this._rows.lastElementChild;
+            ((this._selected && this.allowEmptySelection) ? null : this._rows.lastElementChild);
     } else {
       row = (this._selected && this._selected.nextElementSibling) ||
-            this._rows.firstElementChild;
+            ((this._selected && this.allowEmptySelection) ? null : this._rows.firstElementChild);
     }
     this._selectItem(row);
   }
 
   /**
    * Closes the autocomplete results popup.
    */
   close() {