tor-browser

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

test_autofill_origins.js (32124B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const HEURISTIC_FALLBACK_PROVIDERNAME = "UrlbarProviderHeuristicFallback";
      8 
      9 const origin = "example.com";
     10 
     11 async function cleanup() {
     12  let suggestPrefs = ["history", "bookmark", "openpage"];
     13  for (let type of suggestPrefs) {
     14    Services.prefs.clearUserPref("browser.urlbar.suggest." + type);
     15  }
     16  await cleanupPlaces();
     17 }
     18 
     19 testEngine_setup();
     20 
     21 registerCleanupFunction(async () => {
     22  Services.prefs.clearUserPref("browser.urlbar.suggest.quickactions");
     23 });
     24 Services.prefs.setBoolPref("browser.urlbar.suggest.quickactions", false);
     25 
     26 // "example.com/" should match http://example.com/.  i.e., the search string
     27 // should be treated as if it didn't have the trailing slash.
     28 add_task(async function trailingSlash() {
     29  await PlacesTestUtils.addVisits([
     30    {
     31      uri: "http://example.com/",
     32    },
     33  ]);
     34 
     35  let context = createContext(`${origin}/`, { isPrivate: false });
     36  await check_results({
     37    context,
     38    autofilled: `${origin}/`,
     39    completed: `http://${origin}/`,
     40    matches: [
     41      makeVisitResult(context, {
     42        uri: `http://${origin}/`,
     43        title: `test visit for http://${origin}/`,
     44        heuristic: true,
     45      }),
     46    ],
     47  });
     48  await cleanup();
     49 });
     50 
     51 // "example.com/" should match http://www.example.com/.  i.e., the search string
     52 // should be treated as if it didn't have the trailing slash.
     53 add_task(async function trailingSlashWWW() {
     54  await PlacesTestUtils.addVisits([
     55    {
     56      uri: "http://www.example.com/",
     57    },
     58  ]);
     59  let context = createContext(`${origin}/`, { isPrivate: false });
     60  await check_results({
     61    context,
     62    autofilled: "example.com/",
     63    completed: "http://www.example.com/",
     64    matches: [
     65      makeVisitResult(context, {
     66        uri: `http://www.${origin}/`,
     67        title: `test visit for http://www.${origin}/`,
     68        heuristic: true,
     69      }),
     70    ],
     71  });
     72  await cleanup();
     73 });
     74 
     75 // "ex" should match http://example.com:8888/, and the port should be completed.
     76 add_task(async function port() {
     77  await PlacesTestUtils.addVisits([
     78    {
     79      uri: "http://example.com:8888/",
     80    },
     81  ]);
     82  let context = createContext("ex", { isPrivate: false });
     83  await check_results({
     84    context,
     85    autofilled: "example.com:8888/",
     86    completed: "http://example.com:8888/",
     87    matches: [
     88      makeVisitResult(context, {
     89        uri: `http://${origin}:8888/`,
     90        title: `test visit for http://${origin}:8888/`,
     91        heuristic: true,
     92      }),
     93    ],
     94  });
     95  await cleanup();
     96 });
     97 
     98 // "example.com:8" should match http://example.com:8888/, and the port should
     99 // be completed.
    100 add_task(async function portPartial() {
    101  await PlacesTestUtils.addVisits([
    102    {
    103      uri: "http://example.com:8888/",
    104    },
    105  ]);
    106  let context = createContext(`${origin}:8`, { isPrivate: false });
    107  await check_results({
    108    context,
    109    autofilled: "example.com:8888/",
    110    completed: "http://example.com:8888/",
    111    matches: [
    112      makeVisitResult(context, {
    113        uri: `http://${origin}:8888/`,
    114        title: `test visit for http://${origin}:8888/`,
    115        heuristic: true,
    116      }),
    117    ],
    118  });
    119  await cleanup();
    120 });
    121 
    122 // "EXaM" should match http://example.com/ and the case of the search string
    123 // should be preserved in the autofilled value.
    124 add_task(async function preserveCase() {
    125  await PlacesTestUtils.addVisits([
    126    {
    127      uri: "http://example.com/",
    128    },
    129  ]);
    130  let context = createContext("EXaM", { isPrivate: false });
    131  await check_results({
    132    context,
    133    autofilled: "EXaMple.com/",
    134    completed: "http://example.com/",
    135    matches: [
    136      makeVisitResult(context, {
    137        uri: `http://${origin}/`,
    138        title: `test visit for http://${origin}/`,
    139        heuristic: true,
    140      }),
    141    ],
    142  });
    143  await cleanup();
    144 });
    145 
    146 // "EXaM" should match http://example.com:8888/, the port should be completed,
    147 // and the case of the search string should be preserved in the autofilled
    148 // value.
    149 add_task(async function preserveCasePort() {
    150  await PlacesTestUtils.addVisits([
    151    {
    152      uri: "http://example.com:8888/",
    153    },
    154  ]);
    155  let context = createContext("EXaM", { isPrivate: false });
    156  await check_results({
    157    context,
    158    autofilled: "EXaMple.com:8888/",
    159    completed: "http://example.com:8888/",
    160    matches: [
    161      makeVisitResult(context, {
    162        uri: `http://${origin}:8888/`,
    163        title: `test visit for http://${origin}:8888/`,
    164        heuristic: true,
    165      }),
    166    ],
    167  });
    168  await cleanup();
    169 });
    170 
    171 // "example.com:89" should *not* match http://example.com:8888/.
    172 add_task(async function portNoMatch1() {
    173  await PlacesTestUtils.addVisits([
    174    {
    175      uri: "http://example.com:8888/",
    176    },
    177  ]);
    178  let context = createContext(`${origin}:89`, { isPrivate: false });
    179  await check_results({
    180    context,
    181    matches: [
    182      makeVisitResult(context, {
    183        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    184        uri: `http://${origin}:89/`,
    185        title: `${origin}:89/`,
    186        iconUri: "",
    187        heuristic: true,
    188        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    189      }),
    190    ],
    191  });
    192  await cleanup();
    193 });
    194 
    195 // "example.com:9" should *not* match http://example.com:8888/.
    196 add_task(async function portNoMatch2() {
    197  await PlacesTestUtils.addVisits([
    198    {
    199      uri: "http://example.com:8888/",
    200    },
    201  ]);
    202  let context = createContext(`${origin}:9`, { isPrivate: false });
    203  await check_results({
    204    context,
    205    matches: [
    206      makeVisitResult(context, {
    207        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    208        uri: `http://${origin}:9/`,
    209        title: `${origin}:9/`,
    210        iconUri: "",
    211        heuristic: true,
    212        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    213      }),
    214    ],
    215  });
    216  await cleanup();
    217 });
    218 
    219 // "example/" should *not* match http://example.com/.
    220 add_task(async function trailingSlash_2() {
    221  await PlacesTestUtils.addVisits([
    222    {
    223      uri: "http://example.com/",
    224    },
    225  ]);
    226  let context = createContext("example/", { isPrivate: false });
    227  await check_results({
    228    context,
    229    matches: [
    230      makeVisitResult(context, {
    231        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    232        uri: "http://example/",
    233        title: "example/",
    234        iconUri: "page-icon:http://example/",
    235        heuristic: true,
    236        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    237      }),
    238    ],
    239  });
    240  await cleanup();
    241 });
    242 
    243 // multi.dotted.domain, search up to dot.
    244 add_task(async function multidotted() {
    245  await PlacesTestUtils.addVisits([
    246    {
    247      uri: "http://www.example.co.jp:8888/",
    248    },
    249  ]);
    250  let context = createContext("www.example.co.", { isPrivate: false });
    251  await check_results({
    252    context,
    253    autofilled: "www.example.co.jp:8888/",
    254    completed: "http://www.example.co.jp:8888/",
    255    matches: [
    256      makeVisitResult(context, {
    257        uri: "http://www.example.co.jp:8888/",
    258        title: "test visit for http://www.example.co.jp:8888/",
    259        heuristic: true,
    260      }),
    261    ],
    262  });
    263  await cleanup();
    264 });
    265 
    266 add_task(async function test_ip() {
    267  // IP addresses have complicated rules around whether they show
    268  // HeuristicFallback's backup search result. Flip this pref to disable that
    269  // backup search and simplify ths subtest.
    270  Services.prefs.setBoolPref("keyword.enabled", false);
    271  for (let str of [
    272    "192.168.1.1/",
    273    "255.255.255.255:8080/",
    274    "[2001:db8::1428:57ab]/",
    275    "[::c0a8:5909]/",
    276    "[::1]/",
    277  ]) {
    278    info("testing " + str);
    279    await PlacesTestUtils.addVisits("http://" + str);
    280    for (let i = 1; i < str.length; ++i) {
    281      let context = createContext(str.substring(0, i), { isPrivate: false });
    282      await check_results({
    283        context,
    284        autofilled: str,
    285        completed: "http://" + str,
    286        matches: [
    287          makeVisitResult(context, {
    288            uri: "http://" + str,
    289            title: `test visit for http://${str}`,
    290            heuristic: true,
    291          }),
    292        ],
    293      });
    294    }
    295    await cleanup();
    296  }
    297  Services.prefs.clearUserPref("keyword.enabled");
    298 });
    299 
    300 // host starting with large number.
    301 add_task(async function large_number_host() {
    302  await PlacesTestUtils.addVisits([
    303    {
    304      uri: "http://12345example.it:8888/",
    305    },
    306  ]);
    307  let context = createContext("1234", { isPrivate: false });
    308  await check_results({
    309    context,
    310    autofilled: "12345example.it:8888/",
    311    completed: "http://12345example.it:8888/",
    312    matches: [
    313      makeVisitResult(context, {
    314        uri: "http://12345example.it:8888/",
    315        title: "test visit for http://12345example.it:8888/",
    316        heuristic: true,
    317      }),
    318    ],
    319  });
    320  await cleanup();
    321 });
    322 
    323 // When determining which origins should be autofilled, all the origins sharing
    324 // a host should be added together to get their combined frecency -- i.e.,
    325 // prefixes should be collapsed.  And then from that list, the origin with the
    326 // highest frecency should be chosen.
    327 add_task(async function groupByHost() {
    328  // Add some visits to the same host, example.com.  Add one http and two https
    329  // so that https has a higher frecency and is therefore the origin that should
    330  // be autofilled.  Also add another origin that has a higher frecency than
    331  // both so that alone, neither http nor https would be autofilled, but added
    332  // together they should be.
    333  await PlacesTestUtils.addVisits([
    334    { uri: "http://example.com/", visitDate: daysAgo(30) },
    335 
    336    // Have a higher frecency by being more recent. But not so recent that it
    337    // has a higher frecency than other visits that bump the origins threshold.
    338    { uri: "https://example.com/", visitDate: daysAgo(7) },
    339 
    340    {
    341      uri: "https://mozilla.org/",
    342      transition: PlacesUtils.history.TRANSITION_TYPED,
    343    },
    344    {
    345      uri: "https://mozilla.org/1",
    346      transition: PlacesUtils.history.TRANSITION_TYPED,
    347      visitDate: daysAgo(1),
    348    },
    349 
    350    // Add more origins to make the threshold higher
    351    { uri: "https://mozilla.com/" },
    352    { uri: "https://mozilla.ca/" },
    353  ]);
    354 
    355  let httpFrec = await getOriginFrecency("http://", "example.com");
    356  let httpsFrec = await getOriginFrecency("https://", "example.com");
    357  let otherFrec = await getOriginFrecency("https://", "mozilla.org");
    358 
    359  Assert.less(
    360    httpFrec,
    361    httpsFrec,
    362    "Frecency http://example.com is less than https://example.com"
    363  );
    364  Assert.less(
    365    httpsFrec,
    366    otherFrec,
    367    "Frecency of https://example.com is less than https://mozilla.org"
    368  );
    369 
    370  // Make sure the frecencies of the three origins are as expected in relation
    371  // to the threshold.
    372  let threshold = await getOriginAutofillThreshold();
    373  Assert.less(httpFrec, threshold, "http origin should be < threshold");
    374  Assert.less(httpsFrec, threshold, "https origin should be < threshold");
    375  Assert.lessOrEqual(
    376    threshold,
    377    otherFrec,
    378    "Other origin should cross threshold"
    379  );
    380 
    381  Assert.lessOrEqual(
    382    threshold,
    383    httpFrec + httpsFrec,
    384    "http and https origin added together should cross threshold"
    385  );
    386 
    387  // The https origin should be autofilled.
    388  let context = createContext("ex", { isPrivate: false });
    389  await check_results({
    390    context,
    391    autofilled: "example.com/",
    392    completed: "https://example.com/",
    393    matches: [
    394      makeVisitResult(context, {
    395        uri: "https://example.com/",
    396        title: "test visit for https://example.com/",
    397        heuristic: true,
    398      }),
    399    ],
    400  });
    401 
    402  await cleanup();
    403 });
    404 
    405 // This is the same as the previous (groupByHost), but it changes the standard
    406 // deviation multiplier by setting the corresponding pref.  This makes sure that
    407 // the pref is respected.
    408 add_task(async function groupByHostNonDefaultStddevMultiplier() {
    409  let stddevMultiplier = 1.5;
    410  Services.prefs.setCharPref(
    411    "browser.urlbar.autoFill.stddevMultiplier",
    412    Number(stddevMultiplier).toFixed(1)
    413  );
    414 
    415  await PlacesTestUtils.addVisits([
    416    { uri: "http://example.com/", visitDate: daysAgo(30) },
    417 
    418    { uri: "https://example.com/", visitDate: daysAgo(7) },
    419 
    420    { uri: "https://mozilla.org/" },
    421    { uri: "https://mozilla.org/1", visitDate: daysAgo(1) },
    422    { uri: "https://mozilla.org/2", visitDate: daysAgo(2) },
    423 
    424    // Add more origins to make the threshold higher
    425    { uri: "https://mozilla.com/" },
    426    { uri: "https://mozilla.ca/" },
    427  ]);
    428 
    429  let httpFrec = await getOriginFrecency("http://", "example.com");
    430  let httpsFrec = await getOriginFrecency("https://", "example.com");
    431  let otherFrec = await getOriginFrecency("https://", "mozilla.org");
    432  Assert.less(httpFrec, httpsFrec, "Sanity check");
    433  Assert.less(httpsFrec, otherFrec, "Sanity check");
    434 
    435  // Make sure the frecencies of the three origins are as expected in relation
    436  // to the threshold.
    437  let threshold = await getOriginAutofillThreshold();
    438  Assert.less(httpFrec, threshold, "http origin should be < threshold");
    439  Assert.less(httpsFrec, threshold, "https origin should be < threshold");
    440  Assert.lessOrEqual(
    441    threshold,
    442    otherFrec,
    443    "Other origin should cross threshold"
    444  );
    445 
    446  Assert.lessOrEqual(
    447    threshold,
    448    httpFrec + httpsFrec,
    449    "http and https origin added together should cross threshold"
    450  );
    451 
    452  // The https origin should be autofilled.
    453  let context = createContext("ex", { isPrivate: false });
    454  await check_results({
    455    context,
    456    autofilled: "example.com/",
    457    completed: "https://example.com/",
    458    matches: [
    459      makeVisitResult(context, {
    460        uri: "https://example.com/",
    461        title: "test visit for https://example.com/",
    462        heuristic: true,
    463      }),
    464    ],
    465  });
    466 
    467  Services.prefs.clearUserPref("browser.urlbar.autoFill.stddevMultiplier");
    468 
    469  await cleanup();
    470 });
    471 
    472 // This is similar to suggestHistoryFalse_bookmark_0 in test_autofill_tasks.js,
    473 // but it adds unbookmarked visits for multiple URLs with the same origin.
    474 add_task(async function suggestHistoryFalse_bookmark_multiple() {
    475  // Force only bookmarked pages to be suggested and therefore only bookmarked
    476  // pages to be completed.
    477  Services.prefs.setBoolPref("browser.urlbar.suggest.history", false);
    478 
    479  let search = "ex";
    480  let baseURL = "http://example.com/";
    481  let bookmarkedURL = baseURL + "bookmarked";
    482 
    483  // Add visits for three different URLs all sharing the same origin, and then
    484  // bookmark the second one.  After that, the origin should be autofilled.  The
    485  // reason for adding unbookmarked visits before and after adding the
    486  // bookmarked visit is to make sure our aggregate SQL query for determining
    487  // whether an origin is bookmarked is correct.
    488 
    489  await PlacesTestUtils.addVisits([
    490    {
    491      uri: baseURL + "other1",
    492    },
    493  ]);
    494  let context = createContext(search, { isPrivate: false });
    495  await check_results({
    496    context,
    497    matches: [
    498      makeSearchResult(context, {
    499        engineName: SUGGESTIONS_ENGINE_NAME,
    500        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    501        heuristic: true,
    502      }),
    503    ],
    504  });
    505 
    506  await PlacesTestUtils.addVisits([
    507    {
    508      uri: bookmarkedURL,
    509    },
    510  ]);
    511  context = createContext(search, { isPrivate: false });
    512  await check_results({
    513    context,
    514    matches: [
    515      makeSearchResult(context, {
    516        engineName: SUGGESTIONS_ENGINE_NAME,
    517        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    518        heuristic: true,
    519      }),
    520    ],
    521  });
    522 
    523  await PlacesTestUtils.addVisits([
    524    {
    525      uri: baseURL + "other2",
    526    },
    527  ]);
    528  context = createContext(search, { isPrivate: false });
    529  await check_results({
    530    context,
    531    matches: [
    532      makeSearchResult(context, {
    533        engineName: SUGGESTIONS_ENGINE_NAME,
    534        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    535        heuristic: true,
    536      }),
    537    ],
    538  });
    539 
    540  // Now bookmark the second URL.  It should be suggested and completed.
    541  await PlacesTestUtils.addBookmarkWithDetails({
    542    uri: bookmarkedURL,
    543  });
    544  context = createContext(search, { isPrivate: false });
    545  await check_results({
    546    context,
    547    autofilled: "example.com/",
    548    completed: baseURL,
    549    matches: [
    550      makeVisitResult(context, {
    551        uri: baseURL,
    552        title: UrlbarTestUtils.trimURL(baseURL),
    553        heuristic: true,
    554      }),
    555      makeBookmarkResult(context, {
    556        uri: bookmarkedURL,
    557        title: "A bookmark",
    558      }),
    559    ],
    560  });
    561 
    562  await cleanup();
    563 });
    564 
    565 // This is similar to suggestHistoryFalse_bookmark_prefix_0 in
    566 // autofill_test_autofill_originsAndQueries.js, but it adds unbookmarked visits
    567 // for multiple URLs with the same origin.
    568 add_task(async function suggestHistoryFalse_bookmark_prefix_multiple() {
    569  // Force only bookmarked pages to be suggested and therefore only bookmarked
    570  // pages to be completed.
    571  Services.prefs.setBoolPref("browser.urlbar.suggest.history", false);
    572 
    573  let search = "http://ex";
    574  let baseURL = "http://example.com/";
    575  let bookmarkedURL = baseURL + "bookmarked";
    576 
    577  // Add visits for three different URLs all sharing the same origin, and then
    578  // bookmark the second one.  After that, the origin should be autofilled.  The
    579  // reason for adding unbookmarked visits before and after adding the
    580  // bookmarked visit is to make sure our aggregate SQL query for determining
    581  // whether an origin is bookmarked is correct.
    582 
    583  await PlacesTestUtils.addVisits([
    584    {
    585      uri: baseURL + "other1",
    586    },
    587  ]);
    588  let context = createContext(search, { isPrivate: false });
    589  await check_results({
    590    context,
    591    matches: [
    592      makeVisitResult(context, {
    593        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    594        uri: `${search}/`,
    595        title: `${search}/`,
    596        iconUri: "",
    597        heuristic: true,
    598        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    599      }),
    600    ],
    601  });
    602 
    603  await PlacesTestUtils.addVisits([
    604    {
    605      uri: bookmarkedURL,
    606    },
    607  ]);
    608  context = createContext(search, { isPrivate: false });
    609  await check_results({
    610    context,
    611    matches: [
    612      makeVisitResult(context, {
    613        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    614        uri: `${search}/`,
    615        title: `${search}/`,
    616        iconUri: "",
    617        heuristic: true,
    618        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    619      }),
    620    ],
    621  });
    622 
    623  await PlacesTestUtils.addVisits([
    624    {
    625      uri: baseURL + "other2",
    626    },
    627  ]);
    628  context = createContext(search, { isPrivate: false });
    629  await check_results({
    630    context,
    631    matches: [
    632      makeVisitResult(context, {
    633        source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    634        uri: `${search}/`,
    635        title: `${search}/`,
    636        iconUri: "",
    637        heuristic: true,
    638        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    639      }),
    640    ],
    641  });
    642 
    643  // Now bookmark the second URL.  It should be suggested and completed.
    644  await PlacesTestUtils.addBookmarkWithDetails({
    645    uri: bookmarkedURL,
    646  });
    647  context = createContext(search, { isPrivate: false });
    648  await check_results({
    649    context,
    650    autofilled: "http://example.com/",
    651    completed: baseURL,
    652    matches: [
    653      makeVisitResult(context, {
    654        uri: baseURL,
    655        title: UrlbarTestUtils.trimURL(baseURL),
    656        heuristic: true,
    657      }),
    658      makeBookmarkResult(context, {
    659        uri: bookmarkedURL,
    660        title: "A bookmark",
    661      }),
    662    ],
    663  });
    664 
    665  await cleanup();
    666 });
    667 
    668 // When the autofilled URL is `example.com/`, a visit for `example.com/?` should
    669 // not be included in the results since it dupes the autofill result.
    670 add_task(async function searchParams() {
    671  await PlacesTestUtils.addVisits([
    672    "http://example.com/",
    673    "http://example.com/?",
    674    "http://example.com/?foo",
    675  ]);
    676 
    677  // First, do a search with autofill disabled to make sure the visits were
    678  // properly added. `example.com/?foo` has the highest frecency because it was
    679  // added last; `example.com/?` has the next highest. `example.com/` dupes
    680  // `example.com/?`, so it should not appear.
    681  UrlbarPrefs.set("autoFill", false);
    682  let context = createContext("ex", { isPrivate: false });
    683  await check_results({
    684    context,
    685    matches: [
    686      makeSearchResult(context, {
    687        engineName: SUGGESTIONS_ENGINE_NAME,
    688        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    689        heuristic: true,
    690      }),
    691      makeVisitResult(context, {
    692        uri: "http://example.com/?foo",
    693        title: "test visit for http://example.com/?foo",
    694      }),
    695      makeVisitResult(context, {
    696        uri: "http://example.com/?",
    697        title: "test visit for http://example.com/?",
    698      }),
    699    ],
    700  });
    701 
    702  // Now do a search with autofill enabled. This time `example.com/` will be
    703  // autofilled, and since `example.com/?` dupes it, `example.com/?` should not
    704  // appear.
    705  UrlbarPrefs.clear("autoFill");
    706  context = createContext("ex", { isPrivate: false });
    707  await check_results({
    708    context,
    709    autofilled: "example.com/",
    710    completed: "http://example.com/",
    711    matches: [
    712      makeVisitResult(context, {
    713        uri: "http://example.com/",
    714        title: "test visit for http://example.com/",
    715        heuristic: true,
    716      }),
    717      makeVisitResult(context, {
    718        uri: "http://example.com/?foo",
    719        title: "test visit for http://example.com/?foo",
    720      }),
    721    ],
    722  });
    723 
    724  await cleanup();
    725 });
    726 
    727 // When the autofilled URL is `example.com/`, a visit for `example.com/?` should
    728 // not be included in the results since it dupes the autofill result. (Same as
    729 // the previous task but with https URLs instead of http. There shouldn't be any
    730 // substantive difference.)
    731 add_task(async function searchParams_https() {
    732  await PlacesTestUtils.addVisits([
    733    "https://example.com/",
    734    "https://example.com/?",
    735    "https://example.com/?foo",
    736  ]);
    737 
    738  // First, do a search with autofill disabled to make sure the visits were
    739  // properly added. `example.com/?foo` has the highest frecency because it was
    740  // added last; `example.com/?` has the next highest. `example.com/` dupes
    741  // `example.com/?`, so it should not appear.
    742  UrlbarPrefs.set("autoFill", false);
    743  let context = createContext("ex", { isPrivate: false });
    744  await check_results({
    745    context,
    746    matches: [
    747      makeSearchResult(context, {
    748        engineName: SUGGESTIONS_ENGINE_NAME,
    749        providerName: HEURISTIC_FALLBACK_PROVIDERNAME,
    750        heuristic: true,
    751      }),
    752      makeVisitResult(context, {
    753        uri: "https://example.com/?foo",
    754        title: "test visit for https://example.com/?foo",
    755      }),
    756      makeVisitResult(context, {
    757        uri: "https://example.com/?",
    758        title: "test visit for https://example.com/?",
    759      }),
    760    ],
    761  });
    762 
    763  // Now do a search with autofill enabled. This time `example.com/` will be
    764  // autofilled, and since `example.com/?` dupes it, `example.com/?` should not
    765  // appear.
    766  UrlbarPrefs.clear("autoFill");
    767  context = createContext("ex", { isPrivate: false });
    768  await check_results({
    769    context,
    770    autofilled: "example.com/",
    771    completed: "https://example.com/",
    772    matches: [
    773      makeVisitResult(context, {
    774        uri: "https://example.com/",
    775        title: "test visit for https://example.com/",
    776        heuristic: true,
    777      }),
    778      makeVisitResult(context, {
    779        uri: "https://example.com/?foo",
    780        title: "test visit for https://example.com/?foo",
    781      }),
    782    ],
    783  });
    784 
    785  await cleanup();
    786 });
    787 
    788 // Checks an origin that looks like a prefix: a scheme with no dots + a port.
    789 add_task(async function originLooksLikePrefix() {
    790  let hostAndPort = "localhost:8888";
    791  let address = `http://${hostAndPort}/`;
    792  await PlacesTestUtils.addVisits([{ uri: address }]);
    793 
    794  // addTestSuggestionsEngine adds a search engine
    795  // with localhost as a server, so we have to disable the
    796  // TTS result or else it will show up as a second result
    797  // when searching l to localhost
    798  UrlbarPrefs.set("suggest.engines", false);
    799 
    800  for (let search of ["lo", "localhost", "localhost:", "localhost:8888"]) {
    801    let context = createContext(search, { isPrivate: false });
    802    await check_results({
    803      context,
    804      autofilled: hostAndPort + "/",
    805      completed: address,
    806      matches: [
    807        makeVisitResult(context, {
    808          uri: address,
    809          title: `test visit for http://${hostAndPort}/`,
    810          heuristic: true,
    811        }),
    812      ],
    813    });
    814  }
    815  await cleanup();
    816 });
    817 
    818 // Checks an origin whose prefix is "about:".
    819 add_task(async function about() {
    820  const testData = [
    821    {
    822      uri: "about:config",
    823      input: "conf",
    824      results: [
    825        context =>
    826          makeSearchResult(context, {
    827            engineName: "Suggestions",
    828            heuristic: true,
    829          }),
    830        context =>
    831          makeBookmarkResult(context, {
    832            uri: "about:config",
    833            title: "A bookmark",
    834          }),
    835      ],
    836    },
    837    {
    838      uri: "about:blank",
    839      input: "about:blan",
    840      results: [
    841        context =>
    842          makeVisitResult(context, {
    843            uri: "about:blan",
    844            title: "about:blan",
    845            source: UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
    846            heuristic: true,
    847          }),
    848        context =>
    849          makeBookmarkResult(context, {
    850            uri: "about:blank",
    851            title: "A bookmark",
    852          }),
    853      ],
    854    },
    855  ];
    856 
    857  for (const { uri, input, results } of testData) {
    858    await PlacesTestUtils.addBookmarkWithDetails({ uri });
    859 
    860    const context = createContext(input, { isPrivate: false });
    861    await check_results({
    862      context,
    863      matches: results.map(f => f(context)),
    864    });
    865    await cleanup();
    866  }
    867 });
    868 
    869 // Checks an origin whose prefix is "place:".
    870 add_task(async function place() {
    871  const testData = [
    872    {
    873      uri: "place:transition=7&sort=4",
    874      input: "tran",
    875    },
    876    {
    877      uri: "place:transition=7&sort=4",
    878      input: "place:tran",
    879    },
    880  ];
    881 
    882  for (const { uri, input } of testData) {
    883    await PlacesTestUtils.addBookmarkWithDetails({ uri });
    884 
    885    const context = createContext(input, { isPrivate: false });
    886    await check_results({
    887      context,
    888      matches: [
    889        makeSearchResult(context, {
    890          engineName: "Suggestions",
    891          heuristic: true,
    892        }),
    893      ],
    894    });
    895    await cleanup();
    896  }
    897 });
    898 
    899 add_task(async function nullTitle() {
    900  await doTitleTest({
    901    visits: [
    902      {
    903        uri: "http://example.com/",
    904        // Set title of visits data to an empty string causes
    905        // the title to be null in the database.
    906        title: "",
    907        frecencyBucket: "high",
    908      },
    909      {
    910        uri: "https://www.example.com/",
    911        title: "medium frecency",
    912        frecencyBucket: "medium",
    913      },
    914      {
    915        uri: "http://www.example.com/",
    916        title: "low frecency",
    917        frecencyBucket: "low",
    918      },
    919    ],
    920    input: "example.com",
    921    expected: {
    922      autofilled: "example.com/",
    923      completed: "http://example.com/",
    924      matches: context => [
    925        makeVisitResult(context, {
    926          uri: "http://example.com/",
    927          title: "medium frecency",
    928          heuristic: true,
    929        }),
    930        makeVisitResult(context, {
    931          uri: "https://www.example.com/",
    932          title: "medium frecency",
    933        }),
    934      ],
    935    },
    936  });
    937 });
    938 
    939 add_task(async function domainTitle() {
    940  await doTitleTest({
    941    visits: [
    942      {
    943        uri: "http://example.com/",
    944        title: "example.com",
    945        frecencyBucket: "high",
    946      },
    947      {
    948        uri: "https://www.example.com/",
    949        title: "",
    950        frecencyBucket: "medium",
    951      },
    952      {
    953        uri: "http://www.example.com/",
    954        title: "lowest frecency but has title",
    955        frecencyBucket: "low",
    956      },
    957    ],
    958    input: "example.com",
    959    expected: {
    960      autofilled: "example.com/",
    961      completed: "http://example.com/",
    962      matches: context => [
    963        makeVisitResult(context, {
    964          uri: "http://example.com/",
    965          title: "lowest frecency but has title",
    966          heuristic: true,
    967        }),
    968        makeVisitResult(context, {
    969          uri: "https://www.example.com/",
    970          title: "www.example.com",
    971        }),
    972      ],
    973    },
    974  });
    975 });
    976 
    977 add_task(async function exactMatchedTitle() {
    978  await doTitleTest({
    979    visits: [
    980      {
    981        uri: "http://example.com/",
    982        title: "exact match",
    983        frecencyBucket: "medium",
    984      },
    985      {
    986        uri: "https://www.example.com/",
    987        title: "high frecency uri",
    988        frecencyBucket: "high",
    989      },
    990    ],
    991    input: "http://example.com/",
    992    expected: {
    993      autofilled: "http://example.com/",
    994      completed: "http://example.com/",
    995      matches: context => [
    996        makeVisitResult(context, {
    997          uri: "http://example.com/",
    998          title: "exact match",
    999          heuristic: true,
   1000        }),
   1001        makeVisitResult(context, {
   1002          uri: "https://www.example.com/",
   1003          title: "high frecency uri",
   1004        }),
   1005      ],
   1006    },
   1007  });
   1008 });
   1009 
   1010 async function doTitleTest({ visits, input, expected }) {
   1011  for (let visit of visits) {
   1012    switch (visit.frecencyBucket) {
   1013      case "high": {
   1014        await PlacesTestUtils.addVisits({
   1015          title: visit.title,
   1016          uri: visit.uri,
   1017          transition: PlacesUtils.history.TRANSITION_TYPED,
   1018        });
   1019        break;
   1020      }
   1021      case "medium": {
   1022        await PlacesTestUtils.addVisits({ title: visit.title, uri: visit.uri });
   1023        break;
   1024      }
   1025      case "low": {
   1026        // Non-bookmarked sponsors are categorized as low.
   1027        await PlacesTestUtils.addVisits({
   1028          title: visit.title,
   1029          uri: visit.uri,
   1030        });
   1031        // Add visits doesn't allow you to set the visit source.
   1032        await PlacesUtils.withConnectionWrapper("setVisitSource", async db => {
   1033          await db.execute(
   1034            `
   1035            UPDATE moz_historyvisits
   1036            SET source = :source
   1037            WHERE place_id = (SELECT id FROM moz_places WHERE url = :url)`,
   1038            {
   1039              url: visit.uri,
   1040              source: PlacesUtils.history.VISIT_SOURCE_SPONSORED,
   1041            }
   1042          );
   1043          await db.execute(
   1044            `
   1045            UPDATE moz_places
   1046            SET recalc_frecency = 1
   1047            WHERE id = (SELECT id FROM moz_places WHERE url = :url)`,
   1048            {
   1049              url: visit.uri,
   1050            }
   1051          );
   1052        });
   1053        await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
   1054        break;
   1055      }
   1056    }
   1057  }
   1058 
   1059  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
   1060 
   1061  const context = createContext(input, { isPrivate: false });
   1062  await check_results({
   1063    context,
   1064    autofilled: expected.autofilled,
   1065    completed: expected.completed,
   1066    matches: expected.matches(context),
   1067  });
   1068 
   1069  await cleanup();
   1070 }
   1071 
   1072 /* Tests sorting order when only unvisited bookmarks are available (e.g. in
   1073  permanent private browsing mode), then the only information we have is the
   1074  number of bookmarks per origin, and we're going to use that. */
   1075 add_task(async function just_multiple_unvisited_bookmarks() {
   1076  // These are sorted to avoid confusion with natural sorting, so the one with
   1077  // the highest score is added in the middle.
   1078  let filledUrl = "https://www.tld2.com/";
   1079  let urls = [
   1080    {
   1081      url: "https://tld1.com/",
   1082      count: 1,
   1083    },
   1084    {
   1085      url: "https://tld2.com/",
   1086      count: 2,
   1087    },
   1088    {
   1089      url: filledUrl,
   1090      count: 2,
   1091    },
   1092    {
   1093      url: "https://tld3.com/",
   1094      count: 3,
   1095    },
   1096  ];
   1097 
   1098  await PlacesUtils.history.clear();
   1099  for (let { url, count } of urls) {
   1100    while (count--) {
   1101      await PlacesTestUtils.addBookmarkWithDetails({
   1102        uri: url,
   1103      });
   1104    }
   1105  }
   1106  await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
   1107 
   1108  let context = createContext("tld", { isPrivate: false });
   1109  await check_results({
   1110    context,
   1111    autofilled: "tld2.com/",
   1112    completed: filledUrl,
   1113    matches: [
   1114      makeVisitResult(context, {
   1115        uri: filledUrl,
   1116        title: "A bookmark",
   1117        heuristic: true,
   1118      }),
   1119      makeBookmarkResult(context, {
   1120        uri: "https://tld3.com/",
   1121        title: "A bookmark",
   1122      }),
   1123      makeBookmarkResult(context, {
   1124        uri: "https://tld2.com/",
   1125        title: "A bookmark",
   1126      }),
   1127      makeBookmarkResult(context, {
   1128        uri: "https://tld1.com/",
   1129        title: "A bookmark",
   1130      }),
   1131    ],
   1132  });
   1133 
   1134  await cleanup();
   1135 });