Bug 1315509 - Ctrl+K shortcut should add '?' to the Address Bar if search bar is removed. r=adw
authorMarco Bonardo <mbonardo@mozilla.com>
Fri, 23 Nov 2018 15:15:47 +0000
changeset 504290 9ef539ed2f409a3844cbbadc681ea209af44bca5
parent 504289 6be5d281ae921fca461799ba5168599b480cd7e5
child 504291 8c69ece51451f6ae6da33d9475f7225a51b037d3
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1315509
milestone65.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 1315509 - Ctrl+K shortcut should add '?' to the Address Bar if search bar is removed. r=adw Differential Revision: https://phabricator.services.mozilla.com/D12222
browser/base/content/browser-places.js
browser/base/content/browser.js
browser/base/content/urlbarBindings.xml
toolkit/components/places/UnifiedComplete.js
toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
toolkit/components/places/tests/unifiedcomplete/test_tab_matches.js
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -527,24 +527,17 @@ var PlacesCommandHook = {
     } else {
       organizer.PlacesOrganizer.selectLeftPaneContainerByHierarchy(item);
       organizer.focus();
     }
   },
 
   searchBookmarks() {
     focusAndSelectUrlBar();
-    for (let char of ["*", " "]) {
-      let code = char.charCodeAt(0);
-      gURLBar.inputField.dispatchEvent(new KeyboardEvent("keypress", {
-        keyCode: code,
-        charCode: code,
-        bubbles: true,
-      }));
-    }
+    gURLBar.typeRestrictToken(UrlbarTokenizer.RESTRICT.BOOKMARK);
   },
 };
 
 ChromeUtils.defineModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils",
   "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm");
 
 // View for the history menu.
 function HistoryMenu(aPopupShowingEvent) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -64,16 +64,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   SitePermissions: "resource:///modules/SitePermissions.jsm",
   TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
   TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
   Translation: "resource:///modules/translation/Translation.jsm",
   UITour: "resource:///modules/UITour.jsm",
   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
   UrlbarInput: "resource:///modules/UrlbarInput.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
+  UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
   UrlbarValueFormatter: "resource:///modules/UrlbarValueFormatter.jsm",
   Utils: "resource://gre/modules/sessionstore/Utils.jsm",
   Weave: "resource://services-sync/main.js",
   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
   fxAccounts: "resource://gre/modules/FxAccounts.jsm",
   webrtcUI: "resource:///modules/webrtcUI.jsm",
   ZoomUI: "resource:///modules/ZoomUI.jsm",
@@ -4030,16 +4031,18 @@ const BrowserSearch = {
         Services.obs.addObserver(observer, "browser-delayed-startup-finished");
       }
       return;
     }
 
     let focusUrlBarIfSearchFieldIsNotActive = function(aSearchBar) {
       if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
         focusAndSelectUrlBar(true);
+        // Limit the results to search suggestions, like the search bar.
+        gURLBar.typeRestrictToken(UrlbarTokenizer.RESTRICT.SEARCH);
       }
     };
 
     let searchBar = this.searchBar;
     let placement = CustomizableUI.getPlacementOfWidget("search-container");
     let focusSearchBar = () => {
       searchBar = this.searchBar;
       searchBar.select();
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1705,16 +1705,30 @@ file, You can obtain one at http://mozil
           // possible.  If handleText() returned false, then manually start a
           // new search here.
           if (!this._onInputHandledText) {
             this.gotResultForCurrentQuery = false;
             this.controller.startSearch(value);
           }
         ]]></body>
       </method>
