Bug 1499743 - Address Bar restriction characters: remove typed, change url and search r=adw
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 24 Oct 2018 12:49:00 +0000
changeset 491121 4ff8166e26697789c95de2b03d45893e4ba90f87
parent 491120 99df2932f5d5d0030c9132337ab117bcc4a0fc99
child 491122 27a7c02c5fdf499664213387949679bf94dafa35
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersadw
bugs1499743
milestone65.0a1
Bug 1499743 - Address Bar restriction characters: remove typed, change url and search r=adw Remove the "~" typed restriction character. Change the url restriction character to "$" and the search one to "?". Differential Revision: https://phabricator.services.mozilla.com/D9494
browser/base/content/test/urlbar/browser_urlbarStopSearchOnSelection.js
browser/base/content/test/urlbar/head.js
browser/components/urlbar/UrlbarPrefs.jsm
browser/components/urlbar/UrlbarTokenizer.jsm
browser/components/urlbar/tests/unit/test_tokenizer.js
toolkit/components/places/UnifiedComplete.js
toolkit/components/places/mozIPlacesAutoComplete.idl
toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
toolkit/components/places/tests/unifiedcomplete/test_empty_search.js
toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
toolkit/components/places/tests/unifiedcomplete/test_special_search.js
--- a/browser/base/content/test/urlbar/browser_urlbarStopSearchOnSelection.js
+++ b/browser/base/content/test/urlbar/browser_urlbarStopSearchOnSelection.js
@@ -29,19 +29,18 @@ add_task(async function init() {
     await PlacesUtils.history.clear();
     // Make sure the popup is closed for the next test.
     gURLBar.blur();
     Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
   });
 });
 
 add_task(async function mainTest() {
-  // Trigger an initial search.  Use the "$" token to restrict matches to search
-  // suggestions.
-  await promiseAutocompleteResultPopup("$ test", window);
+  // Trigger an initial search.  Restrict matches to search suggestions.
+  await promiseAutocompleteResultPopup(`${UrlbarTokenizer.RESTRICT.SEARCH} test`, window);
   await promiseSuggestionsPresent("Waiting for initial suggestions");
 
   // Now synthesize typing a character.  promiseAutocompleteResultPopup doesn't
   // work in this case because it causes the popup to close and re-open with the
   // new input text.
   await new Promise(r => EventUtils.synthesizeKey("x", {}, window, r));
 
   // At this point, a new search starts, the autocomplete's _invalidate is
--- a/browser/base/content/test/urlbar/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -1,22 +1,20 @@
 /* eslint-env mozilla/frame-script */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "PlacesUtils",
-  "resource://gre/modules/PlacesUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
-  "resource://testing-common/PlacesTestUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "Preferences",
-  "resource://gre/modules/Preferences.jsm");
-ChromeUtils.defineModuleGetter(this, "HttpServer",
-  "resource://testing-common/httpd.js");
-ChromeUtils.defineModuleGetter(this, "SearchTestUtils",
-  "resource://testing-common/SearchTestUtils.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  HttpServer: "resource://testing-common/httpd.js",
+  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
+  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm",
+  Preferences: "resource://gre/modules/Preferences.jsm",
+  SearchTestUtils: "resource://testing-common/SearchTestUtils.jsm",
+  UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
+});
 
 SearchTestUtils.init(Assert, registerCleanupFunction);
 
 /**
  * Waits for the next top-level document load in the current browser.  The URI
  * of the document is compared against aExpectedURL.  The load is then stopped
  * before it actually starts.
  *
--- a/browser/components/urlbar/UrlbarPrefs.jsm
+++ b/browser/components/urlbar/UrlbarPrefs.jsm
@@ -126,22 +126,24 @@ const PREF_URLBAR_DEFAULTS = new Map([
   // are styled to look like search engine results instead of the usual history
   // results.
   ["restyleSearches", false],
 ]);
 const PREF_OTHER_DEFAULTS = new Map([
   ["keyword.enabled", true],
 ]);
 
-const TYPES = [
-  "history",
-  "bookmark",
-  "openpage",
-  "searches",
-];
+// Maps preferences under browser.urlbar.suggest to behavior names, as defined
+// in mozIPlacesAutoComplete.
+const SUGGEST_PREF_TO_BEHAVIOR = {
+  history: "history",
+  bookmark: "bookmark",
+  openpage: "openpage",
+  searches: "search",
+};
 
 const PREF_TYPES = new Map([
   ["boolean", "Bool"],
   ["string", "Char"],
   ["number", "Int"],
 ]);
 
 // Buckets for match insertion.
@@ -294,18 +296,19 @@ class Preferences {
         return this.get("matchBuckets");
       }
       case "suggest.history.onlyTyped": {
         // If history is not set, onlyTyped value should be ignored.
         return this.get("suggest.history") && this._readPref(pref);
       }
       case "defaultBehavior": {
         let val = 0;
-        for (let type of [...TYPES, "history.onlyTyped"]) {
-          let behavior = type == "history.onlyTyped" ? "TYPED" : type.toUpperCase();
+        for (let type of [...Object.keys(SUGGEST_PREF_TO_BEHAVIOR), "history.onlyTyped"]) {
+          let behavior = type == "history.onlyTyped" ?
+            "TYPED" : SUGGEST_PREF_TO_BEHAVIOR[type].toUpperCase();
           val |= this.get("suggest." + type) &&
                  Ci.mozIPlacesAutoComplete["BEHAVIOR_" + behavior];
         }
         return val;
       }
       case "emptySearchDefaultBehavior": {
         // Further restrictions to apply for "empty searches" (searching for
         // "").  The empty behavior is typed history, if history is enabled.
@@ -347,32 +350,33 @@ class Preferences {
   _updateLinkedPrefs(changedPref = "") {
     // Avoid re-entrance.
     if (this._linkingPrefs) {
       return;
     }
     this._linkingPrefs = true;
     try {
       let branch = Services.prefs.getBranch(PREF_URLBAR_BRANCH);
+      const SUGGEST_PREFS = Object.keys(SUGGEST_PREF_TO_BEHAVIOR);
       if (changedPref.startsWith("suggest.")) {
         // A suggest pref changed, fix autocomplete.enabled.
         branch.setBoolPref("autocomplete.enabled",
-                          TYPES.some(type => this.get("suggest." + type)));
+                           SUGGEST_PREFS.some(type => this.get("suggest." + type)));
       } else if (this.get("autocomplete.enabled")) {
         // If autocomplete is enabled and all of the suggest.* prefs are
         // disabled, reset the suggest.* prefs to their default value.
-        if (TYPES.every(type => !this.get("suggest." + type))) {
-          for (let type of TYPES) {
+        if (SUGGEST_PREFS.every(type => !this.get("suggest." + type))) {
+          for (let type of SUGGEST_PREFS) {
             let def = PREF_URLBAR_DEFAULTS.get("suggest." + type);
             branch.setBoolPref("suggest." + type, def);
           }
         }
       } else {
         // If autocomplete is disabled, deactivate all suggest preferences.
-        for (let type of TYPES) {
+        for (let type of SUGGEST_PREFS) {
           branch.setBoolPref("suggest." + type, false);
         }
       }
     } finally {
       delete this._linkingPrefs;
     }
   }
 
--- a/browser/components/urlbar/UrlbarTokenizer.jsm
+++ b/browser/components/urlbar/UrlbarTokenizer.jsm
@@ -38,20 +38,35 @@ var UrlbarTokenizer = {
   TYPE: {
     TEXT: 1,
     POSSIBLE_ORIGIN: 2, // It may be an ip, a domain, but even just a single word used as host.
     POSSIBLE_URL: 3, // Consumers should still check this with a fixup.
     RESTRICT_HISTORY: 4,
     RESTRICT_BOOKMARK: 5,
     RESTRICT_TAG: 6,
     RESTRICT_OPENPAGE: 7,
-    RESTRICT_TYPED: 8,
-    RESTRICT_SEARCH: 9,
-    RESTRICT_TITLE: 10,
-    RESTRICT_URL: 11,
+    RESTRICT_SEARCH: 8,
+    RESTRICT_TITLE: 9,
+    RESTRICT_URL: 10,
+  },
+
+  // The special characters below can be typed into the urlbar to restrict
+  // the search to a certain category, like history, bookmarks or open pages; or
+  // to force a match on just the title or url.
+  // These restriction characters can be typed alone, or at word boundaries,
+  // provided their meaning cannot be confused, for example # could be present
+  // in a valid url, and thus it should not be interpreted as a restriction.
+  RESTRICT: {
+    HISTORY: "^",
+    BOOKMARK: "*",
+    TAG: "+",
+    OPENPAGE: "%",
+    SEARCH: "?",
+    TITLE: "#",
+    URL: "$",
   },
 
   /**
    * Returns whether the passed in token looks like a URL.
    * This is based on guessing and heuristics, that means if this function
    * returns false, it's surely not a URL, if it returns true, the result must
    * still be verified through URIFixup.
    *
@@ -163,45 +178,34 @@ var UrlbarTokenizer = {
    * @returns {boolean} Whether the token is a restriction character.
    */
   isRestrictionToken(token) {
     return token.type >= this.TYPE.RESTRICT_HISTORY &&
            token.type <= this.TYPE.RESTRICT_URL;
   },
 };
 
