tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_search_suggestions.js (64945B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /**
      5 * Tests that search engine suggestions are returned by
      6 * UrlbarProviderSearchSuggestions.
      7 */
      8 
      9 const lazy = {};
     10 
     11 ChromeUtils.defineESModuleGetters(lazy, {
     12  sinon: "resource://testing-common/Sinon.sys.mjs",
     13 });
     14 
     15 const SUGGEST_PREF = "browser.urlbar.suggest.searches";
     16 const SUGGEST_ENABLED_PREF = "browser.search.suggest.enabled";
     17 const PRIVATE_ENABLED_PREF = "browser.search.suggest.enabled.private";
     18 const PRIVATE_SEARCH_PREF = "browser.search.separatePrivateDefault.ui.enabled";
     19 const TAB_TO_SEARCH_PREF = "browser.urlbar.suggest.engines";
     20 const TRENDING_PREF = "browser.urlbar.trending.featureGate";
     21 const QUICKACTIONS_PREF = "browser.urlbar.suggest.quickactions";
     22 const MAX_RICH_RESULTS_PREF = "browser.urlbar.maxRichResults";
     23 const MAX_FORM_HISTORY_PREF = "browser.urlbar.maxHistoricalSearchSuggestions";
     24 const SHOW_SEARCH_SUGGESTIONS_FIRST_PREF =
     25  "browser.urlbar.showSearchSuggestionsFirst";
     26 const SEARCH_STRING = "hello";
     27 
     28 const MAX_RESULTS = Services.prefs.getIntPref(MAX_RICH_RESULTS_PREF, 10);
     29 
     30 var suggestionsFn;
     31 var previousSuggestionsFn;
     32 let port;
     33 let sandbox;
     34 
     35 /**
     36 * Set the current suggestion funciton.
     37 *
     38 * @param {Function} fn
     39 *   A function that that a search string and returns an array of strings that
     40 *   will be used as search suggestions.
     41 *   Note: `fn` should return > 0 suggestions in most cases. Otherwise, you may
     42 *         encounter unexpected behaviour with UrlbarProviderSuggestion's
     43 *         _lastLowResultsSearchSuggestion safeguard.
     44 */
     45 function setSuggestionsFn(fn) {
     46  previousSuggestionsFn = suggestionsFn;
     47  suggestionsFn = fn;
     48 }
     49 
     50 async function cleanup() {
     51  Services.prefs.clearUserPref("browser.urlbar.autoFill");
     52  Services.prefs.clearUserPref(SUGGEST_PREF);
     53  Services.prefs.clearUserPref(SUGGEST_ENABLED_PREF);
     54  await PlacesUtils.bookmarks.eraseEverything();
     55  await PlacesUtils.history.clear();
     56  sandbox.restore();
     57 }
     58 
     59 async function cleanUpSuggestions() {
     60  await cleanup();
     61  if (previousSuggestionsFn) {
     62    suggestionsFn = previousSuggestionsFn;
     63    previousSuggestionsFn = null;
     64  }
     65 }
     66 
     67 function makeFormHistoryResults(context, count) {
     68  let results = [];
     69  for (let i = 0; i < count; i++) {
     70    results.push(
     71      makeFormHistoryResult(context, {
     72        suggestion: `${SEARCH_STRING} world Form History ${i}`,
     73        engineName: SUGGESTIONS_ENGINE_NAME,
     74      })
     75    );
     76  }
     77  return results;
     78 }
     79 
     80 function makeRemoteSuggestionResults(
     81  context,
     82  { suggestionPrefix = SEARCH_STRING, query = undefined } = {}
     83 ) {
     84  // The suggestions function in `setup` returns:
     85  // [searchString, searchString + "foo", searchString + "bar"]
     86  // But when the heuristic is a search result, the muxer discards suggestion
     87  // results that match the search string, and therefore we expect only two
     88  // remote suggestion results, the "foo" and "bar" ones.
     89  return [
     90    makeSearchResult(context, {
     91      query,
     92      engineName: SUGGESTIONS_ENGINE_NAME,
     93      suggestion: suggestionPrefix + " foo",
     94    }),
     95    makeSearchResult(context, {
     96      query,
     97      engineName: SUGGESTIONS_ENGINE_NAME,
     98      suggestion: suggestionPrefix + " bar",
     99    }),
    100  ];
    101 }
    102 
    103 function setResultGroups(groups) {
    104  sandbox.restore();
    105  sandbox.stub(UrlbarPrefs, "getResultGroups").returns({
    106    children: [
    107      // heuristic
    108      {
    109        maxResultCount: 1,
    110        children: [
    111          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TEST },
    112          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_EXTENSION },
    113          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_SEARCH_TIP },
    114          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_OMNIBOX },
    115          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_AUTOFILL },
    116          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_TOKEN_ALIAS_ENGINE },
    117          { group: UrlbarUtils.RESULT_GROUP.HEURISTIC_FALLBACK },
    118        ],
    119      },
    120      // extensions using the omnibox API
    121      {
    122        group: UrlbarUtils.RESULT_GROUP.OMNIBOX,
    123      },
    124      ...groups,
    125    ],
    126  });
    127 }
    128 
    129 add_setup(async function () {
    130  sandbox = lazy.sinon.createSandbox();
    131 
    132  let engine = await addTestSuggestionsEngine(searchStr => {
    133    return suggestionsFn(searchStr);
    134  });
    135  port = engine.getSubmission("").uri.port;
    136 
    137  setSuggestionsFn(searchStr => {
    138    let suffixes = ["foo", "bar"];
    139    return [searchStr].concat(suffixes.map(s => searchStr + " " + s));
    140  });
    141 
    142  // Install the test engine.
    143  let oldDefaultEngine = await Services.search.getDefault();
    144  registerCleanupFunction(async () => {
    145    Services.search.setDefault(
    146      oldDefaultEngine,
    147      Ci.nsISearchService.CHANGE_REASON_UNKNOWN
    148    );
    149    Services.prefs.clearUserPref(PRIVATE_SEARCH_PREF);
    150    Services.prefs.clearUserPref(TRENDING_PREF);
    151    Services.prefs.clearUserPref(QUICKACTIONS_PREF);
    152    Services.prefs.clearUserPref(TAB_TO_SEARCH_PREF);
    153    sandbox.restore();
    154  });
    155  Services.search.setDefault(engine, Ci.nsISearchService.CHANGE_REASON_UNKNOWN);
    156  Services.prefs.setBoolPref(PRIVATE_SEARCH_PREF, false);
    157  Services.prefs.setBoolPref(TRENDING_PREF, false);
    158  Services.prefs.setBoolPref(QUICKACTIONS_PREF, false);
    159  // Tab-to-search engines can introduce unexpected results, espescially because
    160  // they depend on real en-US engines.
    161  Services.prefs.setBoolPref(TAB_TO_SEARCH_PREF, false);
    162 
    163  // Add MAX_RESULTS form history.
    164  let context = createContext(SEARCH_STRING, { isPrivate: false });
    165  let entries = makeFormHistoryResults(context, MAX_RESULTS).map(r => ({
    166    value: r.payload.suggestion,
    167    source: SUGGESTIONS_ENGINE_NAME,
    168  }));
    169  await UrlbarTestUtils.formHistory.add(entries);
    170 });
    171 
    172 add_task(async function disabled_urlbarSuggestions() {
    173  Services.prefs.setBoolPref(SUGGEST_PREF, false);
    174  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    175  let context = createContext(SEARCH_STRING, { isPrivate: false });
    176  await check_results({
    177    context,
    178    matches: [
    179      makeSearchResult(context, {
    180        engineName: SUGGESTIONS_ENGINE_NAME,
    181        heuristic: true,
    182      }),
    183    ],
    184  });
    185  await cleanUpSuggestions();
    186 });
    187 
    188 add_task(async function disabled_allSuggestions() {
    189  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    190  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, false);
    191  let context = createContext(SEARCH_STRING, { isPrivate: false });
    192  await check_results({
    193    context,
    194    matches: [
    195      makeSearchResult(context, {
    196        engineName: SUGGESTIONS_ENGINE_NAME,
    197        heuristic: true,
    198      }),
    199    ],
    200  });
    201  await cleanUpSuggestions();
    202 });
    203 
    204 add_task(async function disabled_privateWindow() {
    205  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    206  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    207  Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, false);
    208  let context = createContext(SEARCH_STRING, { isPrivate: true });
    209  await check_results({
    210    context,
    211    matches: [
    212      makeSearchResult(context, {
    213        engineName: SUGGESTIONS_ENGINE_NAME,
    214        heuristic: true,
    215      }),
    216    ],
    217  });
    218  await cleanUpSuggestions();
    219 });
    220 
    221 add_task(async function disabled_urlbarSuggestions_withRestrictionToken() {
    222  Services.prefs.setBoolPref(SUGGEST_PREF, false);
    223  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    224  let context = createContext(
    225    `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`,
    226    { isPrivate: false }
    227  );
    228  await check_results({
    229    context,
    230    matches: [
    231      makeSearchResult(context, {
    232        query: SEARCH_STRING,
    233        alias: UrlbarTokenizer.RESTRICT.SEARCH,
    234        engineName: SUGGESTIONS_ENGINE_NAME,
    235        heuristic: true,
    236      }),
    237      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    238      ...makeRemoteSuggestionResults(context, {
    239        query: SEARCH_STRING,
    240      }),
    241    ],
    242  });
    243  await cleanUpSuggestions();
    244 });
    245 
    246 add_task(
    247  async function disabled_urlbarSuggestions_withRestrictionToken_private() {
    248    Services.prefs.setBoolPref(SUGGEST_PREF, false);
    249    Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    250    Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, false);
    251    let context = createContext(
    252      `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`,
    253      { isPrivate: true }
    254    );
    255    await check_results({
    256      context,
    257      matches: [
    258        makeSearchResult(context, {
    259          query: SEARCH_STRING,
    260          alias: UrlbarTokenizer.RESTRICT.SEARCH,
    261          engineName: SUGGESTIONS_ENGINE_NAME,
    262          heuristic: true,
    263        }),
    264      ],
    265    });
    266    await cleanUpSuggestions();
    267  }
    268 );
    269 
    270 add_task(
    271  async function disabled_urlbarSuggestions_withRestrictionToken_private_enabled() {
    272    Services.prefs.setBoolPref(SUGGEST_PREF, false);
    273    Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    274    Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, true);
    275    let context = createContext(
    276      `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`,
    277      { isPrivate: true }
    278    );
    279    await check_results({
    280      context,
    281      matches: [
    282        makeSearchResult(context, {
    283          query: SEARCH_STRING,
    284          alias: UrlbarTokenizer.RESTRICT.SEARCH,
    285          engineName: SUGGESTIONS_ENGINE_NAME,
    286          heuristic: true,
    287        }),
    288        ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    289        ...makeRemoteSuggestionResults(context, {
    290          query: SEARCH_STRING,
    291        }),
    292      ],
    293    });
    294    await cleanUpSuggestions();
    295  }
    296 );
    297 
    298 add_task(async function enabled_by_pref_privateWindow() {
    299  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    300  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    301  Services.prefs.setBoolPref(PRIVATE_ENABLED_PREF, true);
    302  let context = createContext(SEARCH_STRING, { isPrivate: true });
    303  await check_results({
    304    context,
    305    matches: [
    306      makeSearchResult(context, {
    307        engineName: SUGGESTIONS_ENGINE_NAME,
    308        heuristic: true,
    309      }),
    310      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    311      ...makeRemoteSuggestionResults(context),
    312    ],
    313  });
    314  await cleanUpSuggestions();
    315 
    316  Services.prefs.clearUserPref(PRIVATE_ENABLED_PREF);
    317 });
    318 
    319 add_task(async function singleWordQuery() {
    320  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    321  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    322  let context = createContext(SEARCH_STRING, { isPrivate: false });
    323 
    324  await check_results({
    325    context,
    326    matches: [
    327      makeSearchResult(context, {
    328        engineName: SUGGESTIONS_ENGINE_NAME,
    329        heuristic: true,
    330      }),
    331      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    332      ...makeRemoteSuggestionResults(context),
    333    ],
    334  });
    335 
    336  await cleanUpSuggestions();
    337 });
    338 
    339 add_task(async function multiWordQuery() {
    340  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    341  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    342  const query = `${SEARCH_STRING} world`;
    343  let context = createContext(query, { isPrivate: false });
    344  await check_results({
    345    context,
    346    matches: [
    347      makeSearchResult(context, {
    348        engineName: SUGGESTIONS_ENGINE_NAME,
    349        heuristic: true,
    350      }),
    351      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    352      ...makeRemoteSuggestionResults(context, {
    353        suggestionPrefix: query,
    354      }),
    355    ],
    356  });
    357 
    358  await cleanUpSuggestions();
    359 });
    360 
    361 add_task(async function suffixMatch() {
    362  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    363  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    364 
    365  setSuggestionsFn(searchStr => {
    366    let prefixes = ["baz", "quux"];
    367    return prefixes.map(p => p + " " + searchStr);
    368  });
    369 
    370  let context = createContext(SEARCH_STRING, { isPrivate: false });
    371 
    372  await check_results({
    373    context,
    374    matches: [
    375      makeSearchResult(context, {
    376        engineName: SUGGESTIONS_ENGINE_NAME,
    377        heuristic: true,
    378      }),
    379      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    380      makeSearchResult(context, {
    381        engineName: SUGGESTIONS_ENGINE_NAME,
    382        suggestion: "baz " + SEARCH_STRING,
    383      }),
    384      makeSearchResult(context, {
    385        engineName: SUGGESTIONS_ENGINE_NAME,
    386        suggestion: "quux " + SEARCH_STRING,
    387      }),
    388    ],
    389  });
    390 
    391  await cleanUpSuggestions();
    392 });
    393 
    394 add_task(async function remoteSuggestionsDupeSearchString() {
    395  Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 0);
    396 
    397  // Return remote suggestions with the trimmed search string, the uppercased
    398  // search string, and the search string with a trailing space, plus the usual
    399  // "foo" and "bar" suggestions.
    400  setSuggestionsFn(searchStr => {
    401    let suffixes = ["foo", "bar"];
    402    return [searchStr.trim(), searchStr.toUpperCase(), searchStr + " "].concat(
    403      suffixes.map(s => searchStr + " " + s)
    404    );
    405  });
    406 
    407  // Do a search with a trailing space.  All the variations of the search string
    408  // with regard to spaces and case should be discarded from the remote
    409  // suggestions, leaving only the usual "foo" and "bar" suggestions.
    410  let query = SEARCH_STRING + " ";
    411  let context = createContext(query, { isPrivate: false });
    412  await check_results({
    413    context,
    414    matches: [
    415      makeSearchResult(context, {
    416        query,
    417        engineName: SUGGESTIONS_ENGINE_NAME,
    418        heuristic: true,
    419      }),
    420      ...makeRemoteSuggestionResults(context),
    421    ],
    422  });
    423 
    424  await cleanUpSuggestions();
    425  Services.prefs.clearUserPref(MAX_FORM_HISTORY_PREF);
    426 });
    427 
    428 add_task(async function queryIsNotASubstring() {
    429  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    430 
    431  setSuggestionsFn(() => {
    432    return ["aaa", "bbb"];
    433  });
    434 
    435  let context = createContext(SEARCH_STRING, { isPrivate: false });
    436 
    437  await check_results({
    438    context,
    439    matches: [
    440      makeSearchResult(context, {
    441        engineName: SUGGESTIONS_ENGINE_NAME,
    442        heuristic: true,
    443      }),
    444      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    445      makeSearchResult(context, {
    446        engineName: SUGGESTIONS_ENGINE_NAME,
    447        suggestion: "aaa",
    448      }),
    449      makeSearchResult(context, {
    450        engineName: SUGGESTIONS_ENGINE_NAME,
    451        suggestion: "bbb",
    452      }),
    453    ],
    454  });
    455 
    456  await cleanUpSuggestions();
    457 });
    458 
    459 add_task(async function restrictToken() {
    460  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    461  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
    462 
    463  // Add a visit and a bookmark.  Actually, make the bookmark visited too so
    464  // that it's guaranteed, with its higher frecency, to appear above the search
    465  // suggestions.
    466  await PlacesTestUtils.addVisits([
    467    {
    468      uri: Services.io.newURI(`http://example.com/${SEARCH_STRING}-visit`),
    469      title: `${SEARCH_STRING} visit`,
    470    },
    471    {
    472      uri: Services.io.newURI(`http://example.com/${SEARCH_STRING}-bookmark`),
    473      title: `${SEARCH_STRING} bookmark`,
    474    },
    475  ]);
    476 
    477  await PlacesTestUtils.addBookmarkWithDetails({
    478    uri: Services.io.newURI(`http://example.com/${SEARCH_STRING}-bookmark`),
    479    title: `${SEARCH_STRING} bookmark`,
    480  });
    481 
    482  let context = createContext(SEARCH_STRING, { isPrivate: false });
    483 
    484  // Do an unrestricted search to make sure everything appears in it, including
    485  // the visit and bookmark.
    486  await check_results({
    487    context,
    488    matches: [
    489      makeSearchResult(context, {
    490        engineName: SUGGESTIONS_ENGINE_NAME,
    491        heuristic: true,
    492      }),
    493      ...makeFormHistoryResults(context, MAX_RESULTS - 5),
    494      ...makeRemoteSuggestionResults(context),
    495      makeBookmarkResult(context, {
    496        uri: `http://example.com/${SEARCH_STRING}-bookmark`,
    497        title: `${SEARCH_STRING} bookmark`,
    498      }),
    499      makeVisitResult(context, {
    500        uri: `http://example.com/${SEARCH_STRING}-visit`,
    501        title: `${SEARCH_STRING} visit`,
    502      }),
    503    ],
    504  });
    505 
    506  // Now do a restricted search to make sure only suggestions appear.
    507  context = createContext(
    508    `${UrlbarTokenizer.RESTRICT.SEARCH} ${SEARCH_STRING}`,
    509    {
    510      isPrivate: false,
    511    }
    512  );
    513  await check_results({
    514    context,
    515    matches: [
    516      makeSearchResult(context, {
    517        engineName: SUGGESTIONS_ENGINE_NAME,
    518        alias: UrlbarTokenizer.RESTRICT.SEARCH,
    519        query: SEARCH_STRING,
    520        heuristic: true,
    521      }),
    522      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    523      ...makeRemoteSuggestionResults(context, {
    524        suggestionPrefix: SEARCH_STRING,
    525        query: SEARCH_STRING,
    526      }),
    527    ],
    528  });
    529 
    530  // Typing the search restriction char shows the Search Engine entry and local
    531  // results.
    532  context = createContext(UrlbarTokenizer.RESTRICT.SEARCH, {
    533    isPrivate: false,
    534  });
    535  await check_results({
    536    context,
    537    matches: [
    538      makeSearchResult(context, {
    539        engineName: SUGGESTIONS_ENGINE_NAME,
    540        query: "",
    541        heuristic: true,
    542      }),
    543      ...makeFormHistoryResults(context, MAX_RESULTS - 1),
    544    ],
    545  });
    546 
    547  // Also if followed by multiple spaces.
    548  context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH}  `, {
    549    isPrivate: false,
    550  });
    551  await check_results({
    552    context,
    553    matches: [
    554      makeSearchResult(context, {
    555        engineName: SUGGESTIONS_ENGINE_NAME,
    556        alias: UrlbarTokenizer.RESTRICT.SEARCH,
    557        query: "",
    558        heuristic: true,
    559      }),
    560      ...makeFormHistoryResults(context, MAX_RESULTS - 1),
    561    ],
    562  });
    563 
    564  // If followed by any char we should fetch suggestions.
    565  // Note this uses "h" to match form history.
    566  context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH}h`, {
    567    isPrivate: false,
    568  });
    569  await check_results({
    570    context,
    571    matches: [
    572      makeSearchResult(context, {
    573        engineName: SUGGESTIONS_ENGINE_NAME,
    574        query: "h",
    575        heuristic: true,
    576      }),
    577      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    578      ...makeRemoteSuggestionResults(context, {
    579        suggestionPrefix: "h",
    580        query: "h",
    581      }),
    582    ],
    583  });
    584 
    585  // Also if followed by a space and single char.
    586  context = createContext(`${UrlbarTokenizer.RESTRICT.SEARCH} h`, {
    587    isPrivate: false,
    588  });
    589  await check_results({
    590    context,
    591    matches: [
    592      makeSearchResult(context, {
    593        engineName: SUGGESTIONS_ENGINE_NAME,
    594        alias: UrlbarTokenizer.RESTRICT.SEARCH,
    595        query: "h",
    596        heuristic: true,
    597      }),
    598      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    599      ...makeRemoteSuggestionResults(context, {
    600        suggestionPrefix: "h",
    601        query: "h",
    602      }),
    603    ],
    604  });
    605 
    606  // Leading search-mode restriction tokens are removed.
    607  context = createContext(
    608    `${UrlbarTokenizer.RESTRICT.BOOKMARK} ${SEARCH_STRING}`,
    609    { isPrivate: false }
    610  );
    611  await check_results({
    612    context,
    613    matches: [
    614      makeSearchResult(context, {
    615        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    616        heuristic: true,
    617        query: SEARCH_STRING,
    618        alias: UrlbarTokenizer.RESTRICT.BOOKMARK,
    619      }),
    620      makeBookmarkResult(context, {
    621        uri: `http://example.com/${SEARCH_STRING}-bookmark`,
    622        title: `${SEARCH_STRING} bookmark`,
    623      }),
    624    ],
    625  });
    626 
    627  // Non-search-mode restriction tokens remain in the query and heuristic search
    628  // result.
    629  let token;
    630  for (let t of Object.values(UrlbarTokenizer.RESTRICT)) {
    631    if (!UrlbarTokenizer.SEARCH_MODE_RESTRICT.has(t)) {
    632      token = t;
    633      break;
    634    }
    635  }
    636  Assert.ok(
    637    token,
    638    "Non-search-mode restrict token exists -- if not, you can probably remove me!"
    639  );
    640  context = createContext(token, {
    641    isPrivate: false,
    642  });
    643  await check_results({
    644    context,
    645    matches: [
    646      makeSearchResult(context, {
    647        engineName: SUGGESTIONS_ENGINE_NAME,
    648        heuristic: true,
    649      }),
    650    ],
    651  });
    652 
    653  await cleanUpSuggestions();
    654 });
    655 
    656 add_task(async function mixup_frecency() {
    657  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    658 
    659  // At most, we should have 22 results in this subtest. We set this to 30 to
    660  // make we're not cutting off any results and we are actually getting 22.
    661  Services.prefs.setIntPref(MAX_RICH_RESULTS_PREF, 30);
    662 
    663  // Add a visit and a bookmark.  Actually, make the bookmark visited too so
    664  // that it's guaranteed, with its higher frecency, to appear above the search
    665  // suggestions.
    666  await PlacesTestUtils.addVisits([
    667    {
    668      uri: Services.io.newURI("http://example.com/lo0"),
    669      title: `${SEARCH_STRING} low frecency 0`,
    670    },
    671    {
    672      uri: Services.io.newURI("http://example.com/lo1"),
    673      title: `${SEARCH_STRING} low frecency 1`,
    674    },
    675    {
    676      uri: Services.io.newURI("http://example.com/lo2"),
    677      title: `${SEARCH_STRING} low frecency 2`,
    678    },
    679    {
    680      uri: Services.io.newURI("http://example.com/lo3"),
    681      title: `${SEARCH_STRING} low frecency 3`,
    682    },
    683    {
    684      uri: Services.io.newURI("http://example.com/lo4"),
    685      title: `${SEARCH_STRING} low frecency 4`,
    686    },
    687  ]);
    688 
    689  for (let i = 0; i < 5; i++) {
    690    await PlacesTestUtils.addVisits([
    691      {
    692        uri: Services.io.newURI("http://example.com/hi0"),
    693        title: `${SEARCH_STRING} high frecency 0`,
    694        transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
    695      },
    696      {
    697        uri: Services.io.newURI("http://example.com/hi1"),
    698        title: `${SEARCH_STRING} high frecency 1`,
    699        transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
    700      },
    701      {
    702        uri: Services.io.newURI("http://example.com/hi2"),
    703        title: `${SEARCH_STRING} high frecency 2`,
    704        transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
    705      },
    706      {
    707        uri: Services.io.newURI("http://example.com/hi3"),
    708        title: `${SEARCH_STRING} high frecency 3`,
    709        transition: Ci.nsINavHistoryService.TRANSITION_TYPED,
    710      },
    711    ]);
    712  }
    713 
    714  for (let i = 0; i < 4; i++) {
    715    let href = `http://example.com/hi${i}`;
    716    await PlacesTestUtils.addBookmarkWithDetails({
    717      uri: href,
    718      title: `${SEARCH_STRING} high frecency ${i}`,
    719    });
    720  }
    721 
    722  // Do an unrestricted search to make sure everything appears in it, including
    723  // the visit and bookmark.
    724  let context = createContext(SEARCH_STRING, { isPrivate: false });
    725  await check_results({
    726    context,
    727    matches: [
    728      makeSearchResult(context, {
    729        engineName: SUGGESTIONS_ENGINE_NAME,
    730        heuristic: true,
    731      }),
    732      ...makeFormHistoryResults(context, MAX_RESULTS),
    733      ...makeRemoteSuggestionResults(context),
    734      makeBookmarkResult(context, {
    735        uri: "http://example.com/hi3",
    736        title: `${SEARCH_STRING} high frecency 3`,
    737      }),
    738      makeBookmarkResult(context, {
    739        uri: "http://example.com/hi2",
    740        title: `${SEARCH_STRING} high frecency 2`,
    741      }),
    742      makeBookmarkResult(context, {
    743        uri: "http://example.com/hi1",
    744        title: `${SEARCH_STRING} high frecency 1`,
    745      }),
    746      makeBookmarkResult(context, {
    747        uri: "http://example.com/hi0",
    748        title: `${SEARCH_STRING} high frecency 0`,
    749      }),
    750      makeVisitResult(context, {
    751        uri: "http://example.com/lo4",
    752        title: `${SEARCH_STRING} low frecency 4`,
    753      }),
    754      makeVisitResult(context, {
    755        uri: "http://example.com/lo3",
    756        title: `${SEARCH_STRING} low frecency 3`,
    757      }),
    758      makeVisitResult(context, {
    759        uri: "http://example.com/lo2",
    760        title: `${SEARCH_STRING} low frecency 2`,
    761      }),
    762      makeVisitResult(context, {
    763        uri: "http://example.com/lo1",
    764        title: `${SEARCH_STRING} low frecency 1`,
    765      }),
    766      makeVisitResult(context, {
    767        uri: "http://example.com/lo0",
    768        title: `${SEARCH_STRING} low frecency 0`,
    769      }),
    770    ],
    771  });
    772 
    773  // Change the mixup.
    774  setResultGroups([
    775    // 1 suggestion
    776    {
    777      maxResultCount: 1,
    778      children: [
    779        { group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY },
    780        { group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION },
    781      ],
    782    },
    783    // 5 general
    784    {
    785      maxResultCount: 5,
    786      group: UrlbarUtils.RESULT_GROUP.GENERAL,
    787    },
    788    // 1 suggestion
    789    {
    790      maxResultCount: 1,
    791      children: [
    792        { group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY },
    793        { group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION },
    794      ],
    795    },
    796    // remaining general
    797    { group: UrlbarUtils.RESULT_GROUP.GENERAL },
    798    // remaining suggestions
    799    { group: UrlbarUtils.RESULT_GROUP.FORM_HISTORY },
    800    { group: UrlbarUtils.RESULT_GROUP.REMOTE_SUGGESTION },
    801  ]);
    802 
    803  // Do an unrestricted search to make sure everything appears in it, including
    804  // the visits and bookmarks.
    805  context = createContext(SEARCH_STRING, { isPrivate: false });
    806  await check_results({
    807    context,
    808    matches: [
    809      makeSearchResult(context, {
    810        engineName: SUGGESTIONS_ENGINE_NAME,
    811        heuristic: true,
    812      }),
    813      ...makeFormHistoryResults(context, 1),
    814      makeBookmarkResult(context, {
    815        uri: "http://example.com/hi3",
    816        title: `${SEARCH_STRING} high frecency 3`,
    817      }),
    818      makeBookmarkResult(context, {
    819        uri: "http://example.com/hi2",
    820        title: `${SEARCH_STRING} high frecency 2`,
    821      }),
    822      makeBookmarkResult(context, {
    823        uri: "http://example.com/hi1",
    824        title: `${SEARCH_STRING} high frecency 1`,
    825      }),
    826      makeBookmarkResult(context, {
    827        uri: "http://example.com/hi0",
    828        title: `${SEARCH_STRING} high frecency 0`,
    829      }),
    830      makeVisitResult(context, {
    831        uri: "http://example.com/lo4",
    832        title: `${SEARCH_STRING} low frecency 4`,
    833      }),
    834      ...makeFormHistoryResults(context, 2).slice(1),
    835      makeVisitResult(context, {
    836        uri: "http://example.com/lo3",
    837        title: `${SEARCH_STRING} low frecency 3`,
    838      }),
    839      makeVisitResult(context, {
    840        uri: "http://example.com/lo2",
    841        title: `${SEARCH_STRING} low frecency 2`,
    842      }),
    843      makeVisitResult(context, {
    844        uri: "http://example.com/lo1",
    845        title: `${SEARCH_STRING} low frecency 1`,
    846      }),
    847      makeVisitResult(context, {
    848        uri: "http://example.com/lo0",
    849        title: `${SEARCH_STRING} low frecency 0`,
    850      }),
    851      ...makeFormHistoryResults(context, MAX_RESULTS).slice(2),
    852      ...makeRemoteSuggestionResults(context),
    853    ],
    854  });
    855 
    856  Services.prefs.clearUserPref(MAX_RICH_RESULTS_PREF);
    857  await cleanUpSuggestions();
    858 });
    859 
    860 add_task(async function prohibit_suggestions() {
    861  Services.prefs.setBoolPref(SUGGEST_PREF, true);
    862  Services.prefs.setBoolPref(
    863    `browser.fixup.domainwhitelist.${SEARCH_STRING}`,
    864    false
    865  );
    866 
    867  let context = createContext(SEARCH_STRING, { isPrivate: false });
    868  await check_results({
    869    context,
    870    matches: [
    871      makeSearchResult(context, {
    872        engineName: SUGGESTIONS_ENGINE_NAME,
    873        heuristic: true,
    874      }),
    875      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    876      ...makeRemoteSuggestionResults(context),
    877    ],
    878  });
    879 
    880  Services.prefs.setBoolPref(
    881    `browser.fixup.domainwhitelist.${SEARCH_STRING}`,
    882    true
    883  );
    884  registerCleanupFunction(() => {
    885    Services.prefs.setBoolPref(
    886      `browser.fixup.domainwhitelist.${SEARCH_STRING}`,
    887      false
    888    );
    889  });
    890  context = createContext(SEARCH_STRING, { isPrivate: false });
    891  await check_results({
    892    context,
    893    matches: [
    894      makeVisitResult(context, {
    895        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    896        uri: `http://${SEARCH_STRING}/`,
    897        title: `${SEARCH_STRING}/`,
    898        iconUri: "",
    899        heuristic: true,
    900      }),
    901      ...makeFormHistoryResults(context, MAX_RESULTS - 2),
    902      makeSearchResult(context, {
    903        engineName: SUGGESTIONS_ENGINE_NAME,
    904        heuristic: false,
    905      }),
    906    ],
    907  });
    908 
    909  // When using multiple words, we should still get suggestions:
    910  let query = `${SEARCH_STRING} world`;
    911  context = createContext(query, { isPrivate: false });
    912  await check_results({
    913    context,
    914    matches: [
    915      makeSearchResult(context, {
    916        engineName: SUGGESTIONS_ENGINE_NAME,
    917        heuristic: true,
    918      }),
    919      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    920      ...makeRemoteSuggestionResults(context, { suggestionPrefix: query }),
    921    ],
    922  });
    923 
    924  // Clear the whitelist for SEARCH_STRING and try preferring DNS for any single
    925  // word instead:
    926  Services.prefs.setBoolPref(
    927    `browser.fixup.domainwhitelist.${SEARCH_STRING}`,
    928    false
    929  );
    930  Services.prefs.setBoolPref("browser.fixup.dns_first_for_single_words", true);
    931  registerCleanupFunction(() => {
    932    Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words");
    933  });
    934 
    935  context = createContext(SEARCH_STRING, { isPrivate: false });
    936  await check_results({
    937    context,
    938    matches: [
    939      makeVisitResult(context, {
    940        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    941        uri: `http://${SEARCH_STRING}/`,
    942        title: `${SEARCH_STRING}/`,
    943        iconUri: "",
    944        heuristic: true,
    945      }),
    946      ...makeFormHistoryResults(context, MAX_RESULTS - 2),
    947      makeSearchResult(context, {
    948        engineName: SUGGESTIONS_ENGINE_NAME,
    949        heuristic: false,
    950      }),
    951    ],
    952  });
    953 
    954  context = createContext("somethingelse", { isPrivate: false });
    955  await check_results({
    956    context,
    957    matches: [
    958      makeVisitResult(context, {
    959        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    960        uri: "http://somethingelse/",
    961        title: "somethingelse/",
    962        iconUri: "",
    963        heuristic: true,
    964      }),
    965      makeSearchResult(context, {
    966        engineName: SUGGESTIONS_ENGINE_NAME,
    967        heuristic: false,
    968      }),
    969    ],
    970  });
    971 
    972  // When using multiple words, we should still get suggestions:
    973  query = `${SEARCH_STRING} world`;
    974  context = createContext(query, { isPrivate: false });
    975  await check_results({
    976    context,
    977    matches: [
    978      makeSearchResult(context, {
    979        engineName: SUGGESTIONS_ENGINE_NAME,
    980        heuristic: true,
    981      }),
    982      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
    983      ...makeRemoteSuggestionResults(context, { suggestionPrefix: query }),
    984    ],
    985  });
    986 
    987  Services.prefs.clearUserPref("browser.fixup.dns_first_for_single_words");
    988 
    989  context = createContext("http://1.2.3.4/", { isPrivate: false });
    990  await check_results({
    991    context,
    992    matches: [
    993      makeVisitResult(context, {
    994        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    995        uri: "http://1.2.3.4/",
    996        title: "http://1.2.3.4/",
    997        iconUri: "page-icon:http://1.2.3.4/",
    998        heuristic: true,
    999      }),
   1000    ],
   1001  });
   1002 
   1003  context = createContext("[2001::1]:30", { isPrivate: false });
   1004  await check_results({
   1005    context,
   1006    matches: [
   1007      makeVisitResult(context, {
   1008        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1009        uri: "http://[2001::1]:30/",
   1010        title: "[2001::1]:30/",
   1011        iconUri: "",
   1012        heuristic: true,
   1013      }),
   1014    ],
   1015  });
   1016 
   1017  context = createContext("user:pass@test", { isPrivate: false });
   1018  await check_results({
   1019    context,
   1020    matches: [
   1021      makeVisitResult(context, {
   1022        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1023        uri: "http://user:pass@test/",
   1024        title: "user:pass@test/",
   1025        iconUri: "",
   1026        heuristic: true,
   1027      }),
   1028    ],
   1029  });
   1030 
   1031  context = createContext("user:pass@mozilla.org", { isPrivate: false });
   1032  await check_results({
   1033    context,
   1034    matches: [
   1035      makeVisitResult(context, {
   1036        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1037        uri: "http://user:pass@mozilla.org/",
   1038        title: "user:pass@mozilla.org/",
   1039        iconUri: "",
   1040        heuristic: true,
   1041      }),
   1042    ],
   1043  });
   1044 
   1045  context = createContext("mozilla.org:1234", { isPrivate: false });
   1046  await check_results({
   1047    context,
   1048    matches: [
   1049      makeVisitResult(context, {
   1050        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1051        uri: "http://mozilla.org:1234/",
   1052        title: "mozilla.org:1234/",
   1053        iconUri: "",
   1054        heuristic: true,
   1055      }),
   1056    ],
   1057  });
   1058 
   1059  context = createContext("data:text/plain,Content", { isPrivate: false });
   1060  await check_results({
   1061    context,
   1062    matches: [
   1063      makeVisitResult(context, {
   1064        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1065        uri: "data:text/plain,Content",
   1066        title: "data:text/plain,Content",
   1067        iconUri: "",
   1068        heuristic: true,
   1069      }),
   1070    ],
   1071  });
   1072 
   1073  context = createContext("a", { isPrivate: false });
   1074  await check_results({
   1075    context,
   1076    matches: [
   1077      makeSearchResult(context, {
   1078        engineName: SUGGESTIONS_ENGINE_NAME,
   1079        heuristic: true,
   1080      }),
   1081    ],
   1082  });
   1083 
   1084  await cleanUpSuggestions();
   1085 });
   1086 
   1087 add_task(async function simple_origin_queries() {
   1088  Services.prefs.setBoolPref(SUGGEST_PREF, true);
   1089  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
   1090 
   1091  // Queries that are actual simple origins should return search suggestions
   1092  // only when `allowSearchSuggestionsForSimpleOrigins` is true.
   1093  let queries = ["mozilla.org", "example.com", "example.co", "readme.md"];
   1094 
   1095  for (let allow of [false, true]) {
   1096    Services.prefs.setBoolPref(
   1097      "browser.urlbar.allowSearchSuggestionsForSimpleOrigins",
   1098      allow
   1099    );
   1100 
   1101    for (let query of queries) {
   1102      info("Testing: " + JSON.stringify({ allow, query }));
   1103 
   1104      let context = createContext(query, { isPrivate: false });
   1105      let expected = [
   1106        makeVisitResult(context, {
   1107          source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1108          title: `${query}/`,
   1109          uri: `http://${query}/`,
   1110          iconUri: "",
   1111          heuristic: true,
   1112        }),
   1113      ];
   1114      if (allow) {
   1115        expected.push(
   1116          ...makeRemoteSuggestionResults(context, { suggestionPrefix: query })
   1117        );
   1118      }
   1119      expected.push(
   1120        makeSearchResult(context, {
   1121          query,
   1122          engineName: SUGGESTIONS_ENGINE_NAME,
   1123        })
   1124      );
   1125 
   1126      await check_results({
   1127        context,
   1128        matches: expected,
   1129      });
   1130    }
   1131  }
   1132 
   1133  await cleanUpSuggestions();
   1134  Services.prefs.clearUserPref(
   1135    "browser.urlbar.allowSearchSuggestionsForSimpleOrigins"
   1136  );
   1137 });
   1138 
   1139 add_task(async function simple_origin_like_queries() {
   1140  Services.prefs.setBoolPref(SUGGEST_PREF, true);
   1141  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
   1142 
   1143  // Queries that aren't simple origins but look like simple origins should
   1144  // return search suggestions only when
   1145  // `allowSearchSuggestionsForSimpleOrigins` is true.
   1146  let queries = ["mozilla.o", "mozilla."];
   1147 
   1148  for (let allow of [false, true]) {
   1149    Services.prefs.setBoolPref(
   1150      "browser.urlbar.allowSearchSuggestionsForSimpleOrigins",
   1151      allow
   1152    );
   1153 
   1154    for (let query of queries) {
   1155      info("Testing: " + JSON.stringify({ allow, query }));
   1156 
   1157      let context = createContext(query, { isPrivate: false });
   1158      let expected = [
   1159        makeSearchResult(context, {
   1160          engineName: SUGGESTIONS_ENGINE_NAME,
   1161          heuristic: true,
   1162        }),
   1163      ];
   1164      if (allow) {
   1165        expected.push(
   1166          ...makeRemoteSuggestionResults(context, { suggestionPrefix: query })
   1167        );
   1168      }
   1169 
   1170      await check_results({
   1171        context,
   1172        matches: expected,
   1173      });
   1174    }
   1175  }
   1176 
   1177  await cleanUpSuggestions();
   1178  Services.prefs.clearUserPref(
   1179    "browser.urlbar.allowSearchSuggestionsForSimpleOrigins"
   1180  );
   1181 });
   1182 
   1183 add_task(async function uri_like_queries() {
   1184  Services.prefs.setBoolPref(SUGGEST_PREF, true);
   1185  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
   1186 
   1187  // Queries that don't look like origins but that might be confused for URLs
   1188  // should always return search suggestions regardless of
   1189  // `allowSearchSuggestionsForSimpleOrigins`.
   1190  const uriLikeQueries = [
   1191    "mozilla.org is a great website",
   1192    "I like mozilla.org",
   1193    "a/b testing",
   1194    "he/him",
   1195    "Google vs.",
   1196    "5.8 cm",
   1197  ];
   1198 
   1199  for (let allow of [false, true]) {
   1200    Services.prefs.setBoolPref(
   1201      "browser.urlbar.allowSearchSuggestionsForSimpleOrigins",
   1202      allow
   1203    );
   1204 
   1205    for (let query of uriLikeQueries) {
   1206      info("Testing: " + JSON.stringify({ allow, query }));
   1207 
   1208      let context = createContext(query, { isPrivate: false });
   1209      await check_results({
   1210        context,
   1211        matches: [
   1212          makeSearchResult(context, {
   1213            engineName: SUGGESTIONS_ENGINE_NAME,
   1214            heuristic: true,
   1215          }),
   1216          ...makeRemoteSuggestionResults(context, {
   1217            suggestionPrefix: query,
   1218          }),
   1219        ],
   1220      });
   1221    }
   1222  }
   1223 
   1224  await cleanUpSuggestions();
   1225  Services.prefs.clearUserPref(
   1226    "browser.urlbar.allowSearchSuggestionsForSimpleOrigins"
   1227  );
   1228 });
   1229 
   1230 add_task(async function avoid_remote_url_suggestions_1() {
   1231  Services.prefs.setBoolPref(SUGGEST_PREF, true);
   1232  setSuggestionsFn(searchStr => {
   1233    let suffixes = [".com", "/test", ":1]", "@test", ". com"];
   1234    return suffixes.map(s => searchStr + s);
   1235  });
   1236 
   1237  const query = "test";
   1238 
   1239  await UrlbarTestUtils.formHistory.add([`${query}.com`]);
   1240 
   1241  let context = createContext(query, { isPrivate: false });
   1242  await check_results({
   1243    context,
   1244    matches: [
   1245      makeSearchResult(context, {
   1246        engineName: SUGGESTIONS_ENGINE_NAME,
   1247        heuristic: true,
   1248      }),
   1249      makeFormHistoryResult(context, {
   1250        engineName: SUGGESTIONS_ENGINE_NAME,
   1251        suggestion: `${query}.com`,
   1252      }),
   1253      makeSearchResult(context, {
   1254        engineName: SUGGESTIONS_ENGINE_NAME,
   1255        suggestion: `${query}. com`,
   1256      }),
   1257    ],
   1258  });
   1259 
   1260  await cleanUpSuggestions();
   1261  await UrlbarTestUtils.formHistory.remove([`${query}.com`]);
   1262 });
   1263 
   1264 add_task(async function avoid_remote_url_suggestions_2() {
   1265  Services.prefs.setBoolPref(SUGGEST_PREF, true);
   1266  Services.prefs.setBoolPref("browser.urlbar.autoFill", false);
   1267 
   1268  setSuggestionsFn(searchStr => {
   1269    let suffixes = ["ed", "eds"];
   1270    return suffixes.map(s => searchStr + s);
   1271  });
   1272 
   1273  let context = createContext("htt", { isPrivate: false });
   1274  await check_results({
   1275    context,
   1276    matches: [
   1277      makeSearchResult(context, {
   1278        engineName: SUGGESTIONS_ENGINE_NAME,
   1279        heuristic: true,
   1280      }),
   1281      makeSearchResult(context, {
   1282        engineName: SUGGESTIONS_ENGINE_NAME,
   1283        suggestion: "htted",
   1284      }),
   1285      makeSearchResult(context, {
   1286        engineName: SUGGESTIONS_ENGINE_NAME,
   1287        suggestion: "htteds",
   1288      }),
   1289    ],
   1290  });
   1291 
   1292  context = createContext("ftp", { isPrivate: false });
   1293  await check_results({
   1294    context,
   1295    matches: [
   1296      makeSearchResult(context, {
   1297        engineName: SUGGESTIONS_ENGINE_NAME,
   1298        heuristic: true,
   1299      }),
   1300      makeSearchResult(context, {
   1301        engineName: SUGGESTIONS_ENGINE_NAME,
   1302        suggestion: "ftped",
   1303      }),
   1304      makeSearchResult(context, {
   1305        engineName: SUGGESTIONS_ENGINE_NAME,
   1306        suggestion: "ftpeds",
   1307      }),
   1308    ],
   1309  });
   1310 
   1311  context = createContext("http", { isPrivate: false });
   1312  await check_results({
   1313    context,
   1314    matches: [
   1315      makeSearchResult(context, {
   1316        engineName: SUGGESTIONS_ENGINE_NAME,
   1317        heuristic: true,
   1318      }),
   1319      makeSearchResult(context, {
   1320        engineName: SUGGESTIONS_ENGINE_NAME,
   1321        suggestion: "httped",
   1322      }),
   1323      makeSearchResult(context, {
   1324        engineName: SUGGESTIONS_ENGINE_NAME,
   1325        suggestion: "httpeds",
   1326      }),
   1327    ],
   1328  });
   1329 
   1330  context = createContext("http:", { isPrivate: false });
   1331  await check_results({
   1332    context,
   1333    matches: [
   1334      makeSearchResult(context, {
   1335        engineName: SUGGESTIONS_ENGINE_NAME,
   1336        heuristic: true,
   1337      }),
   1338    ],
   1339  });
   1340 
   1341  context = createContext("https", { isPrivate: false });
   1342  await check_results({
   1343    context,
   1344    matches: [
   1345      makeSearchResult(context, {
   1346        engineName: SUGGESTIONS_ENGINE_NAME,
   1347        heuristic: true,
   1348      }),
   1349      makeSearchResult(context, {
   1350        engineName: SUGGESTIONS_ENGINE_NAME,
   1351        suggestion: "httpsed",
   1352      }),
   1353      makeSearchResult(context, {
   1354        engineName: SUGGESTIONS_ENGINE_NAME,
   1355        suggestion: "httpseds",
   1356      }),
   1357    ],
   1358  });
   1359 
   1360  context = createContext("https:", { isPrivate: false });
   1361  await check_results({
   1362    context,
   1363    matches: [
   1364      makeSearchResult(context, {
   1365        engineName: SUGGESTIONS_ENGINE_NAME,
   1366        heuristic: true,
   1367      }),
   1368    ],
   1369  });
   1370 
   1371  context = createContext("httpd", { isPrivate: false });
   1372  await check_results({
   1373    context,
   1374    matches: [
   1375      makeSearchResult(context, {
   1376        engineName: SUGGESTIONS_ENGINE_NAME,
   1377        heuristic: true,
   1378      }),
   1379      makeSearchResult(context, {
   1380        engineName: SUGGESTIONS_ENGINE_NAME,
   1381        suggestion: "httpded",
   1382      }),
   1383      makeSearchResult(context, {
   1384        engineName: SUGGESTIONS_ENGINE_NAME,
   1385        suggestion: "httpdeds",
   1386      }),
   1387    ],
   1388  });
   1389 
   1390  // Check FTP disabled
   1391  context = createContext("ftp:", { isPrivate: false });
   1392  await check_results({
   1393    context,
   1394    matches: [
   1395      makeSearchResult(context, {
   1396        engineName: SUGGESTIONS_ENGINE_NAME,
   1397        heuristic: true,
   1398      }),
   1399    ],
   1400  });
   1401 
   1402  context = createContext("ftp:/", { isPrivate: false });
   1403  await check_results({
   1404    context,
   1405    matches: [
   1406      makeSearchResult(context, {
   1407        engineName: SUGGESTIONS_ENGINE_NAME,
   1408        heuristic: true,
   1409      }),
   1410    ],
   1411  });
   1412 
   1413  context = createContext("ftp://", { isPrivate: false });
   1414  await check_results({
   1415    context,
   1416    matches: [
   1417      makeSearchResult(context, {
   1418        engineName: SUGGESTIONS_ENGINE_NAME,
   1419        heuristic: true,
   1420      }),
   1421    ],
   1422  });
   1423 
   1424  context = createContext("ftp://test", { isPrivate: false });
   1425  await check_results({
   1426    context,
   1427    matches: [
   1428      makeVisitResult(context, {
   1429        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1430        uri: "ftp://test/",
   1431        title: "ftp://test/",
   1432        iconUri: "",
   1433        heuristic: true,
   1434      }),
   1435    ],
   1436  });
   1437 
   1438  context = createContext("http:/", { isPrivate: false });
   1439  await check_results({
   1440    context,
   1441    matches: [
   1442      makeSearchResult(context, {
   1443        engineName: SUGGESTIONS_ENGINE_NAME,
   1444        heuristic: true,
   1445      }),
   1446    ],
   1447  });
   1448 
   1449  context = createContext("https:/", { isPrivate: false });
   1450  await check_results({
   1451    context,
   1452    matches: [
   1453      makeSearchResult(context, {
   1454        engineName: SUGGESTIONS_ENGINE_NAME,
   1455        heuristic: true,
   1456      }),
   1457    ],
   1458  });
   1459 
   1460  context = createContext("http://", { isPrivate: false });
   1461  await check_results({
   1462    context,
   1463    matches: [
   1464      makeSearchResult(context, {
   1465        engineName: SUGGESTIONS_ENGINE_NAME,
   1466        heuristic: true,
   1467      }),
   1468    ],
   1469  });
   1470 
   1471  context = createContext("https://", { isPrivate: false });
   1472  await check_results({
   1473    context,
   1474    matches: [
   1475      makeSearchResult(context, {
   1476        engineName: SUGGESTIONS_ENGINE_NAME,
   1477        heuristic: true,
   1478      }),
   1479    ],
   1480  });
   1481 
   1482  context = createContext("http://www", { isPrivate: false });
   1483  await check_results({
   1484    context,
   1485    matches: [
   1486      makeVisitResult(context, {
   1487        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1488        uri: "http://www/",
   1489        title: "http://www/",
   1490        iconUri: "",
   1491        heuristic: true,
   1492      }),
   1493    ],
   1494  });
   1495 
   1496  context = createContext("https://www", { isPrivate: false });
   1497  await check_results({
   1498    context,
   1499    matches: [
   1500      makeVisitResult(context, {
   1501        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1502        uri: "https://www/",
   1503        title: "https://www/",
   1504        iconUri: "",
   1505        heuristic: true,
   1506      }),
   1507    ],
   1508  });
   1509 
   1510  context = createContext("http://test", { isPrivate: false });
   1511  await check_results({
   1512    context,
   1513    matches: [
   1514      makeVisitResult(context, {
   1515        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1516        uri: "http://test/",
   1517        title: "http://test/",
   1518        iconUri: "",
   1519        heuristic: true,
   1520      }),
   1521    ],
   1522  });
   1523 
   1524  context = createContext("https://test", { isPrivate: false });
   1525  await check_results({
   1526    context,
   1527    matches: [
   1528      makeVisitResult(context, {
   1529        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1530        uri: "https://test/",
   1531        title: "https://test/",
   1532        iconUri: "",
   1533        heuristic: true,
   1534      }),
   1535    ],
   1536  });
   1537 
   1538  context = createContext("http://www.test", { isPrivate: false });
   1539  await check_results({
   1540    context,
   1541    matches: [
   1542      makeVisitResult(context, {
   1543        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1544        uri: "http://www.test/",
   1545        title: "http://www.test/",
   1546        iconUri: "",
   1547        heuristic: true,
   1548      }),
   1549    ],
   1550  });
   1551 
   1552  context = createContext("http://www.test.com", { isPrivate: false });
   1553  await check_results({
   1554    context,
   1555    matches: [
   1556      makeVisitResult(context, {
   1557        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1558        uri: "http://www.test.com/",
   1559        title: "http://www.test.com/",
   1560        iconUri: "",
   1561        heuristic: true,
   1562      }),
   1563    ],
   1564  });
   1565 
   1566  context = createContext("file", { isPrivate: false });
   1567  await check_results({
   1568    context,
   1569    matches: [
   1570      makeSearchResult(context, {
   1571        engineName: SUGGESTIONS_ENGINE_NAME,
   1572        heuristic: true,
   1573      }),
   1574      makeSearchResult(context, {
   1575        engineName: SUGGESTIONS_ENGINE_NAME,
   1576        suggestion: "fileed",
   1577      }),
   1578      makeSearchResult(context, {
   1579        engineName: SUGGESTIONS_ENGINE_NAME,
   1580        suggestion: "fileeds",
   1581      }),
   1582    ],
   1583  });
   1584 
   1585  context = createContext("file:", { isPrivate: false });
   1586  await check_results({
   1587    context,
   1588    matches: [
   1589      makeSearchResult(context, {
   1590        engineName: SUGGESTIONS_ENGINE_NAME,
   1591        heuristic: true,
   1592      }),
   1593    ],
   1594  });
   1595 
   1596  context = createContext("file:///Users", { isPrivate: false });
   1597  await check_results({
   1598    context,
   1599    matches: [
   1600      makeVisitResult(context, {
   1601        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
   1602        uri: "file:///Users",
   1603        title: "file:///Users",
   1604        iconUri: "",
   1605        heuristic: true,
   1606      }),
   1607    ],
   1608  });
   1609 
   1610  context = createContext("moz-test://", { isPrivate: false });
   1611  await check_results({
   1612    context,
   1613    matches: [
   1614      makeSearchResult(context, {
   1615        engineName: SUGGESTIONS_ENGINE_NAME,
   1616        heuristic: true,
   1617      }),
   1618    ],
   1619  });
   1620 
   1621  context = createContext("moz+test://", { isPrivate: false });
   1622  await check_results({
   1623    context,
   1624    matches: [
   1625      makeSearchResult(context, {
   1626        engineName: SUGGESTIONS_ENGINE_NAME,
   1627        heuristic: true,
   1628      }),
   1629    ],
   1630  });
   1631 
   1632  context = createContext("about", { isPrivate: false });
   1633  await check_results({
   1634    context,
   1635    matches: [
   1636      makeSearchResult(context, {
   1637        engineName: SUGGESTIONS_ENGINE_NAME,
   1638        heuristic: true,
   1639      }),
   1640      makeSearchResult(context, {
   1641        engineName: SUGGESTIONS_ENGINE_NAME,
   1642        suggestion: "abouted",
   1643      }),
   1644      makeSearchResult(context, {
   1645        engineName: SUGGESTIONS_ENGINE_NAME,
   1646        suggestion: "abouteds",
   1647      }),
   1648    ],
   1649  });
   1650 
   1651  await cleanUpSuggestions();
   1652 });
   1653 
   1654 add_task(async function restrict_remote_suggestions_after_no_results() {
   1655  // We don't fetch remote suggestions if a query with a length over
   1656  // maxCharsForSearchSuggestions returns 0 results. We set it to 4 here to
   1657  // avoid constructing a 100+ character string.
   1658  Services.prefs.setIntPref("browser.urlbar.maxCharsForSearchSuggestions", 4);
   1659  setSuggestionsFn(() => {
   1660    return [];
   1661  });
   1662 
   1663  const query = SEARCH_STRING.substring(0, SEARCH_STRING.length - 1);
   1664  let context = createContext(query, { isPrivate: false });
   1665  await check_results({
   1666    context,
   1667    matches: [
   1668      makeSearchResult(context, {
   1669        engineName: SUGGESTIONS_ENGINE_NAME,
   1670        heuristic: true,
   1671      }),
   1672      ...makeFormHistoryResults(context, MAX_RESULTS - 1),
   1673    ],
   1674  });
   1675 
   1676  context = createContext(SEARCH_STRING, { isPrivate: false });
   1677  await check_results({
   1678    context,
   1679    matches: [
   1680      makeSearchResult(context, {
   1681        engineName: SUGGESTIONS_ENGINE_NAME,
   1682        heuristic: true,
   1683      }),
   1684      ...makeFormHistoryResults(context, MAX_RESULTS - 1),
   1685      // Because the previous search returned no suggestions, we will not fetch
   1686      // remote suggestions for this query that is just a longer version of the
   1687      // previous query.
   1688    ],
   1689  });
   1690 
   1691  // Do one more search before resetting maxCharsForSearchSuggestions to reset
   1692  // the search suggestion provider's _lastLowResultsSearchSuggestion property.
   1693  // Otherwise it will be stuck at SEARCH_STRING, which interferes with
   1694  // subsequent tests.
   1695  context = createContext("not the search string", { isPrivate: false });
   1696  await check_results({
   1697    context,
   1698    matches: [
   1699      makeSearchResult(context, {
   1700        engineName: SUGGESTIONS_ENGINE_NAME,
   1701        heuristic: true,
   1702      }),
   1703    ],
   1704  });
   1705 
   1706  Services.prefs.clearUserPref("browser.urlbar.maxCharsForSearchSuggestions");
   1707 
   1708  await cleanUpSuggestions();
   1709 });
   1710 
   1711 add_task(async function formHistory() {
   1712  Services.prefs.setBoolPref(SUGGEST_PREF, true);
   1713  Services.prefs.setBoolPref(SUGGEST_ENABLED_PREF, true);
   1714 
   1715  // `maxHistoricalSearchSuggestions` is no longer treated as a max count but as
   1716  // a boolean: If it's zero, then the user has opted out of form history so we
   1717  // shouldn't include any at all; if it's non-zero, then we include form
   1718  // history according to the limits specified in the muxer's result groups.
   1719 
   1720  // zero => no form history
   1721  Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 0);
   1722  let context = createContext(SEARCH_STRING, { isPrivate: false });
   1723  await check_results({
   1724    context,
   1725    matches: [
   1726      makeSearchResult(context, {
   1727        engineName: SUGGESTIONS_ENGINE_NAME,
   1728        heuristic: true,
   1729      }),
   1730      ...makeRemoteSuggestionResults(context),
   1731    ],
   1732  });
   1733 
   1734  // non-zero => allow form history
   1735  Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 1);
   1736  context = createContext(SEARCH_STRING, { isPrivate: false });
   1737  await check_results({
   1738    context,
   1739    matches: [
   1740      makeSearchResult(context, {
   1741        engineName: SUGGESTIONS_ENGINE_NAME,
   1742        heuristic: true,
   1743      }),
   1744      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
   1745      ...makeRemoteSuggestionResults(context),
   1746    ],
   1747  });
   1748 
   1749  // non-zero => allow form history
   1750  Services.prefs.setIntPref(MAX_FORM_HISTORY_PREF, 2);
   1751  context = createContext(SEARCH_STRING, { isPrivate: false });
   1752  await check_results({
   1753    context,
   1754    matches: [
   1755      makeSearchResult(context, {
   1756        engineName: SUGGESTIONS_ENGINE_NAME,
   1757        heuristic: true,
   1758      }),
   1759      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
   1760      ...makeRemoteSuggestionResults(context),
   1761    ],
   1762  });
   1763 
   1764  Services.prefs.clearUserPref(MAX_FORM_HISTORY_PREF);
   1765 
   1766  // Do a search for exactly the suggestion of the first form history result.
   1767  // The heuristic's query should be the suggestion; the first form history
   1768  // result should not be included since it dupes the heuristic; the other form
   1769  // history results should not be included since they don't match; and both
   1770  // remote suggestions should be included.
   1771  let firstSuggestion = makeFormHistoryResults(context, 1)[0].payload
   1772    .suggestion;
   1773  context = createContext(firstSuggestion, { isPrivate: false });
   1774  await check_results({
   1775    context,
   1776    matches: [
   1777      makeSearchResult(context, {
   1778        engineName: SUGGESTIONS_ENGINE_NAME,
   1779        heuristic: true,
   1780      }),
   1781      ...makeRemoteSuggestionResults(context, {
   1782        suggestionPrefix: firstSuggestion,
   1783      }),
   1784    ],
   1785  });
   1786 
   1787  // Do the same search but in uppercase with a trailing space.  We should get
   1788  // the same results, i.e., the form history result dupes the trimmed search
   1789  // string so it shouldn't be included.
   1790  let query = firstSuggestion.toUpperCase() + " ";
   1791  context = createContext(query, { isPrivate: false });
   1792  await check_results({
   1793    context,
   1794    matches: [
   1795      makeSearchResult(context, {
   1796        query,
   1797        engineName: SUGGESTIONS_ENGINE_NAME,
   1798        heuristic: true,
   1799      }),
   1800      ...makeRemoteSuggestionResults(context, {
   1801        suggestionPrefix: firstSuggestion.toUpperCase(),
   1802      }),
   1803    ],
   1804  });
   1805 
   1806  // Add a form history entry that dupes the first remote suggestion and do a
   1807  // search that triggers both.  The form history should be included but the
   1808  // remote suggestion should not since it dupes the form history.
   1809  let suggestionPrefix = "dupe";
   1810  let dupeSuggestion = makeRemoteSuggestionResults(context, {
   1811    suggestionPrefix,
   1812  })[0].payload.suggestion;
   1813  Assert.ok(dupeSuggestion, "Sanity check: dupeSuggestion is defined");
   1814  await UrlbarTestUtils.formHistory.add([dupeSuggestion]);
   1815 
   1816  context = createContext(suggestionPrefix, { isPrivate: false });
   1817  await check_results({
   1818    context,
   1819    matches: [
   1820      makeSearchResult(context, {
   1821        engineName: SUGGESTIONS_ENGINE_NAME,
   1822        heuristic: true,
   1823      }),
   1824      makeFormHistoryResult(context, {
   1825        suggestion: dupeSuggestion,
   1826        engineName: SUGGESTIONS_ENGINE_NAME,
   1827      }),
   1828      ...makeRemoteSuggestionResults(context, { suggestionPrefix }).slice(1),
   1829    ],
   1830  });
   1831 
   1832  await UrlbarTestUtils.formHistory.remove([dupeSuggestion]);
   1833 
   1834  // Add these form history strings to use below.
   1835  let formHistoryStrings = ["foo", "FOO ", "foobar", "fooquux"];
   1836  await UrlbarTestUtils.formHistory.add(formHistoryStrings);
   1837 
   1838  // Search for "foo".  "foo" and "FOO " shouldn't be included since they dupe
   1839  // the heuristic.  Both "foobar" and "fooquux" should be included even though
   1840  // the max form history count is only two and there are four matching form
   1841  // history results (including the discarded "foo" and "FOO ").
   1842  context = createContext("foo", { isPrivate: false });
   1843  await check_results({
   1844    context,
   1845    matches: [
   1846      makeSearchResult(context, {
   1847        engineName: SUGGESTIONS_ENGINE_NAME,
   1848        heuristic: true,
   1849      }),
   1850      makeFormHistoryResult(context, {
   1851        suggestion: "foobar",
   1852        engineName: SUGGESTIONS_ENGINE_NAME,
   1853      }),
   1854      makeFormHistoryResult(context, {
   1855        suggestion: "fooquux",
   1856        engineName: SUGGESTIONS_ENGINE_NAME,
   1857      }),
   1858      ...makeRemoteSuggestionResults(context, {
   1859        suggestionPrefix: "foo",
   1860      }),
   1861    ],
   1862  });
   1863 
   1864  // Add a visit that matches "foo" and will autofill so that the heuristic is
   1865  // not a search result.  Now the "foo" and "foobar" form history should be
   1866  // included.  The "foo" remote suggestion should not be included since it
   1867  // dupes the "foo" form history.
   1868  await PlacesTestUtils.addVisits("http://foo.example.com/");
   1869  context = createContext("foo", { isPrivate: false });
   1870  await check_results({
   1871    context,
   1872    matches: [
   1873      makeVisitResult(context, {
   1874        source: UrlbarUtils.RESULT_SOURCE.HISTORY,
   1875        uri: "http://foo.example.com/",
   1876        title: "test visit for http://foo.example.com/",
   1877        heuristic: true,
   1878      }),
   1879      makeFormHistoryResult(context, {
   1880        suggestion: "foo",
   1881        engineName: SUGGESTIONS_ENGINE_NAME,
   1882      }),
   1883      makeFormHistoryResult(context, {
   1884        suggestion: "foobar",
   1885        engineName: SUGGESTIONS_ENGINE_NAME,
   1886      }),
   1887      makeFormHistoryResult(context, {
   1888        suggestion: "fooquux",
   1889        engineName: SUGGESTIONS_ENGINE_NAME,
   1890      }),
   1891      ...makeRemoteSuggestionResults(context, {
   1892        suggestionPrefix: "foo",
   1893      }),
   1894    ],
   1895  });
   1896  await PlacesUtils.history.clear();
   1897 
   1898  // Add SERPs for "foobar", "fooBAR ", and "food", and search for "foo".  The
   1899  // "foo" form history should be excluded since it dupes the heuristic; the
   1900  // "foobar" and "fooquux" form history should be included; the "food" SERP
   1901  // should be included since it doesn't dupe either form history result; and
   1902  // the "foobar" and "fooBAR " SERPs depend on the result groups, see below.
   1903  let engine = await Services.search.getDefault();
   1904  let serpURLs = ["foobar", "fooBAR ", "food"].map(
   1905    term => UrlbarUtils.getSearchQueryUrl(engine, term)[0]
   1906  );
   1907  await PlacesTestUtils.addVisits(serpURLs);
   1908 
   1909  // First set showSearchSuggestionsFirst = false so that general results appear
   1910  // before suggestions, which means that the muxer visits the "foobar" and
   1911  // "fooBAR " SERPs before visiting the "foobar" form history, and so it
   1912  // doesn't see that these two SERPs dupe the form history.  They are therefore
   1913  // included.
   1914  Services.prefs.setBoolPref(SHOW_SEARCH_SUGGESTIONS_FIRST_PREF, false);
   1915  context = createContext("foo", { isPrivate: false });
   1916  await check_results({
   1917    context,
   1918    matches: [
   1919      makeSearchResult(context, {
   1920        engineName: SUGGESTIONS_ENGINE_NAME,
   1921        heuristic: true,
   1922      }),
   1923      makeVisitResult(context, {
   1924        uri: `http://localhost:${port}/search?q=food`,
   1925        title: `test visit for http://localhost:${port}/search?q=food`,
   1926      }),
   1927      makeVisitResult(context, {
   1928        uri: `http://localhost:${port}/search?q=fooBAR+`,
   1929        title: `test visit for http://localhost:${port}/search?q=fooBAR+`,
   1930      }),
   1931      makeVisitResult(context, {
   1932        uri: `http://localhost:${port}/search?q=foobar`,
   1933        title: `test visit for http://localhost:${port}/search?q=foobar`,
   1934      }),
   1935      makeFormHistoryResult(context, {
   1936        suggestion: "foobar",
   1937        engineName: SUGGESTIONS_ENGINE_NAME,
   1938      }),
   1939      makeFormHistoryResult(context, {
   1940        suggestion: "fooquux",
   1941        engineName: SUGGESTIONS_ENGINE_NAME,
   1942      }),
   1943      ...makeRemoteSuggestionResults(context, {
   1944        suggestionPrefix: "foo",
   1945      }),
   1946    ],
   1947  });
   1948 
   1949  // Now clear showSearchSuggestionsFirst so that suggestions appear before
   1950  // general results.  Now the muxer will see that the "foobar" and "fooBAR "
   1951  // SERPs dupe the "foobar" form history, so it will exclude them.
   1952  Services.prefs.clearUserPref(SHOW_SEARCH_SUGGESTIONS_FIRST_PREF);
   1953  context = createContext("foo", { isPrivate: false });
   1954  await check_results({
   1955    context,
   1956    matches: [
   1957      makeSearchResult(context, {
   1958        engineName: SUGGESTIONS_ENGINE_NAME,
   1959        heuristic: true,
   1960      }),
   1961      makeFormHistoryResult(context, {
   1962        suggestion: "foobar",
   1963        engineName: SUGGESTIONS_ENGINE_NAME,
   1964      }),
   1965      makeFormHistoryResult(context, {
   1966        suggestion: "fooquux",
   1967        engineName: SUGGESTIONS_ENGINE_NAME,
   1968      }),
   1969      ...makeRemoteSuggestionResults(context, {
   1970        suggestionPrefix: "foo",
   1971      }),
   1972      makeVisitResult(context, {
   1973        uri: `http://localhost:${port}/search?q=food`,
   1974        title: `test visit for http://localhost:${port}/search?q=food`,
   1975      }),
   1976    ],
   1977  });
   1978 
   1979  await UrlbarTestUtils.formHistory.remove(formHistoryStrings);
   1980 
   1981  await cleanUpSuggestions();
   1982  await PlacesUtils.history.clear();
   1983 });
   1984 
   1985 // When the heuristic is hidden, search results that match the heuristic should
   1986 // be included and not deduped.
   1987 add_task(async function hideHeuristic() {
   1988  UrlbarPrefs.set("experimental.hideHeuristic", true);
   1989  UrlbarPrefs.set("browser.search.suggest.enabled", true);
   1990  UrlbarPrefs.set("suggest.searches", true);
   1991  let context = createContext(SEARCH_STRING, { isPrivate: false });
   1992  await check_results({
   1993    context,
   1994    matches: [
   1995      makeSearchResult(context, {
   1996        engineName: SUGGESTIONS_ENGINE_NAME,
   1997        heuristic: true,
   1998      }),
   1999      ...makeFormHistoryResults(context, MAX_RESULTS - 3),
   2000      makeSearchResult(context, {
   2001        query: SEARCH_STRING,
   2002        engineName: SUGGESTIONS_ENGINE_NAME,
   2003        suggestion: SEARCH_STRING,
   2004      }),
   2005      ...makeRemoteSuggestionResults(context),
   2006    ],
   2007  });
   2008  await cleanUpSuggestions();
   2009  UrlbarPrefs.clear("experimental.hideHeuristic");
   2010 });
   2011 
   2012 // When the heuristic is hidden, form history results that match the heuristic
   2013 // should be included and not deduped.
   2014 add_task(async function hideHeuristic_formHistory() {
   2015  UrlbarPrefs.set("experimental.hideHeuristic", true);
   2016  UrlbarPrefs.set("browser.search.suggest.enabled", true);
   2017  UrlbarPrefs.set("suggest.searches", true);
   2018 
   2019  // Search for exactly the suggestion of the first form history result.
   2020  // Expected results:
   2021  //
   2022  // * First form history should be included even though it dupes the heuristic
   2023  // * Other form history should not be included because they don't match the
   2024  //   search string
   2025  // * The first remote suggestion that just echoes the search string should not
   2026  //   be included because it dupes the first form history
   2027  // * The remaining remote suggestions should be included because they don't
   2028  //   dupe anything
   2029  let context = createContext(SEARCH_STRING, { isPrivate: false });
   2030  let firstFormHistory = makeFormHistoryResults(context, 1)[0];
   2031  context = createContext(firstFormHistory.payload.suggestion, {
   2032    isPrivate: false,
   2033  });
   2034  await check_results({
   2035    context,
   2036    matches: [
   2037      makeSearchResult(context, {
   2038        engineName: SUGGESTIONS_ENGINE_NAME,
   2039        heuristic: true,
   2040      }),
   2041      firstFormHistory,
   2042      ...makeRemoteSuggestionResults(context, {
   2043        suggestionPrefix: firstFormHistory.payload.suggestion,
   2044      }),
   2045    ],
   2046  });
   2047 
   2048  // Add these form history strings to use below.
   2049  let formHistoryStrings = ["foo", "FOO ", "foobar", "fooquux"];
   2050  await UrlbarTestUtils.formHistory.add(formHistoryStrings);
   2051 
   2052  // Search for "foo". Expected results:
   2053  //
   2054  // * "foo" form history should be included even though it dupes the heuristic
   2055  // * "FOO " form history should not be included because it dupes the "foo"
   2056  //   form history
   2057  // * "foobar" and "fooqux" form history should be included because they don't
   2058  //   dupe anything
   2059  // * "foo" remote suggestion should not be included because it dupes the "foo"
   2060  //   form history
   2061  // * "foo foo" and "foo bar" remote suggestions should be included because
   2062  //   they don't dupe anything
   2063  context = createContext("foo", { isPrivate: false });
   2064  await check_results({
   2065    context,
   2066    matches: [
   2067      makeSearchResult(context, {
   2068        engineName: SUGGESTIONS_ENGINE_NAME,
   2069        heuristic: true,
   2070      }),
   2071      makeFormHistoryResult(context, {
   2072        suggestion: "foo",
   2073        engineName: SUGGESTIONS_ENGINE_NAME,
   2074      }),
   2075      makeFormHistoryResult(context, {
   2076        suggestion: "foobar",
   2077        engineName: SUGGESTIONS_ENGINE_NAME,
   2078      }),
   2079      makeFormHistoryResult(context, {
   2080        suggestion: "fooquux",
   2081        engineName: SUGGESTIONS_ENGINE_NAME,
   2082      }),
   2083      ...makeRemoteSuggestionResults(context, {
   2084        suggestionPrefix: "foo",
   2085      }),
   2086    ],
   2087  });
   2088 
   2089  // Add SERPs for "foo" and "food", and search for "foo". Expected results:
   2090  //
   2091  // * "foo" form history should be included even though it dupes the heuristic
   2092  // * "foobar" and "fooqux" form history should be included because they don't
   2093  //   dupe anything
   2094  // * "foo" SERP depends on `showSearchSuggestionsFirst`, see below
   2095  // * "food" SERP should be include because it doesn't dupe anything
   2096  // * "foo" remote suggestion should not be included because it dupes the "foo"
   2097  //   form history
   2098  // * "foo foo" and "foo bar" remote suggestions should be included because
   2099  //   they don't dupe anything
   2100  let engine = await Services.search.getDefault();
   2101  let serpURLs = ["foo", "food"].map(
   2102    term => UrlbarUtils.getSearchQueryUrl(engine, term)[0]
   2103  );
   2104  await PlacesTestUtils.addVisits(serpURLs);
   2105 
   2106  // With `showSearchSuggestionsFirst = false` so that general results appear
   2107  // before suggestions, the muxer visits the "foo" (and "food") SERPs before
   2108  // visiting the "foo" form history, and so it doesn't see that the "foo" SERP
   2109  // dupes the form history. The SERP is therefore included.
   2110  UrlbarPrefs.set("showSearchSuggestionsFirst", false);
   2111  context = createContext("foo", { isPrivate: false });
   2112  await check_results({
   2113    context,
   2114    matches: [
   2115      makeSearchResult(context, {
   2116        engineName: SUGGESTIONS_ENGINE_NAME,
   2117        heuristic: true,
   2118      }),
   2119      makeVisitResult(context, {
   2120        uri: `http://localhost:${port}/search?q=food`,
   2121        title: `test visit for http://localhost:${port}/search?q=food`,
   2122      }),
   2123      makeVisitResult(context, {
   2124        uri: `http://localhost:${port}/search?q=foo`,
   2125        title: `test visit for http://localhost:${port}/search?q=foo`,
   2126      }),
   2127      makeFormHistoryResult(context, {
   2128        suggestion: "foo",
   2129        engineName: SUGGESTIONS_ENGINE_NAME,
   2130      }),
   2131      makeFormHistoryResult(context, {
   2132        suggestion: "foobar",
   2133        engineName: SUGGESTIONS_ENGINE_NAME,
   2134      }),
   2135      makeFormHistoryResult(context, {
   2136        suggestion: "fooquux",
   2137        engineName: SUGGESTIONS_ENGINE_NAME,
   2138      }),
   2139      ...makeRemoteSuggestionResults(context, {
   2140        suggestionPrefix: "foo",
   2141      }),
   2142    ],
   2143  });
   2144 
   2145  // Now clear `showSearchSuggestionsFirst` so that suggestions appear before
   2146  // general results. Now the muxer will see that the "foo" SERP dupes the "foo"
   2147  // form history, so it will exclude it.
   2148  UrlbarPrefs.clear("showSearchSuggestionsFirst");
   2149  context = createContext("foo", { isPrivate: false });
   2150  await check_results({
   2151    context,
   2152    matches: [
   2153      makeSearchResult(context, {
   2154        engineName: SUGGESTIONS_ENGINE_NAME,
   2155        heuristic: true,
   2156      }),
   2157      makeFormHistoryResult(context, {
   2158        suggestion: "foo",
   2159        engineName: SUGGESTIONS_ENGINE_NAME,
   2160      }),
   2161      makeFormHistoryResult(context, {
   2162        suggestion: "foobar",
   2163        engineName: SUGGESTIONS_ENGINE_NAME,
   2164      }),
   2165      makeFormHistoryResult(context, {
   2166        suggestion: "fooquux",
   2167        engineName: SUGGESTIONS_ENGINE_NAME,
   2168      }),
   2169      ...makeRemoteSuggestionResults(context, {
   2170        suggestionPrefix: "foo",
   2171      }),
   2172      makeVisitResult(context, {
   2173        uri: `http://localhost:${port}/search?q=food`,
   2174        title: `test visit for http://localhost:${port}/search?q=food`,
   2175      }),
   2176    ],
   2177  });
   2178 
   2179  await UrlbarTestUtils.formHistory.remove(formHistoryStrings);
   2180 
   2181  await cleanUpSuggestions();
   2182  UrlbarPrefs.clear("experimental.hideHeuristic");
   2183 });