+
+      <method name="typeRestrictToken">
+        <parameter name="char"/>
+        <body><![CDATA[
+          for (let c of [char, " "]) {
+            let code = c.charCodeAt(0);
+            gURLBar.inputField.dispatchEvent(new KeyboardEvent("keypress", {
+              keyCode: code,
+              charCode: code,
+              bubbles: true,
+            }));
+          }
+        ]]></body>
+      </method>
     </implementation>
 
     <handlers>
       <handler event="keydown"><![CDATA[
         if (this._noActionKeys.has(event.keyCode) &&
             this.popup.selectedIndex >= 0 &&
             !this._pressedNoActionKeys.has(event.keyCode)) {
           if (this._pressedNoActionKeys.size == 0) {
@@ -2488,18 +2502,24 @@ file, You can obtain one at http://mozil
             // because the result may have already been added but only now is
             // being selected, and we need to check gotResultForCurrentQuery
             // because the result may be from the previous search and already
             // selected and is now being reused.
             if (selectHeuristic || !this.input.gotResultForCurrentQuery) {
               this.input.formatValue();
 
               // Also, hide the one-off search buttons if the user is using, or
-              // starting to use, an "@engine" search engine alias.
-              this.toggleOneOffSearches(this.input.value.trim()[0] != "@");
+              // starting to use, an "@engine" search engine alias, or typed
+              // only the search restriction character.
+              let trimmedValue = this.input.value.trim();
+              this.toggleOneOffSearches(
+                trimmedValue[0] != "@" &&
+                (trimmedValue[0] != UrlbarTokenizer.RESTRICT.SEARCH ||
+                 trimmedValue.length != 1)
+              );
             }
 
             // If this is the first time we get the result from the current
             // search and we are not in the private context, we can speculatively
             // connect to the intended site as a performance optimization.
             if (!this.input.gotResultForCurrentQuery &&
                 this.input.speculativeConnectEnabled &&
                 !this.input.inPrivateContext &&
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -1,13 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * vim: sw=2 ts=2 sts=2 expandtab
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* eslint complexity: ["error", 50] */
 
 "use strict";
 
 // Constants
 
 const MS_PER_DAY = 86400000; // 24 * 60 * 60 * 1000
 
 // AutoComplete query type constants.
@@ -608,18 +609,30 @@ function Search(searchString, searchPara
   this._inPrivateWindow = params.has("private-window");
   this._prohibitAutoFill = params.has("prohibit-autofill");
 
   let userContextId = searchParam.match(REGEXP_USER_CONTEXT_ID);
   this._userContextId = userContextId ?
                           parseInt(userContextId[1], 10) :
                           Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
 
-  this._searchTokens =
-    this.filterTokens(getUnfilteredSearchTokens(this._searchString));
+  let unfilteredTokens = getUnfilteredSearchTokens(this._searchString);
+
+  // We handle any leading restriction character specially, in particular for
+  // a search restriction we also handle the case where there's no space before
+  // the query, like "?porcupine".
+  if (unfilteredTokens.length > 1 &&
+      this._trimmedOriginalSearchString.startsWith(unfilteredTokens[0]) &&
+      Object.values(UrlbarTokenizer.RESTRICT).includes(unfilteredTokens[0])) {
+    this._leadingRestrictionToken = unfilteredTokens[0];
+  } else if (this._trimmedOriginalSearchString.startsWith(UrlbarTokenizer.RESTRICT.SEARCH)) {
+    this._leadingRestrictionToken = UrlbarTokenizer.RESTRICT.SEARCH;
+  }
+
+  this._searchTokens = this.filterTokens(unfilteredTokens);
 
   // The heuristic token is the first filtered search token, but only when it's
   // actually the first thing in the search string.  If a prefix or restriction
   // character occurs first, then the heurstic token is null.  We use the
   // heuristic token to help determine the heuristic result.  It may be a Places
   // keyword, a search engine alias, an extension keyword, or simply a URL or
   // part of the search string the user has typed.  We won't know until we
   // create the heuristic result.
@@ -890,22 +903,31 @@ Search.prototype = {
     // searches on every keystroke.
     // Though, if there's no heuristic result, we start searching immediately,
     // since autocomplete may be waiting for us.
     if (hasHeuristic) {
       await this._sleep(UrlbarPrefs.get("delay"));
       if (!this.pending)
         return;
 
-      // If the heuristic result is a search engine result with a token alias
-      // and an empty query, then we're done.  We want to show only that single
-      // result as a clear hint that the user can continue typing to search.
-      if (this._searchEngineAliasMatch &&
-          this._searchEngineAliasMatch.isTokenAlias &&
-          !this._searchEngineAliasMatch.query) {
+      // If the heuristic result is a search engine result with an empty query
+      // and we have either a token alias or the search restriction char, then
+      // we're done.  We want to show only that single result as a clear hint
+      // that the user can continue typing to search.
+      // For the restriction character case, also consider a single char query
+      // or just the char itself, anyway we don't return search suggestions
+      // unless at least 2 chars have been typed. Thus "?__" and "? a" should
+      // finish here, while "?aa" should continue.
+      let emptyQueryTokenAlias = this._searchEngineAliasMatch &&
+                                  this._searchEngineAliasMatch.isTokenAlias &&
+                                  !this._searchEngineAliasMatch.query;
+      let emptySearchRestriction = this._trimmedOriginalSearchString.length <= 3 &&
+                                   this._leadingRestrictionToken == UrlbarTokenizer.RESTRICT.SEARCH &&
+                                   /\s*\S?$/.test(this._trimmedOriginalSearchString);
+      if (emptySearchRestriction || emptyQueryTokenAlias) {
         this._cleanUpNonCurrentMatches(null, false);
         this._autocompleteSearch.finishSearch(true);
         return;
       }
     }
 
     // Only add extension suggestions if the first token is a registered keyword
     // and the search string has characters after the first token.
@@ -1569,20 +1591,21 @@ Search.prototype = {
     return true;
   },
 
   async _matchCurrentSearchEngine() {
     let engine = await PlacesSearchAutocompleteProvider.currentEngine();
     if (!engine || !this.pending) {
       return false;
     }
-    this._addSearchEngineMatch({
-      engine,
-      query: this._originalSearchString,
-    });
+    // Strip a leading restriction char.
+    let query = this._leadingRestrictionToken ?
+      substringAfter(this._trimmedOriginalSearchString, this._leadingRestrictionToken).trim() :
+      this._trimmedOriginalSearchString;
+    this._addSearchEngineMatch({ engine, query });
     return true;
   },
 
   _addExtensionMatch(content, comment) {
     let count = this._counts[UrlbarUtils.MATCH_GROUP.EXTENSION] +
                 this._counts[UrlbarUtils.MATCH_GROUP.HEURISTIC];
     if (count >= UrlbarUtils.MAXIMUM_ALLOWED_EXTENSION_MATCHES) {
       return;
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
@@ -297,30 +297,18 @@ add_task(async function restrictToken() 
     ],
   });
 
   // Now do a restricted search to make sure only suggestions appear.
   await check_autocomplete({
     search: `${UrlbarTokenizer.RESTRICT.SEARCH} hello`,
     searchParam: "enable-actions",
     matches: [
-      // TODO (bug 1177895) This is wrong.
       makeSearchMatch(`${UrlbarTokenizer.RESTRICT.SEARCH} hello`,
-                      { engineName: ENGINE_NAME, heuristic: true }),
-      {
-        uri: makeActionURI(("searchengine"), {
-          engineName: ENGINE_NAME,
-          input: "hello",
-          searchQuery: "hello",
-          searchSuggestion: "hello",
-        }),
-        title: ENGINE_NAME,
-        style: ["action", "searchengine", "suggestion"],
-        icon: "",
-      },
+                      { searchQuery: "hello", engineName: ENGINE_NAME, heuristic: true }),
       {
         uri: makeActionURI(("searchengine"), {
           engineName: ENGINE_NAME,
           input: "hello foo",
           searchQuery: "hello",
           searchSuggestion: "hello foo",
         }),
         title: ENGINE_NAME,
@@ -336,16 +324,63 @@ add_task(async function restrictToken() 
         }),
         title: ENGINE_NAME,
         style: ["action", "searchengine", "suggestion"],
         icon: "",
       },
     ],
   });
 
+  // Typing the search restriction char shows only the Search Engine entry with
+  // no query.
+  await check_autocomplete({
+    search: UrlbarTokenizer.RESTRICT.SEARCH,
+    searchParam: "enable-actions",
+    matches: [
+      makeSearchMatch(UrlbarTokenizer.RESTRICT.SEARCH,
+                      { searchQuery: "", engineName: ENGINE_NAME, heuristic: true }),
+    ],
+  });
+  // Also if followed by multiple spaces.
+  await check_autocomplete({
+    search: UrlbarTokenizer.RESTRICT.SEARCH + "  ",
+    searchParam: "enable-actions",
+    matches: [
+      makeSearchMatch(UrlbarTokenizer.RESTRICT.SEARCH + "  ",
+                      { searchQuery: "", engineName: ENGINE_NAME, heuristic: true }),
+    ],
+  });
+  // Also if followed by a single char.
+  await check_autocomplete({
+    search: UrlbarTokenizer.RESTRICT.SEARCH + "a",
+    searchParam: "enable-actions",
+    matches: [
+      makeSearchMatch(UrlbarTokenizer.RESTRICT.SEARCH + "a",
+                      { searchQuery: "a", engineName: ENGINE_NAME, heuristic: true }),
+    ],
+  });
+  // Also if followed by a space and single char.
+  await check_autocomplete({
+    search: UrlbarTokenizer.RESTRICT.SEARCH + " a",
+    searchParam: "enable-actions",
+    matches: [
+      makeSearchMatch(UrlbarTokenizer.RESTRICT.SEARCH + " a",
+                      { searchQuery: "a", engineName: ENGINE_NAME, heuristic: true }),
+    ],
+  });
+  // Any other restriction char allows to search for it.
+  await check_autocomplete({
+    search: UrlbarTokenizer.RESTRICT.OPENPAGE,
+    searchParam: "enable-actions",
+    matches: [
+      makeSearchMatch(UrlbarTokenizer.RESTRICT.OPENPAGE,
+                      { engineName: ENGINE_NAME, heuristic: true }),
+    ],
+  });
+
   await cleanUpSuggestions();
 });
 
 add_task(async function mixup_frecency() {
   Services.prefs.setBoolPref(SUGGEST_PREF, true);
 
   // Add a visit and a bookmark.  Actually, make the bookmark visited too so
   // that it's guaranteed, with its higher frecency, to appear above the search
--- a/toolkit/components/places/tests/unifiedcomplete/test_tab_matches.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_tab_matches.js
@@ -1,16 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * vim:set ts=2 sw=2 sts=2 et:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var gTabRestrictChar = "%";
-
 add_task(async function test_tab_matches() {
   Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
 
   let uri1 = NetUtil.newURI("http://abc.com/");
   let uri2 = NetUtil.newURI("http://xyz.net/");
   let uri3 = NetUtil.newURI("about:mozilla");
   let uri4 = NetUtil.newURI("data:text/html,test");
   let uri5 = NetUtil.newURI("http://foobar.org");
@@ -125,42 +123,44 @@ add_task(async function test_tab_matches
                { uri: uri1, title: "ABC rocks", style: [ "favicon" ] },
                { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] },
                { uri: uri5, title: "foobar.org - much better than ABC, definitely better than XYZ", style: [ "favicon" ] } ],
   });
 
   info("tab match search with restriction character");
   addOpenPages(uri1, 1);
   await check_autocomplete({
-    search: gTabRestrictChar + " abc",
+    search: UrlbarTokenizer.RESTRICT.OPENPAGE + " abc",
     searchParam: "enable-actions",
-    matches: [ makeSearchMatch(gTabRestrictChar + " abc", { heuristic: true }),
+    matches: [ makeSearchMatch(UrlbarTokenizer.RESTRICT.OPENPAGE + " abc",
+                               { heuristic: true, searchQuery: "abc" }),
                makeSwitchToTabMatch("http://abc.com/", { title: "ABC rocks" }) ],
   });
 
   info("tab match with not-addable pages");
   await check_autocomplete({
     search: "mozilla",
     searchParam: "enable-actions",
     matches: [ makeSearchMatch("mozilla", { heuristic: true }),
                makeSwitchToTabMatch("about:mozilla") ],
   });
 
   info("tab match with not-addable pages and restriction character");
   await check_autocomplete({
-    search: gTabRestrictChar + " mozilla",
+    search: UrlbarTokenizer.RESTRICT.OPENPAGE + " mozilla",
     searchParam: "enable-actions",
-    matches: [ makeSearchMatch(gTabRestrictChar + " mozilla", { heuristic: true }),
+    matches: [ makeSearchMatch(UrlbarTokenizer.RESTRICT.OPENPAGE + " mozilla",
+                               { heuristic: true, searchQuery: "mozilla" }),
                makeSwitchToTabMatch("about:mozilla") ],
   });
 
   info("tab match with not-addable pages and only restriction character");
   await check_autocomplete({
-    search: gTabRestrictChar,
+    search: UrlbarTokenizer.RESTRICT.OPENPAGE,
     searchParam: "enable-actions",
-    matches: [ makeSearchMatch(gTabRestrictChar, { heuristic: true }),
+    matches: [ makeSearchMatch(UrlbarTokenizer.RESTRICT.OPENPAGE, { heuristic: true }),
                makeSwitchToTabMatch("http://abc.com/", { title: "ABC rocks" }),
                makeSwitchToTabMatch("about:mozilla"),
                makeSwitchToTabMatch("data:text/html,test") ],
   });
 
   await cleanup();
 });