-// The special characters below can be typed into the urlbar to restrict
-// the search to a certain category, like history, bookmarks or open pages; or
-// to force a match on just the title or url.
-// These restriction characters can be typed alone, or at word boundaries,
-// provided their meaning cannot be confused, for example # could be present
-// in a valid url, and thus it should not be interpreted as a restriction.
-UrlbarTokenizer.CHAR_TO_TYPE_MAP = new Map([
-  ["^", UrlbarTokenizer.TYPE.RESTRICT_HISTORY],
-  ["*", UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK],
-  ["+", UrlbarTokenizer.TYPE.RESTRICT_TAG],
-  ["%", UrlbarTokenizer.TYPE.RESTRICT_OPENPAGE],
-  ["~", UrlbarTokenizer.TYPE.RESTRICT_TYPED],
-  ["$", UrlbarTokenizer.TYPE.RESTRICT_SEARCH],
-  ["#", UrlbarTokenizer.TYPE.RESTRICT_TITLE],
-  ["@", UrlbarTokenizer.TYPE.RESTRICT_URL],
-]);
+const CHAR_TO_TYPE_MAP = new Map(
+  Object.entries(UrlbarTokenizer.RESTRICT).map(
+    ([type, char]) => [ char, UrlbarTokenizer.TYPE[`RESTRICT_${type}`] ]
+  )
+);
 
 /**
  * Given a search string, splits it into string tokens.
  * @param {string} searchString
  *        The search string to split
  * @returns {array} An array of string tokens.
  */
 function splitString(searchString) {
   // The first step is splitting on unicode whitespaces.
   let tokens = searchString.trim().split(UrlbarTokenizer.REGEXP_SPACES);
   let accumulator = [];
-  let hasRestrictionToken = tokens.some(t => UrlbarTokenizer.CHAR_TO_TYPE_MAP.has(t));
-  let chars = Array.from(UrlbarTokenizer.CHAR_TO_TYPE_MAP.keys()).join("");
+  let hasRestrictionToken = tokens.some(t => CHAR_TO_TYPE_MAP.has(t));
+  let chars = Array.from(CHAR_TO_TYPE_MAP.keys()).join("");
   logger.debug("Restriction chars", chars);
   for (let token of tokens) {
     // It's possible we have to split a token, if there's no separate restriction
     // character and a token starts or ends with a restriction character, and it's
     // not confusable (for example # at the end of an url.
     // If the token looks like a url, certain characters may appear at the end
     // of the path or the query string, thus ignore those.
     if (!hasRestrictionToken &&
@@ -243,17 +247,17 @@ function filterTokens(tokens) {
     UrlbarTokenizer.TYPE.RESTRICT_TITLE,
     UrlbarTokenizer.TYPE.RESTRICT_URL,
   ]);
   for (let token of tokens) {
     let tokenObj = {
       value: token,
       type: UrlbarTokenizer.TYPE.TEXT,
     };
-    let restrictionType = UrlbarTokenizer.CHAR_TO_TYPE_MAP.get(token);
+    let restrictionType = CHAR_TO_TYPE_MAP.get(token);
     if (tokens.length > 1 &&
         restrictionType &&
         foundRestriction.length == 0 ||
         (foundRestriction.length == 1 &&
          (combinables.has(foundRestriction[0]) && !combinables.has(restrictionType)) ||
          (!combinables.has(foundRestriction[0]) && combinables.has(restrictionType)))) {
       tokenObj.type = restrictionType;
       foundRestriction.push(restrictionType);
--- a/browser/components/urlbar/tests/unit/test_tokenizer.js
+++ b/browser/components/urlbar/tests/unit/test_tokenizer.js
@@ -20,95 +20,103 @@ add_task(async function test_tokenizer()
       expectedTokens: [
         { value: "test1", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
         { value: "test2", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
         { value: "test3", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
         { value: "test4", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
       ],
     },
     { desc: "separate restriction char at beginning",
-      searchString: "* test",
+      searchString: `${UrlbarTokenizer.RESTRICT.BOOKMARK} test`,
       expectedTokens: [
-        { value: "*", type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
         { value: "test", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
       ],
     },
     { desc: "separate restriction char at end",
-      searchString: "test *",
+      searchString: `test ${UrlbarTokenizer.RESTRICT.BOOKMARK}`,
       expectedTokens: [
         { value: "test", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
-        { value: "*", type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
       ],
     },
     { desc: "boundary restriction char at end",
-      searchString: "test*",
+      searchString: `test${UrlbarTokenizer.RESTRICT.BOOKMARK}`,
       expectedTokens: [
         { value: "test", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
-        { value: "*", type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
       ],
     },
     { desc: "double boundary restriction char",
-      searchString: "*test#",
+      searchString: `${UrlbarTokenizer.RESTRICT.BOOKMARK}test${UrlbarTokenizer.RESTRICT.TITLE}`,
       expectedTokens: [
-        { value: "*", type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
-        { value: "test#", type: UrlbarTokenizer.TYPE.TEXT },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
+        { value: `test${UrlbarTokenizer.RESTRICT.TITLE}`, type: UrlbarTokenizer.TYPE.TEXT },
       ],
     },
     { desc: "double non-combinable restriction char, single char string",
-      searchString: "t*$",
+      searchString: `t${UrlbarTokenizer.RESTRICT.BOOKMARK}${UrlbarTokenizer.RESTRICT.SEARCH}`,
       expectedTokens: [
-        { value: "t*", type: UrlbarTokenizer.TYPE.TEXT },
-        { value: "$", type: UrlbarTokenizer.TYPE.RESTRICT_SEARCH },
+        { value: `t${UrlbarTokenizer.RESTRICT.BOOKMARK}`, type: UrlbarTokenizer.TYPE.TEXT },
+        { value: UrlbarTokenizer.RESTRICT.SEARCH, type: UrlbarTokenizer.TYPE.RESTRICT_SEARCH },
       ],
     },
     { desc: "only boundary restriction chars",
-      searchString: "*#",
+      searchString: `${UrlbarTokenizer.RESTRICT.BOOKMARK}${UrlbarTokenizer.RESTRICT.TITLE}`,
       expectedTokens: [
-        { value: "*", type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
-        { value: "#", type: UrlbarTokenizer.TYPE.RESTRICT_TITLE },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
+        { value: UrlbarTokenizer.RESTRICT.TITLE, type: UrlbarTokenizer.TYPE.RESTRICT_TITLE },
       ],
     },
     { desc: "only the boundary restriction char",
-      searchString: "*",
+      searchString: UrlbarTokenizer.RESTRICT.BOOKMARK,
       expectedTokens: [
-        { value: "*", type: UrlbarTokenizer.TYPE.TEXT },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.TEXT },
       ],
     },
-    { desc: "boundary restriction char on path",
+    // Some restriction chars may be # or ?, that are also valid path parts.
+    // The next 2 tests will check we consider those as part of url paths.
+    { desc: "boundary # char on path",
       searchString: "test/#",
       expectedTokens: [
         { value: "test/#", type: UrlbarTokenizer.TYPE.POSSIBLE_URL },
       ],
     },
+    { desc: "boundary ? char on path",
+      searchString: "test/?",
+      expectedTokens: [
+        { value: "test/?", type: UrlbarTokenizer.TYPE.POSSIBLE_URL },
+      ],
+    },
     { desc: "multiple boundary restriction chars suffix",
-      searchString: "test ^ ~",
+      searchString: `test ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.TAG}`,
       expectedTokens: [
         { value: "test", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
-        { value: "^", type: UrlbarTokenizer.TYPE.RESTRICT_HISTORY },
-        { value: "~", type: UrlbarTokenizer.TYPE.TEXT },
+        { value: UrlbarTokenizer.RESTRICT.HISTORY, type: UrlbarTokenizer.TYPE.RESTRICT_HISTORY },
+        { value: UrlbarTokenizer.RESTRICT.TAG, type: UrlbarTokenizer.TYPE.TEXT },
       ],
     },
     { desc: "multiple boundary restriction chars prefix",
-      searchString: "^ ~ test",
+      searchString: `${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.TAG} test`,
       expectedTokens: [
-        { value: "^", type: UrlbarTokenizer.TYPE.RESTRICT_HISTORY },
-        { value: "~", type: UrlbarTokenizer.TYPE.TEXT },
+        { value: UrlbarTokenizer.RESTRICT.HISTORY, type: UrlbarTokenizer.TYPE.RESTRICT_HISTORY },
+        { value: UrlbarTokenizer.RESTRICT.TAG, type: UrlbarTokenizer.TYPE.TEXT },
         { value: "test", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
       ],
     },
     { desc: "Math with division",
       searchString: "3.6/1.2",
       expectedTokens: [
         { value: "3.6/1.2", type: UrlbarTokenizer.TYPE.TEXT },
       ],
     },
     { desc: "ipv4 in bookmarks",
-      searchString: "* 192.168.1.1:8",
+      searchString: `${UrlbarTokenizer.RESTRICT.BOOKMARK} 192.168.1.1:8`,
       expectedTokens: [
-        { value: "*", type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
+        { value: UrlbarTokenizer.RESTRICT.BOOKMARK, type: UrlbarTokenizer.TYPE.RESTRICT_BOOKMARK },
         { value: "192.168.1.1:8", type: UrlbarTokenizer.TYPE.POSSIBLE_ORIGIN },
       ],
     },
     { desc: "email",
       searchString: "test@mozilla.com",
       expectedTokens: [
         { value: "test@mozilla.com", type: UrlbarTokenizer.TYPE.TEXT },
       ],
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -73,30 +73,16 @@ const QUERYINDEX_BOOKMARKED    = 3;
 const QUERYINDEX_BOOKMARKTITLE = 4;
 const QUERYINDEX_TAGS          = 5;
 const QUERYINDEX_VISITCOUNT    = 6;
 const QUERYINDEX_TYPED         = 7;
 const QUERYINDEX_PLACEID       = 8;
 const QUERYINDEX_SWITCHTAB     = 9;
 const QUERYINDEX_FRECENCY      = 10;
 
-// The special characters below can be typed into the urlbar to either restrict
-// the search to visited history, bookmarked, tagged pages; or force a match on
-// just the title text or url.
-const TOKEN_TO_BEHAVIOR_MAP = new Map([
-  ["^", "history"],
-  ["*", "bookmark"],
-  ["+", "tag"],
-  ["%", "openpage"],
-  ["~", "typed"],
-  ["$", "searches"],
-  ["#", "title"],
-  ["@", "url"],
-]);
-
 // If a URL starts with one of these prefixes, then we don't provide search
 // suggestions for it.
 const DISALLOWED_URLLIKE_PREFIXES = [
   "http", "https", "ftp",
 ];
 
 // This SQL query fragment provides the following:
 //   - whether the entry is bookmarked (QUERYINDEX_BOOKMARKED)
@@ -349,22 +335,32 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PlacesSearchAutocompleteProvider: "resource://gre/modules/PlacesSearchAutocompleteProvider.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   ProfileAge: "resource://gre/modules/ProfileAge.jsm",
   Sqlite: "resource://gre/modules/Sqlite.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.jsm",
   UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.jsm",
+  UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "syncUsernamePref",
                                       "services.sync.username");
 
+// The special characters below can be typed into the urlbar to either restrict
+// the search to visited history, bookmarked, tagged pages; or force a match on
+// just the title text or url.
+XPCOMUtils.defineLazyGetter(this, "TOKEN_TO_BEHAVIOR_MAP", () => new Map(
+  Object.entries(UrlbarTokenizer.RESTRICT).map(
+    ([type, char]) => [char, type.toLowerCase()]
+  )
+));
+
 function setTimeout(callback, ms) {
   let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   timer.initWithCallback(callback, ms, timer.TYPE_ONE_SHOT);
   return timer;
 }
 
 const kProtocolsWithIcons = ["chrome:", "moz-extension:", "about:", "http:", "https:", "ftp:"];
 function iconHelper(url) {
@@ -872,17 +868,17 @@ Search.prototype = {
       let query =
         this._searchEngineAliasMatch ? this._searchEngineAliasMatch.query :
         this._searchTokens.join(" ");
       if (query) {
         // Limit the string sent for search suggestions to a maximum length.
         query = query.substr(0, UrlbarPrefs.get("maxCharsForSearchSuggestions"));
         // Avoid fetching suggestions if they are not required, private browsing
         // mode is enabled, or the query may expose sensitive information.
-        if (this.hasBehavior("searches") &&
+        if (this.hasBehavior("search") &&
             !this._inPrivateWindow &&
             !this._prohibitSearchSuggestionsFor(query)) {
           let engine;
           if (this._searchEngineAliasMatch) {
             engine = this._searchEngineAliasMatch.engine;
           } else {
             engine = await PlacesSearchAutocompleteProvider.currentEngine();
             if (!this.pending) {
--- a/toolkit/components/places/mozIPlacesAutoComplete.idl
+++ b/toolkit/components/places/mozIPlacesAutoComplete.idl
@@ -100,17 +100,17 @@ interface mozIPlacesAutoComplete : nsISu
    * Use intersection between history, typed, bookmark, tag and openpage
    * instead of union, when the restrict bit is set.
    */
   const long BEHAVIOR_RESTRICT = 1 << 8;
 
   /**
    * Include search suggestions from the currently selected search provider.
    */
-  const long BEHAVIOR_SEARCHES = 1 << 9;
+  const long BEHAVIOR_SEARCH = 1 << 9;
 
   /**
    * Populate list of Preloaded Sites from JSON.
    *
    * @param sites
    *        Array of [url,title] to populate from.
    */
   void populatePreloadedSiteStorage(in jsval sites);
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
@@ -23,16 +23,17 @@ ChromeUtils.import("resource://testing-c
   XPCOMUtils.defineLazyScriptGetter(this, "addAutofillTasks", uri.spec);
 }
 
 // Put any other stuff relative to this test folder below.
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.jsm",
+  UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
 });
 
 const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 ";
 
 async function cleanup() {
   Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled");
   Services.prefs.clearUserPref("browser.urlbar.autoFill");
   Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines");
--- a/toolkit/components/places/tests/unifiedcomplete/test_empty_search.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_empty_search.js
@@ -51,21 +51,24 @@ add_task(async function test_javascript_
                { uri: uri5, title: "title", style: ["bookmark"] },
                { uri: uri6, title: "title", style: ["bookmark"] },
                makeSwitchToTabMatch("http://t.foo/6", { title: "title" }),
              ],
   });
 
   // Note the next few tests do *not* get a search result as enable-actions
   // isn't specified.
-  info("Match only typed history");
+  info("Match only history");
   await check_autocomplete({
-    search: "foo ^ ~",
-    matches: [ { uri: uri3, title: "title" },
-               { uri: uri4, title: "title" } ],
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY}`,
+    matches: [ { uri: uri1, title: "title" },
+               { uri: uri2, title: "title" },
+               { uri: uri3, title: "title" },
+               { uri: uri4, title: "title" },
+               { uri: uri7, title: "title" } ],
   });
 
   info("Drop-down empty search matches only typed history");
   await check_autocomplete({
     search: "",
     matches: [ { uri: uri3, title: "title" },
                { uri: uri4, title: "title" } ],
   });
--- a/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_search_suggestions.js
@@ -1,16 +1,15 @@
 ChromeUtils.import("resource://gre/modules/FormHistory.jsm");
 
 const ENGINE_NAME = "engine-suggestions.xml";
 // This is fixed to match the port number in engine-suggestions.xml.
 const SERVER_PORT = 9000;
 const SUGGEST_PREF = "browser.urlbar.suggest.searches";
 const SUGGEST_ENABLED_PREF = "browser.search.suggest.enabled";
-const SUGGEST_RESTRICT_TOKEN = "$";
 
 var suggestionsFn;
 var previousSuggestionsFn;
 
 function setSuggestionsFn(fn) {
   previousSuggestionsFn = suggestionsFn;
   suggestionsFn = fn;
 }
@@ -292,21 +291,22 @@ add_task(async function restrictToken() 
         style: ["action", "searchengine", "suggestion"],
         icon: "",
       },
     ],
   });
 
   // Now do a restricted search to make sure only suggestions appear.
   await check_autocomplete({
-    search: SUGGEST_RESTRICT_TOKEN + " hello",
+    search: `${UrlbarTokenizer.RESTRICT.SEARCH} hello`,
     searchParam: "enable-actions",
     matches: [
       // TODO (bug 1177895) This is wrong.
-      makeSearchMatch(SUGGEST_RESTRICT_TOKEN + " hello", { engineName: ENGINE_NAME, heuristic: true }),
+      makeSearchMatch(`${UrlbarTokenizer.RESTRICT.SEARCH} hello`,
+                      { engineName: ENGINE_NAME, heuristic: true }),
       {
         uri: makeActionURI(("searchengine"), {
           engineName: ENGINE_NAME,
           input: "hello foo",
           searchQuery: "hello",
           searchSuggestion: "hello foo",
         }),
         title: ENGINE_NAME,
--- a/toolkit/components/places/tests/unifiedcomplete/test_special_search.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_special_search.js
@@ -44,259 +44,219 @@ add_task(async function test_special_sea
   await addBookmark( { uri: uri9, title: "title", tags: [ "foo.bar" ] } );
   await addBookmark( { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ] } );
   await addBookmark( { uri: uri11, title: "title", tags: [ "foo.bar" ] } );
   await addBookmark( { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ] } );
 
   // Test restricting searches
   info("History restrict");
   await check_autocomplete({
-    search: "^",
+    search: UrlbarTokenizer.RESTRICT.HISTORY,
     matches: [ { uri: uri1, title: "title" },
                { uri: uri2, title: "foo.bar" },
                { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
   info("Star restrict");
   await check_autocomplete({
-    search: "*",
+    search: UrlbarTokenizer.RESTRICT.BOOKMARK,
     matches: [ { uri: uri5, title: "title", style: [ "bookmark" ] },
                { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri7, title: "title", style: [ "bookmark" ] },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar"], style: [ "bookmark-tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
   });
 
   info("Tag restrict");
   await check_autocomplete({
-    search: "+",
+    search: UrlbarTokenizer.RESTRICT.TAG,
     matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
   // Test specials as any word position
   info("Special as first word");
   await check_autocomplete({
-    search: "^ foo bar",
+    search: `${UrlbarTokenizer.RESTRICT.HISTORY} foo bar`,
     matches: [ { uri: uri2, title: "foo.bar" },
                { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
   info("Special as middle word");
   await check_autocomplete({
-    search: "foo ^ bar",
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY} bar`,
     matches: [ { uri: uri2, title: "foo.bar" },
                { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
   info("Special as last word");
   await check_autocomplete({
-    search: "foo bar ^",
+    search: `foo bar ${UrlbarTokenizer.RESTRICT.HISTORY}`,
     matches: [ { uri: uri2, title: "foo.bar" },
                { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
   // Test restricting and matching searches with a term
-  info("foo ^ -> history");
+  info(`foo ${UrlbarTokenizer.RESTRICT.HISTORY} -> history`);
   await check_autocomplete({
-    search: "foo ^",
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY}`,
     matches: [ { uri: uri2, title: "foo.bar" },
                { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo * -> is star");
+  info(`foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} -> is star`);
   await check_autocomplete({
-    search: "foo *",
+    search: `foo ${UrlbarTokenizer.RESTRICT.BOOKMARK}`,
     matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri7, title: "title", style: [ "bookmark" ] },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
   });
 
-  info("foo # -> in title");
+  info(`foo ${UrlbarTokenizer.RESTRICT.TITLE} -> in title`);
   await check_autocomplete({
-    search: "foo #",
+    search: `foo ${UrlbarTokenizer.RESTRICT.TITLE}`,
     matches: [ { uri: uri2, title: "foo.bar" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo @ -> in url");
+  info(`foo ${UrlbarTokenizer.RESTRICT.URL} -> in url`);
   await check_autocomplete({
-    search: "foo @",
+    search: `foo ${UrlbarTokenizer.RESTRICT.URL}`,
     matches: [ { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri7, title: "title", style: [ "bookmark" ] },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo + -> is tag");
+  info(`foo ${UrlbarTokenizer.RESTRICT.TAG} -> is tag`);
   await check_autocomplete({
-    search: "foo +",
+    search: `foo ${UrlbarTokenizer.RESTRICT.TAG}`,
     matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo ~ -> is typed");
+  // Test various pairs of special searches
+  info(`foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.BOOKMARK} -> history, is star`);
   await check_autocomplete({
-    search: "foo ~",
-    matches: [ { uri: uri4, title: "foo.bar" },
-               { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
-  });
-
-  // Test various pairs of special searches
-  info("foo ^ * -> history, is star");
-  await check_autocomplete({
-    search: "foo ^ *",
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.BOOKMARK}`,
     matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
   });
 
-  info("foo ^ # -> history, in title");
+  info(`foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.TITLE} -> history, in title`);
   await check_autocomplete({
-    search: "foo ^ #",
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.TITLE}`,
     matches: [ { uri: uri2, title: "foo.bar" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri6, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo ^ @ -> history, in url");
+  info(`foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.URL} -> history, in url`);
   await check_autocomplete({
-    search: "foo ^ @",
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.URL}`,
     matches: [ { uri: uri3, title: "title" },
                { uri: uri4, title: "foo.bar" },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo ^ + -> history, is tag");
+  info(`foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.TAG} -> history, is tag`);
   await check_autocomplete({
-    search: "foo ^ +",
+    search: `foo ${UrlbarTokenizer.RESTRICT.HISTORY} ${UrlbarTokenizer.RESTRICT.TAG}`,
     matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo ^ ~ -> history, is typed");
+  info(`foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} ${UrlbarTokenizer.RESTRICT.TITLE} -> is star, in title`);
   await check_autocomplete({
-    search: "foo ^ ~",
-    matches: [ { uri: uri4, title: "foo.bar" },
-               { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
-  });
-
-  info("foo * # -> is star, in title");
-  await check_autocomplete({
-    search: "foo * #",
+    search: `foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} ${UrlbarTokenizer.RESTRICT.TITLE}`,
     matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
   });
 
-  info("foo * @ -> is star, in url");
+  info(`foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} ${UrlbarTokenizer.RESTRICT.URL} -> is star, in url`);
   await check_autocomplete({
-    search: "foo * @",
+    search: `foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} ${UrlbarTokenizer.RESTRICT.URL}`,
     matches: [ { uri: uri7, title: "title", style: [ "bookmark" ] },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
   });
 
-  info("foo * + -> same as +");
+  info(`foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} ${UrlbarTokenizer.RESTRICT.TAG} -> same as ${UrlbarTokenizer.RESTRICT.TAG}`);
   await check_autocomplete({
-    search: "foo * +",
+    search: `foo ${UrlbarTokenizer.RESTRICT.BOOKMARK} ${UrlbarTokenizer.RESTRICT.TAG}`,
     matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
   });
 
-  info("foo * ~ -> is star, is typed");
+  info(`foo ${UrlbarTokenizer.RESTRICT.TITLE} ${UrlbarTokenizer.RESTRICT.URL} -> in title, in url`);
   await check_autocomplete({
-    search: "foo * ~",
-    matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ],
-  });
-
-  info("foo # @ -> in title, in url");
-  await check_autocomplete({
-    search: "foo # @",
+    search: `foo ${UrlbarTokenizer.RESTRICT.TITLE} ${UrlbarTokenizer.RESTRICT.URL}`,
     matches: [ { uri: uri4, title: "foo.bar" },
                { uri: uri8, title: "foo.bar", style: [ "bookmark" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo # + -> in title, is tag");
+  info(`foo ${UrlbarTokenizer.RESTRICT.TITLE} ${UrlbarTokenizer.RESTRICT.TAG} -> in title, is tag`);
   await check_autocomplete({
-    search: "foo # +",
+    search: `foo ${UrlbarTokenizer.RESTRICT.TITLE} ${UrlbarTokenizer.RESTRICT.TAG}`,
     matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri10, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo # ~ -> in title, is typed");
+  info(`foo ${UrlbarTokenizer.RESTRICT.URL} ${UrlbarTokenizer.RESTRICT.TAG} -> in url, is tag`);
   await check_autocomplete({
-    search: "foo # ~",
-    matches: [ { uri: uri4, title: "foo.bar" },
-               { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
-  });
-
-  info("foo @ + -> in url, is tag");
-  await check_autocomplete({
-    search: "foo @ +",
+    search: `foo ${UrlbarTokenizer.RESTRICT.URL} ${UrlbarTokenizer.RESTRICT.TAG}`,
     matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] },
                { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ],
   });
 
-  info("foo @ ~ -> in url, is typed");
-  await check_autocomplete({
-    search: "foo @ ~",
-    matches: [ { uri: uri4, title: "foo.bar" },
-               { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
-  });
-
-  info("foo + ~ -> is tag, is typed");
-  await check_autocomplete({
-    search: "foo + ~",
-    matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ],
-  });
-
   // Disable autoFill for the next tests, see test_autoFill_default_behavior.js
   // for specific tests.
   Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
 
   // Test default usage by setting certain browser.urlbar.suggest.* prefs
   info("foo -> default history");
   setSuggestPrefsToFalse();
   Services.prefs.setBoolPref("browser.urlbar.suggest.history", true);