tor-browser

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

test_quicksuggest_impressionCaps.js (93778B)


      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 // Tests impression frequency capping for quick suggest results.
      6 
      7 "use strict";
      8 
      9 ChromeUtils.defineESModuleGetters(this, {
     10  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
     11  setTimeout: "resource://gre/modules/Timer.sys.mjs",
     12 });
     13 
     14 const REMOTE_SETTINGS_RESULTS = [
     15  {
     16    id: 1,
     17    url: "http://example.com/sponsored",
     18    title: "Sponsored suggestion",
     19    keywords: ["sponsored"],
     20    click_url: "http://example.com/click",
     21    impression_url: "http://example.com/impression",
     22    advertiser: "TestAdvertiser",
     23    iab_category: "22 - Shopping",
     24  },
     25  {
     26    id: 2,
     27    url: "http://example.com/nonsponsored",
     28    title: "Non-sponsored suggestion",
     29    keywords: ["nonsponsored"],
     30    click_url: "http://example.com/click",
     31    impression_url: "http://example.com/impression",
     32    advertiser: "TestAdvertiser",
     33    iab_category: "5 - Education",
     34  },
     35 ];
     36 
     37 const EXPECTED_SPONSORED_URLBAR_RESULT = {
     38  type: UrlbarUtils.RESULT_TYPE.URL,
     39  source: UrlbarUtils.RESULT_SOURCE.SEARCH,
     40  heuristic: false,
     41  payload: {
     42    telemetryType: "adm_sponsored",
     43    url: "http://example.com/sponsored",
     44    originalUrl: "http://example.com/sponsored",
     45    title: "Sponsored suggestion",
     46    icon: null,
     47    isSponsored: true,
     48    sponsoredImpressionUrl: "http://example.com/impression",
     49    sponsoredClickUrl: "http://example.com/click",
     50    sponsoredBlockId: 1,
     51    sponsoredAdvertiser: "TestAdvertiser",
     52    sponsoredIabCategory: "22 - Shopping",
     53    descriptionL10n: { id: "urlbar-result-action-sponsored" },
     54    helpUrl: QuickSuggest.HELP_URL,
     55    helpL10n: {
     56      id: "urlbar-result-menu-learn-more-about-firefox-suggest",
     57    },
     58    isBlockable: true,
     59    source: "remote-settings",
     60    provider: "AdmWikipedia",
     61  },
     62 };
     63 
     64 const EXPECTED_NONSPONSORED_URLBAR_RESULT = {
     65  type: UrlbarUtils.RESULT_TYPE.URL,
     66  source: UrlbarUtils.RESULT_SOURCE.SEARCH,
     67  heuristic: false,
     68  payload: {
     69    telemetryType: "adm_nonsponsored",
     70    url: "http://example.com/nonsponsored",
     71    originalUrl: "http://example.com/nonsponsored",
     72    title: "Non-sponsored suggestion",
     73    icon: null,
     74    isSponsored: false,
     75    sponsoredImpressionUrl: "http://example.com/impression",
     76    sponsoredClickUrl: "http://example.com/click",
     77    sponsoredBlockId: 2,
     78    sponsoredAdvertiser: "TestAdvertiser",
     79    sponsoredIabCategory: "5 - Education",
     80    helpUrl: QuickSuggest.HELP_URL,
     81    helpL10n: {
     82      id: "urlbar-result-menu-learn-more-about-firefox-suggest",
     83    },
     84    isBlockable: true,
     85    source: "remote-settings",
     86    provider: "AdmWikipedia",
     87  },
     88 };
     89 
     90 let gSandbox;
     91 let gDateNowStub;
     92 let gStartupDateMsStub;
     93 
     94 add_setup(async () => {
     95  // Disable search suggestions so we don't hit the network.
     96  Services.prefs.setBoolPref("browser.search.suggest.enabled", false);
     97 
     98  await QuickSuggestTestUtils.ensureQuickSuggestInit({
     99    remoteSettingsRecords: [
    100      {
    101        type: "data",
    102        attachment: REMOTE_SETTINGS_RESULTS,
    103      },
    104    ],
    105    prefs: [
    106      ["quicksuggest.impressionCaps.sponsoredEnabled", true],
    107      ["quicksuggest.impressionCaps.nonSponsoredEnabled", true],
    108      ["suggest.quicksuggest.all", true],
    109      ["suggest.quicksuggest.sponsored", true],
    110    ],
    111  });
    112 
    113  // Set up a sinon stub for the `Date.now()` implementation inside of
    114  // UrlbarProviderQuickSuggest. This lets us test searches performed at
    115  // specific times. See `doTimedCallbacks()` for more info.
    116  gSandbox = sinon.createSandbox();
    117  gDateNowStub = gSandbox.stub(
    118    Cu.getGlobalForObject(UrlbarProviderQuickSuggest).Date,
    119    "now"
    120  );
    121 
    122  // Set up a sinon stub for `UrlbarProviderQuickSuggest._getStartupDateMs()` to
    123  // let the test override the startup date.
    124  gStartupDateMsStub = gSandbox.stub(
    125    QuickSuggest.impressionCaps,
    126    "_getStartupDateMs"
    127  );
    128  gStartupDateMsStub.returns(0);
    129 });
    130 
    131 // Tests a single interval.
    132 add_task(async function oneInterval() {
    133  await doTest({
    134    config: {
    135      impression_caps: {
    136        sponsored: {
    137          custom: [{ interval_s: 3, max_count: 1 }],
    138        },
    139      },
    140    },
    141    callback: async () => {
    142      await doTimedSearches("sponsored", {
    143        0: {
    144          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    145          telemetry: {
    146            events: [
    147              {
    148                object: "hit",
    149                extra: {
    150                  eventDate: "0",
    151                  intervalSeconds: "3",
    152                  maxCount: "1",
    153                  startDate: "0",
    154                  impressionDate: "0",
    155                  count: "1",
    156                  type: "sponsored",
    157                  eventCount: "1",
    158                },
    159              },
    160            ],
    161          },
    162        },
    163        1: {
    164          results: [[]],
    165        },
    166        2: {
    167          results: [[]],
    168        },
    169        3: {
    170          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    171          telemetry: {
    172            events: [
    173              {
    174                object: "reset",
    175                extra: {
    176                  eventDate: "3000",
    177                  intervalSeconds: "3",
    178                  maxCount: "1",
    179                  startDate: "0",
    180                  impressionDate: "0",
    181                  count: "1",
    182                  type: "sponsored",
    183                  eventCount: "1",
    184                },
    185              },
    186              {
    187                object: "hit",
    188                extra: {
    189                  eventDate: "3000",
    190                  intervalSeconds: "3",
    191                  maxCount: "1",
    192                  startDate: "3000",
    193                  impressionDate: "3000",
    194                  count: "1",
    195                  type: "sponsored",
    196                  eventCount: "1",
    197                },
    198              },
    199            ],
    200          },
    201        },
    202        4: {
    203          results: [[]],
    204        },
    205        5: {
    206          results: [[]],
    207        },
    208      });
    209    },
    210  });
    211 });
    212 
    213 // Tests multiple intervals.
    214 add_task(async function multipleIntervals() {
    215  await doTest({
    216    config: {
    217      impression_caps: {
    218        sponsored: {
    219          custom: [
    220            { interval_s: 1, max_count: 1 },
    221            { interval_s: 5, max_count: 3 },
    222            { interval_s: 10, max_count: 5 },
    223          ],
    224        },
    225      },
    226    },
    227    callback: async () => {
    228      await doTimedSearches("sponsored", {
    229        // 0s: 1 new impression; 1 impression total
    230        0: {
    231          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    232          telemetry: {
    233            events: [
    234              // hit: interval_s: 1, max_count: 1
    235              {
    236                object: "hit",
    237                extra: {
    238                  eventDate: "0",
    239                  intervalSeconds: "1",
    240                  maxCount: "1",
    241                  startDate: "0",
    242                  impressionDate: "0",
    243                  count: "1",
    244                  type: "sponsored",
    245                  eventCount: "1",
    246                },
    247              },
    248            ],
    249          },
    250        },
    251        // 1s: 1 new impression; 2 impressions total
    252        1: {
    253          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    254          telemetry: {
    255            events: [
    256              // reset: interval_s: 1, max_count: 1
    257              {
    258                object: "reset",
    259                extra: {
    260                  eventDate: "1000",
    261                  intervalSeconds: "1",
    262                  maxCount: "1",
    263                  startDate: "0",
    264                  impressionDate: "0",
    265                  count: "1",
    266                  type: "sponsored",
    267                  eventCount: "1",
    268                },
    269              },
    270              // hit: interval_s: 1, max_count: 1
    271              {
    272                object: "hit",
    273                extra: {
    274                  eventDate: "1000",
    275                  intervalSeconds: "1",
    276                  maxCount: "1",
    277                  startDate: "1000",
    278                  impressionDate: "1000",
    279                  count: "1",
    280                  type: "sponsored",
    281                  eventCount: "1",
    282                },
    283              },
    284            ],
    285          },
    286        },
    287        // 2s: 1 new impression; 3 impressions total
    288        2: {
    289          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    290          telemetry: {
    291            events: [
    292              // reset: interval_s: 1, max_count: 1
    293              {
    294                object: "reset",
    295                extra: {
    296                  eventDate: "2000",
    297                  intervalSeconds: "1",
    298                  maxCount: "1",
    299                  startDate: "1000",
    300                  impressionDate: "1000",
    301                  count: "1",
    302                  type: "sponsored",
    303                  eventCount: "1",
    304                },
    305              },
    306              // hit: interval_s: 1, max_count: 1
    307              {
    308                object: "hit",
    309                extra: {
    310                  eventDate: "2000",
    311                  intervalSeconds: "1",
    312                  maxCount: "1",
    313                  startDate: "2000",
    314                  impressionDate: "2000",
    315                  count: "1",
    316                  type: "sponsored",
    317                  eventCount: "1",
    318                },
    319              },
    320              // hit: interval_s: 5, max_count: 3
    321              {
    322                object: "hit",
    323                extra: {
    324                  eventDate: "2000",
    325                  intervalSeconds: "5",
    326                  maxCount: "3",
    327                  startDate: "0",
    328                  impressionDate: "2000",
    329                  count: "3",
    330                  type: "sponsored",
    331                  eventCount: "1",
    332                },
    333              },
    334            ],
    335          },
    336        },
    337        // 3s: no new impressions; 3 impressions total
    338        3: {
    339          results: [[]],
    340          telemetry: {
    341            events: [
    342              // reset: interval_s: 1, max_count: 1
    343              {
    344                object: "reset",
    345                extra: {
    346                  eventDate: "3000",
    347                  intervalSeconds: "1",
    348                  maxCount: "1",
    349                  startDate: "2000",
    350                  impressionDate: "2000",
    351                  count: "1",
    352                  type: "sponsored",
    353                  eventCount: "1",
    354                },
    355              },
    356            ],
    357          },
    358        },
    359        // 4s: no new impressions; 3 impressions total
    360        4: {
    361          results: [[]],
    362          telemetry: {
    363            events: [
    364              // reset: interval_s: 1, max_count: 1
    365              {
    366                object: "reset",
    367                extra: {
    368                  eventDate: "4000",
    369                  intervalSeconds: "1",
    370                  maxCount: "1",
    371                  startDate: "3000",
    372                  impressionDate: "2000",
    373                  count: "0",
    374                  type: "sponsored",
    375                  eventCount: "1",
    376                },
    377              },
    378            ],
    379          },
    380        },
    381        // 5s: 1 new impression; 4 impressions total
    382        5: {
    383          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    384          telemetry: {
    385            events: [
    386              // reset: interval_s: 1, max_count: 1
    387              {
    388                object: "reset",
    389                extra: {
    390                  eventDate: "5000",
    391                  intervalSeconds: "1",
    392                  maxCount: "1",
    393                  startDate: "4000",
    394                  impressionDate: "2000",
    395                  count: "0",
    396                  type: "sponsored",
    397                  eventCount: "1",
    398                },
    399              },
    400              // reset: interval_s: 5, max_count: 3
    401              {
    402                object: "reset",
    403                extra: {
    404                  eventDate: "5000",
    405                  intervalSeconds: "5",
    406                  maxCount: "3",
    407                  startDate: "0",
    408                  impressionDate: "2000",
    409                  count: "3",
    410                  type: "sponsored",
    411                  eventCount: "1",
    412                },
    413              },
    414              // hit: interval_s: 1, max_count: 1
    415              {
    416                object: "hit",
    417                extra: {
    418                  eventDate: "5000",
    419                  intervalSeconds: "1",
    420                  maxCount: "1",
    421                  startDate: "5000",
    422                  impressionDate: "5000",
    423                  count: "1",
    424                  type: "sponsored",
    425                  eventCount: "1",
    426                },
    427              },
    428            ],
    429          },
    430        },
    431        // 6s: 1 new impression; 5 impressions total
    432        6: {
    433          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    434          telemetry: {
    435            events: [
    436              // reset: interval_s: 1, max_count: 1
    437              {
    438                object: "reset",
    439                extra: {
    440                  eventDate: "6000",
    441                  intervalSeconds: "1",
    442                  maxCount: "1",
    443                  startDate: "5000",
    444                  impressionDate: "5000",
    445                  count: "1",
    446                  type: "sponsored",
    447                  eventCount: "1",
    448                },
    449              },
    450              // hit: interval_s: 1, max_count: 1
    451              {
    452                object: "hit",
    453                extra: {
    454                  eventDate: "6000",
    455                  intervalSeconds: "1",
    456                  maxCount: "1",
    457                  startDate: "6000",
    458                  impressionDate: "6000",
    459                  count: "1",
    460                  type: "sponsored",
    461                  eventCount: "1",
    462                },
    463              },
    464              // hit: interval_s: 10, max_count: 5
    465              {
    466                object: "hit",
    467                extra: {
    468                  eventDate: "6000",
    469                  intervalSeconds: "10",
    470                  maxCount: "5",
    471                  startDate: "0",
    472                  impressionDate: "6000",
    473                  count: "5",
    474                  type: "sponsored",
    475                  eventCount: "1",
    476                },
    477              },
    478            ],
    479          },
    480        },
    481        // 7s: no new impressions; 5 impressions total
    482        7: {
    483          results: [[]],
    484          telemetry: {
    485            events: [
    486              // reset: interval_s: 1, max_count: 1
    487              {
    488                object: "reset",
    489                extra: {
    490                  eventDate: "7000",
    491                  intervalSeconds: "1",
    492                  maxCount: "1",
    493                  startDate: "6000",
    494                  impressionDate: "6000",
    495                  count: "1",
    496                  type: "sponsored",
    497                  eventCount: "1",
    498                },
    499              },
    500            ],
    501          },
    502        },
    503        // 8s: no new impressions; 5 impressions total
    504        8: {
    505          results: [[]],
    506          telemetry: {
    507            events: [
    508              // reset: interval_s: 1, max_count: 1
    509              {
    510                object: "reset",
    511                extra: {
    512                  eventDate: "8000",
    513                  intervalSeconds: "1",
    514                  maxCount: "1",
    515                  startDate: "7000",
    516                  impressionDate: "6000",
    517                  count: "0",
    518                  type: "sponsored",
    519                  eventCount: "1",
    520                },
    521              },
    522            ],
    523          },
    524        },
    525        // 9s: no new impressions; 5 impressions total
    526        9: {
    527          results: [[]],
    528          telemetry: {
    529            events: [
    530              // reset: interval_s: 1, max_count: 1
    531              {
    532                object: "reset",
    533                extra: {
    534                  eventDate: "9000",
    535                  intervalSeconds: "1",
    536                  maxCount: "1",
    537                  startDate: "8000",
    538                  impressionDate: "6000",
    539                  count: "0",
    540                  type: "sponsored",
    541                  eventCount: "1",
    542                },
    543              },
    544            ],
    545          },
    546        },
    547        // 10s: 1 new impression; 6 impressions total
    548        10: {
    549          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    550          telemetry: {
    551            events: [
    552              // reset: interval_s: 1, max_count: 1
    553              {
    554                object: "reset",
    555                extra: {
    556                  eventDate: "10000",
    557                  intervalSeconds: "1",
    558                  maxCount: "1",
    559                  startDate: "9000",
    560                  impressionDate: "6000",
    561                  count: "0",
    562                  type: "sponsored",
    563                  eventCount: "1",
    564                },
    565              },
    566              // reset: interval_s: 5, max_count: 3
    567              {
    568                object: "reset",
    569                extra: {
    570                  eventDate: "10000",
    571                  intervalSeconds: "5",
    572                  maxCount: "3",
    573                  startDate: "5000",
    574                  impressionDate: "6000",
    575                  count: "2",
    576                  type: "sponsored",
    577                  eventCount: "1",
    578                },
    579              },
    580              // reset: interval_s: 10, max_count: 5
    581              {
    582                object: "reset",
    583                extra: {
    584                  eventDate: "10000",
    585                  intervalSeconds: "10",
    586                  maxCount: "5",
    587                  startDate: "0",
    588                  impressionDate: "6000",
    589                  count: "5",
    590                  type: "sponsored",
    591                  eventCount: "1",
    592                },
    593              },
    594              // hit: interval_s: 1, max_count: 1
    595              {
    596                object: "hit",
    597                extra: {
    598                  eventDate: "10000",
    599                  intervalSeconds: "1",
    600                  maxCount: "1",
    601                  startDate: "10000",
    602                  impressionDate: "10000",
    603                  count: "1",
    604                  type: "sponsored",
    605                  eventCount: "1",
    606                },
    607              },
    608            ],
    609          },
    610        },
    611        // 11s: 1 new impression; 7 impressions total
    612        11: {
    613          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    614          telemetry: {
    615            events: [
    616              // reset: interval_s: 1, max_count: 1
    617              {
    618                object: "reset",
    619                extra: {
    620                  eventDate: "11000",
    621                  intervalSeconds: "1",
    622                  maxCount: "1",
    623                  startDate: "10000",
    624                  impressionDate: "10000",
    625                  count: "1",
    626                  type: "sponsored",
    627                  eventCount: "1",
    628                },
    629              },
    630              // hit: interval_s: 1, max_count: 1
    631              {
    632                object: "hit",
    633                extra: {
    634                  eventDate: "11000",
    635                  intervalSeconds: "1",
    636                  maxCount: "1",
    637                  startDate: "11000",
    638                  impressionDate: "11000",
    639                  count: "1",
    640                  type: "sponsored",
    641                  eventCount: "1",
    642                },
    643              },
    644            ],
    645          },
    646        },
    647        // 12s: 1 new impression; 8 impressions total
    648        12: {
    649          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    650          telemetry: {
    651            events: [
    652              // reset: interval_s: 1, max_count: 1
    653              {
    654                object: "reset",
    655                extra: {
    656                  eventDate: "12000",
    657                  intervalSeconds: "1",
    658                  maxCount: "1",
    659                  startDate: "11000",
    660                  impressionDate: "11000",
    661                  count: "1",
    662                  type: "sponsored",
    663                  eventCount: "1",
    664                },
    665              },
    666              // hit: interval_s: 1, max_count: 1
    667              {
    668                object: "hit",
    669                extra: {
    670                  eventDate: "12000",
    671                  intervalSeconds: "1",
    672                  maxCount: "1",
    673                  startDate: "12000",
    674                  impressionDate: "12000",
    675                  count: "1",
    676                  type: "sponsored",
    677                  eventCount: "1",
    678                },
    679              },
    680              // hit: interval_s: 5, max_count: 3
    681              {
    682                object: "hit",
    683                extra: {
    684                  eventDate: "12000",
    685                  intervalSeconds: "5",
    686                  maxCount: "3",
    687                  startDate: "10000",
    688                  impressionDate: "12000",
    689                  count: "3",
    690                  type: "sponsored",
    691                  eventCount: "1",
    692                },
    693              },
    694            ],
    695          },
    696        },
    697        // 13s: no new impressions; 8 impressions total
    698        13: {
    699          results: [[]],
    700          telemetry: {
    701            events: [
    702              // reset: interval_s: 1, max_count: 1
    703              {
    704                object: "reset",
    705                extra: {
    706                  eventDate: "13000",
    707                  intervalSeconds: "1",
    708                  maxCount: "1",
    709                  startDate: "12000",
    710                  impressionDate: "12000",
    711                  count: "1",
    712                  type: "sponsored",
    713                  eventCount: "1",
    714                },
    715              },
    716            ],
    717          },
    718        },
    719        // 14s: no new impressions; 8 impressions total
    720        14: {
    721          results: [[]],
    722          telemetry: {
    723            events: [
    724              // reset: interval_s: 1, max_count: 1
    725              {
    726                object: "reset",
    727                extra: {
    728                  eventDate: "14000",
    729                  intervalSeconds: "1",
    730                  maxCount: "1",
    731                  startDate: "13000",
    732                  impressionDate: "12000",
    733                  count: "0",
    734                  type: "sponsored",
    735                  eventCount: "1",
    736                },
    737              },
    738            ],
    739          },
    740        },
    741        // 15s: 1 new impression; 9 impressions total
    742        15: {
    743          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    744          telemetry: {
    745            events: [
    746              // reset: interval_s: 1, max_count: 1
    747              {
    748                object: "reset",
    749                extra: {
    750                  eventDate: "15000",
    751                  intervalSeconds: "1",
    752                  maxCount: "1",
    753                  startDate: "14000",
    754                  impressionDate: "12000",
    755                  count: "0",
    756                  type: "sponsored",
    757                  eventCount: "1",
    758                },
    759              },
    760              // reset: interval_s: 5, max_count: 3
    761              {
    762                object: "reset",
    763                extra: {
    764                  eventDate: "15000",
    765                  intervalSeconds: "5",
    766                  maxCount: "3",
    767                  startDate: "10000",
    768                  impressionDate: "12000",
    769                  count: "3",
    770                  type: "sponsored",
    771                  eventCount: "1",
    772                },
    773              },
    774              // hit: interval_s: 1, max_count: 1
    775              {
    776                object: "hit",
    777                extra: {
    778                  eventDate: "15000",
    779                  intervalSeconds: "1",
    780                  maxCount: "1",
    781                  startDate: "15000",
    782                  impressionDate: "15000",
    783                  count: "1",
    784                  type: "sponsored",
    785                  eventCount: "1",
    786                },
    787              },
    788            ],
    789          },
    790        },
    791        // 16s: 1 new impression; 10 impressions total
    792        16: {
    793          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    794          telemetry: {
    795            events: [
    796              // reset: interval_s: 1, max_count: 1
    797              {
    798                object: "reset",
    799                extra: {
    800                  eventDate: "16000",
    801                  intervalSeconds: "1",
    802                  maxCount: "1",
    803                  startDate: "15000",
    804                  impressionDate: "15000",
    805                  count: "1",
    806                  type: "sponsored",
    807                  eventCount: "1",
    808                },
    809              },
    810              // hit: interval_s: 1, max_count: 1
    811              {
    812                object: "hit",
    813                extra: {
    814                  eventDate: "16000",
    815                  intervalSeconds: "1",
    816                  maxCount: "1",
    817                  startDate: "16000",
    818                  impressionDate: "16000",
    819                  count: "1",
    820                  type: "sponsored",
    821                  eventCount: "1",
    822                },
    823              },
    824              // hit: interval_s: 10, max_count: 5
    825              {
    826                object: "hit",
    827                extra: {
    828                  eventDate: "16000",
    829                  intervalSeconds: "10",
    830                  maxCount: "5",
    831                  startDate: "10000",
    832                  impressionDate: "16000",
    833                  count: "5",
    834                  type: "sponsored",
    835                  eventCount: "1",
    836                },
    837              },
    838            ],
    839          },
    840        },
    841        // 17s: no new impressions; 10 impressions total
    842        17: {
    843          results: [[]],
    844          telemetry: {
    845            events: [
    846              // reset: interval_s: 1, max_count: 1
    847              {
    848                object: "reset",
    849                extra: {
    850                  eventDate: "17000",
    851                  intervalSeconds: "1",
    852                  maxCount: "1",
    853                  startDate: "16000",
    854                  impressionDate: "16000",
    855                  count: "1",
    856                  type: "sponsored",
    857                  eventCount: "1",
    858                },
    859              },
    860            ],
    861          },
    862        },
    863        // 18s: no new impressions; 10 impressions total
    864        18: {
    865          results: [[]],
    866          telemetry: {
    867            events: [
    868              // reset: interval_s: 1, max_count: 1
    869              {
    870                object: "reset",
    871                extra: {
    872                  eventDate: "18000",
    873                  intervalSeconds: "1",
    874                  maxCount: "1",
    875                  startDate: "17000",
    876                  impressionDate: "16000",
    877                  count: "0",
    878                  type: "sponsored",
    879                  eventCount: "1",
    880                },
    881              },
    882            ],
    883          },
    884        },
    885        // 19s: no new impressions; 10 impressions total
    886        19: {
    887          results: [[]],
    888          telemetry: {
    889            events: [
    890              // reset: interval_s: 1, max_count: 1
    891              {
    892                object: "reset",
    893                extra: {
    894                  eventDate: "19000",
    895                  intervalSeconds: "1",
    896                  maxCount: "1",
    897                  startDate: "18000",
    898                  impressionDate: "16000",
    899                  count: "0",
    900                  type: "sponsored",
    901                  eventCount: "1",
    902                },
    903              },
    904            ],
    905          },
    906        },
    907        // 20s: 1 new impression; 11 impressions total
    908        20: {
    909          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
    910          telemetry: {
    911            events: [
    912              // reset: interval_s: 1, max_count: 1
    913              {
    914                object: "reset",
    915                extra: {
    916                  eventDate: "20000",
    917                  intervalSeconds: "1",
    918                  maxCount: "1",
    919                  startDate: "19000",
    920                  impressionDate: "16000",
    921                  count: "0",
    922                  type: "sponsored",
    923                  eventCount: "1",
    924                },
    925              },
    926              // reset: interval_s: 5, max_count: 3
    927              {
    928                object: "reset",
    929                extra: {
    930                  eventDate: "20000",
    931                  intervalSeconds: "5",
    932                  maxCount: "3",
    933                  startDate: "15000",
    934                  impressionDate: "16000",
    935                  count: "2",
    936                  type: "sponsored",
    937                  eventCount: "1",
    938                },
    939              },
    940              // reset: interval_s: 10, max_count: 5
    941              {
    942                object: "reset",
    943                extra: {
    944                  eventDate: "20000",
    945                  intervalSeconds: "10",
    946                  maxCount: "5",
    947                  startDate: "10000",
    948                  impressionDate: "16000",
    949                  count: "5",
    950                  type: "sponsored",
    951                  eventCount: "1",
    952                },
    953              },
    954              // hit: interval_s: 1, max_count: 1
    955              {
    956                object: "hit",
    957                extra: {
    958                  eventDate: "20000",
    959                  intervalSeconds: "1",
    960                  maxCount: "1",
    961                  startDate: "20000",
    962                  impressionDate: "20000",
    963                  count: "1",
    964                  type: "sponsored",
    965                  eventCount: "1",
    966                },
    967              },
    968            ],
    969          },
    970        },
    971      });
    972    },
    973  });
    974 });
    975 
    976 // Tests a lifetime cap.
    977 add_task(async function lifetime() {
    978  await doTest({
    979    config: {
    980      impression_caps: {
    981        sponsored: {
    982          lifetime: 3,
    983        },
    984      },
    985    },
    986    callback: async () => {
    987      await doTimedSearches("sponsored", {
    988        0: {
    989          results: [
    990            [EXPECTED_SPONSORED_URLBAR_RESULT],
    991            [EXPECTED_SPONSORED_URLBAR_RESULT],
    992            [EXPECTED_SPONSORED_URLBAR_RESULT],
    993            [],
    994          ],
    995          telemetry: {
    996            events: [
    997              {
    998                object: "hit",
    999                extra: {
   1000                  eventDate: "0",
   1001                  intervalSeconds: "Infinity",
   1002                  maxCount: "3",
   1003                  startDate: "0",
   1004                  impressionDate: "0",
   1005                  count: "3",
   1006                  type: "sponsored",
   1007                  eventCount: "1",
   1008                },
   1009              },
   1010            ],
   1011          },
   1012        },
   1013        1: {
   1014          results: [[]],
   1015        },
   1016      });
   1017    },
   1018  });
   1019 });
   1020 
   1021 // Tests one interval and a lifetime cap together.
   1022 add_task(async function intervalAndLifetime() {
   1023  await doTest({
   1024    config: {
   1025      impression_caps: {
   1026        sponsored: {
   1027          lifetime: 3,
   1028          custom: [{ interval_s: 1, max_count: 1 }],
   1029        },
   1030      },
   1031    },
   1032    callback: async () => {
   1033      await doTimedSearches("sponsored", {
   1034        // 0s: 1 new impression; 1 impression total
   1035        0: {
   1036          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1037          telemetry: {
   1038            events: [
   1039              // hit: interval_s: 1, max_count: 1
   1040              {
   1041                object: "hit",
   1042                extra: {
   1043                  eventDate: "0",
   1044                  intervalSeconds: "1",
   1045                  maxCount: "1",
   1046                  startDate: "0",
   1047                  impressionDate: "0",
   1048                  count: "1",
   1049                  type: "sponsored",
   1050                  eventCount: "1",
   1051                },
   1052              },
   1053            ],
   1054          },
   1055        },
   1056        // 1s: 1 new impression; 2 impressions total
   1057        1: {
   1058          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1059          telemetry: {
   1060            events: [
   1061              // reset: interval_s: 1, max_count: 1
   1062              {
   1063                object: "reset",
   1064                extra: {
   1065                  eventDate: "1000",
   1066                  intervalSeconds: "1",
   1067                  maxCount: "1",
   1068                  startDate: "0",
   1069                  impressionDate: "0",
   1070                  count: "1",
   1071                  type: "sponsored",
   1072                  eventCount: "1",
   1073                },
   1074              },
   1075              // hit: interval_s: 1, max_count: 1
   1076              {
   1077                object: "hit",
   1078                extra: {
   1079                  eventDate: "1000",
   1080                  intervalSeconds: "1",
   1081                  maxCount: "1",
   1082                  startDate: "1000",
   1083                  impressionDate: "1000",
   1084                  count: "1",
   1085                  type: "sponsored",
   1086                  eventCount: "1",
   1087                },
   1088              },
   1089            ],
   1090          },
   1091        },
   1092        // 2s: 1 new impression; 3 impressions total
   1093        2: {
   1094          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1095          telemetry: {
   1096            events: [
   1097              // reset: interval_s: 1, max_count: 1
   1098              {
   1099                object: "reset",
   1100                extra: {
   1101                  eventDate: "2000",
   1102                  intervalSeconds: "1",
   1103                  maxCount: "1",
   1104                  startDate: "1000",
   1105                  impressionDate: "1000",
   1106                  count: "1",
   1107                  type: "sponsored",
   1108                  eventCount: "1",
   1109                },
   1110              },
   1111              // hit: interval_s: 1, max_count: 1
   1112              {
   1113                object: "hit",
   1114                extra: {
   1115                  eventDate: "2000",
   1116                  intervalSeconds: "1",
   1117                  maxCount: "1",
   1118                  startDate: "2000",
   1119                  impressionDate: "2000",
   1120                  count: "1",
   1121                  type: "sponsored",
   1122                  eventCount: "1",
   1123                },
   1124              },
   1125              // hit: interval_s: Infinity, max_count: 3
   1126              {
   1127                object: "hit",
   1128                extra: {
   1129                  eventDate: "2000",
   1130                  intervalSeconds: "Infinity",
   1131                  maxCount: "3",
   1132                  startDate: "0",
   1133                  impressionDate: "2000",
   1134                  count: "3",
   1135                  type: "sponsored",
   1136                  eventCount: "1",
   1137                },
   1138              },
   1139            ],
   1140          },
   1141        },
   1142        3: {
   1143          results: [[]],
   1144          telemetry: {
   1145            events: [
   1146              // reset: interval_s: 1, max_count: 1
   1147              {
   1148                object: "reset",
   1149                extra: {
   1150                  eventDate: "3000",
   1151                  intervalSeconds: "1",
   1152                  maxCount: "1",
   1153                  startDate: "2000",
   1154                  impressionDate: "2000",
   1155                  count: "1",
   1156                  type: "sponsored",
   1157                  eventCount: "1",
   1158                },
   1159              },
   1160            ],
   1161          },
   1162        },
   1163      });
   1164    },
   1165  });
   1166 });
   1167 
   1168 // Tests multiple intervals and a lifetime cap together.
   1169 add_task(async function multipleIntervalsAndLifetime() {
   1170  await doTest({
   1171    config: {
   1172      impression_caps: {
   1173        sponsored: {
   1174          lifetime: 4,
   1175          custom: [
   1176            { interval_s: 1, max_count: 1 },
   1177            { interval_s: 5, max_count: 3 },
   1178          ],
   1179        },
   1180      },
   1181    },
   1182    callback: async () => {
   1183      await doTimedSearches("sponsored", {
   1184        // 0s: 1 new impression; 1 impression total
   1185        0: {
   1186          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1187          telemetry: {
   1188            events: [
   1189              // hit: interval_s: 1, max_count: 1
   1190              {
   1191                object: "hit",
   1192                extra: {
   1193                  eventDate: "0",
   1194                  intervalSeconds: "1",
   1195                  maxCount: "1",
   1196                  startDate: "0",
   1197                  impressionDate: "0",
   1198                  count: "1",
   1199                  type: "sponsored",
   1200                  eventCount: "1",
   1201                },
   1202              },
   1203            ],
   1204          },
   1205        },
   1206        // 1s: 1 new impression; 2 impressions total
   1207        1: {
   1208          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1209          telemetry: {
   1210            events: [
   1211              // reset: interval_s: 1, max_count: 1
   1212              {
   1213                object: "reset",
   1214                extra: {
   1215                  eventDate: "1000",
   1216                  intervalSeconds: "1",
   1217                  maxCount: "1",
   1218                  startDate: "0",
   1219                  impressionDate: "0",
   1220                  count: "1",
   1221                  type: "sponsored",
   1222                  eventCount: "1",
   1223                },
   1224              },
   1225              // hit: interval_s: 1, max_count: 1
   1226              {
   1227                object: "hit",
   1228                extra: {
   1229                  eventDate: "1000",
   1230                  intervalSeconds: "1",
   1231                  maxCount: "1",
   1232                  startDate: "1000",
   1233                  impressionDate: "1000",
   1234                  count: "1",
   1235                  type: "sponsored",
   1236                  eventCount: "1",
   1237                },
   1238              },
   1239            ],
   1240          },
   1241        },
   1242        // 2s: 1 new impression; 3 impressions total
   1243        2: {
   1244          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1245          telemetry: {
   1246            events: [
   1247              // reset: interval_s: 1, max_count: 1
   1248              {
   1249                object: "reset",
   1250                extra: {
   1251                  eventDate: "2000",
   1252                  intervalSeconds: "1",
   1253                  maxCount: "1",
   1254                  startDate: "1000",
   1255                  impressionDate: "1000",
   1256                  count: "1",
   1257                  type: "sponsored",
   1258                  eventCount: "1",
   1259                },
   1260              },
   1261              // hit: interval_s: 1, max_count: 1
   1262              {
   1263                object: "hit",
   1264                extra: {
   1265                  eventDate: "2000",
   1266                  intervalSeconds: "1",
   1267                  maxCount: "1",
   1268                  startDate: "2000",
   1269                  impressionDate: "2000",
   1270                  count: "1",
   1271                  type: "sponsored",
   1272                  eventCount: "1",
   1273                },
   1274              },
   1275              // hit: interval_s: 5, max_count: 3
   1276              {
   1277                object: "hit",
   1278                extra: {
   1279                  eventDate: "2000",
   1280                  intervalSeconds: "5",
   1281                  maxCount: "3",
   1282                  startDate: "0",
   1283                  impressionDate: "2000",
   1284                  count: "3",
   1285                  type: "sponsored",
   1286                  eventCount: "1",
   1287                },
   1288              },
   1289            ],
   1290          },
   1291        },
   1292        // 3s: no new impressions; 3 impressions total
   1293        3: {
   1294          results: [[]],
   1295          telemetry: {
   1296            events: [
   1297              // reset: interval_s: 1, max_count: 1
   1298              {
   1299                object: "reset",
   1300                extra: {
   1301                  eventDate: "3000",
   1302                  intervalSeconds: "1",
   1303                  maxCount: "1",
   1304                  startDate: "2000",
   1305                  impressionDate: "2000",
   1306                  count: "1",
   1307                  type: "sponsored",
   1308                  eventCount: "1",
   1309                },
   1310              },
   1311            ],
   1312          },
   1313        },
   1314        // 4s: no new impressions; 3 impressions total
   1315        4: {
   1316          results: [[]],
   1317          telemetry: {
   1318            events: [
   1319              // reset: interval_s: 1, max_count: 1
   1320              {
   1321                object: "reset",
   1322                extra: {
   1323                  eventDate: "4000",
   1324                  intervalSeconds: "1",
   1325                  maxCount: "1",
   1326                  startDate: "3000",
   1327                  impressionDate: "2000",
   1328                  count: "0",
   1329                  type: "sponsored",
   1330                  eventCount: "1",
   1331                },
   1332              },
   1333            ],
   1334          },
   1335        },
   1336        // 5s: 1 new impression; 4 impressions total
   1337        5: {
   1338          results: [[EXPECTED_SPONSORED_URLBAR_RESULT], []],
   1339          telemetry: {
   1340            events: [
   1341              // reset: interval_s: 1, max_count: 1
   1342              {
   1343                object: "reset",
   1344                extra: {
   1345                  eventDate: "5000",
   1346                  intervalSeconds: "1",
   1347                  maxCount: "1",
   1348                  startDate: "4000",
   1349                  impressionDate: "2000",
   1350                  count: "0",
   1351                  type: "sponsored",
   1352                  eventCount: "1",
   1353                },
   1354              },
   1355              // reset: interval_s: 5, max_count: 3
   1356              {
   1357                object: "reset",
   1358                extra: {
   1359                  eventDate: "5000",
   1360                  intervalSeconds: "5",
   1361                  maxCount: "3",
   1362                  startDate: "0",
   1363                  impressionDate: "2000",
   1364                  count: "3",
   1365                  type: "sponsored",
   1366                  eventCount: "1",
   1367                },
   1368              },
   1369              // hit: interval_s: 1, max_count: 1
   1370              {
   1371                object: "hit",
   1372                extra: {
   1373                  eventDate: "5000",
   1374                  intervalSeconds: "1",
   1375                  maxCount: "1",
   1376                  startDate: "5000",
   1377                  impressionDate: "5000",
   1378                  count: "1",
   1379                  type: "sponsored",
   1380                  eventCount: "1",
   1381                },
   1382              },
   1383              // hit: interval_s: Infinity, max_count: 4
   1384              {
   1385                object: "hit",
   1386                extra: {
   1387                  eventDate: "5000",
   1388                  intervalSeconds: "Infinity",
   1389                  maxCount: "4",
   1390                  startDate: "0",
   1391                  impressionDate: "5000",
   1392                  count: "4",
   1393                  type: "sponsored",
   1394                  eventCount: "1",
   1395                },
   1396              },
   1397            ],
   1398          },
   1399        },
   1400        // 6s: no new impressions; 4 impressions total
   1401        6: {
   1402          results: [[]],
   1403          telemetry: {
   1404            events: [
   1405              // reset: interval_s: 1, max_count: 1
   1406              {
   1407                object: "reset",
   1408                extra: {
   1409                  eventDate: "6000",
   1410                  intervalSeconds: "1",
   1411                  maxCount: "1",
   1412                  startDate: "5000",
   1413                  impressionDate: "5000",
   1414                  count: "1",
   1415                  type: "sponsored",
   1416                  eventCount: "1",
   1417                },
   1418              },
   1419            ],
   1420          },
   1421        },
   1422        // 7s: no new impressions; 4 impressions total
   1423        7: {
   1424          results: [[]],
   1425          telemetry: {
   1426            events: [
   1427              // reset: interval_s: 1, max_count: 1
   1428              {
   1429                object: "reset",
   1430                extra: {
   1431                  eventDate: "7000",
   1432                  intervalSeconds: "1",
   1433                  maxCount: "1",
   1434                  startDate: "6000",
   1435                  impressionDate: "5000",
   1436                  count: "0",
   1437                  type: "sponsored",
   1438                  eventCount: "1",
   1439                },
   1440              },
   1441            ],
   1442          },
   1443        },
   1444      });
   1445    },
   1446  });
   1447 });
   1448 
   1449 // Smoke test for non-sponsored caps. Most tasks use sponsored results and caps,
   1450 // but sponsored and non-sponsored should behave the same since they use the
   1451 // same code paths.
   1452 add_task(async function nonsponsored() {
   1453  await doTest({
   1454    config: {
   1455      impression_caps: {
   1456        nonsponsored: {
   1457          lifetime: 4,
   1458          custom: [
   1459            { interval_s: 1, max_count: 1 },
   1460            { interval_s: 5, max_count: 3 },
   1461          ],
   1462        },
   1463      },
   1464    },
   1465    callback: async () => {
   1466      await doTimedSearches("nonsponsored", {
   1467        // 0s: 1 new impression; 1 impression total
   1468        0: {
   1469          results: [[EXPECTED_NONSPONSORED_URLBAR_RESULT], []],
   1470          telemetry: {
   1471            events: [
   1472              // hit: interval_s: 1, max_count: 1
   1473              {
   1474                object: "hit",
   1475                extra: {
   1476                  eventDate: "0",
   1477                  intervalSeconds: "1",
   1478                  maxCount: "1",
   1479                  startDate: "0",
   1480                  impressionDate: "0",
   1481                  count: "1",
   1482                  type: "nonsponsored",
   1483                  eventCount: "1",
   1484                },
   1485              },
   1486            ],
   1487          },
   1488        },
   1489        // 1s: 1 new impression; 2 impressions total
   1490        1: {
   1491          results: [[EXPECTED_NONSPONSORED_URLBAR_RESULT], []],
   1492          telemetry: {
   1493            events: [
   1494              // reset: interval_s: 1, max_count: 1
   1495              {
   1496                object: "reset",
   1497                extra: {
   1498                  eventDate: "1000",
   1499                  intervalSeconds: "1",
   1500                  maxCount: "1",
   1501                  startDate: "0",
   1502                  impressionDate: "0",
   1503                  count: "1",
   1504                  type: "nonsponsored",
   1505                  eventCount: "1",
   1506                },
   1507              },
   1508              // hit: interval_s: 1, max_count: 1
   1509              {
   1510                object: "hit",
   1511                extra: {
   1512                  eventDate: "1000",
   1513                  intervalSeconds: "1",
   1514                  maxCount: "1",
   1515                  startDate: "1000",
   1516                  impressionDate: "1000",
   1517                  count: "1",
   1518                  type: "nonsponsored",
   1519                  eventCount: "1",
   1520                },
   1521              },
   1522            ],
   1523          },
   1524        },
   1525        // 2s: 1 new impression; 3 impressions total
   1526        2: {
   1527          results: [[EXPECTED_NONSPONSORED_URLBAR_RESULT], []],
   1528          telemetry: {
   1529            events: [
   1530              // reset: interval_s: 1, max_count: 1
   1531              {
   1532                object: "reset",
   1533                extra: {
   1534                  eventDate: "2000",
   1535                  intervalSeconds: "1",
   1536                  maxCount: "1",
   1537                  startDate: "1000",
   1538                  impressionDate: "1000",
   1539                  count: "1",
   1540                  type: "nonsponsored",
   1541                  eventCount: "1",
   1542                },
   1543              },
   1544              // hit: interval_s: 1, max_count: 1
   1545              {
   1546                object: "hit",
   1547                extra: {
   1548                  eventDate: "2000",
   1549                  intervalSeconds: "1",
   1550                  maxCount: "1",
   1551                  startDate: "2000",
   1552                  impressionDate: "2000",
   1553                  count: "1",
   1554                  type: "nonsponsored",
   1555                  eventCount: "1",
   1556                },
   1557              },
   1558              // hit: interval_s: 5, max_count: 3
   1559              {
   1560                object: "hit",
   1561                extra: {
   1562                  eventDate: "2000",
   1563                  intervalSeconds: "5",
   1564                  maxCount: "3",
   1565                  startDate: "0",
   1566                  impressionDate: "2000",
   1567                  count: "3",
   1568                  type: "nonsponsored",
   1569                  eventCount: "1",
   1570                },
   1571              },
   1572            ],
   1573          },
   1574        },
   1575        // 3s: no new impressions; 3 impressions total
   1576        3: {
   1577          results: [[]],
   1578          telemetry: {
   1579            events: [
   1580              // reset: interval_s: 1, max_count: 1
   1581              {
   1582                object: "reset",
   1583                extra: {
   1584                  eventDate: "3000",
   1585                  intervalSeconds: "1",
   1586                  maxCount: "1",
   1587                  startDate: "2000",
   1588                  impressionDate: "2000",
   1589                  count: "1",
   1590                  type: "nonsponsored",
   1591                  eventCount: "1",
   1592                },
   1593              },
   1594            ],
   1595          },
   1596        },
   1597        // 4s: no new impressions; 3 impressions total
   1598        4: {
   1599          results: [[]],
   1600          telemetry: {
   1601            events: [
   1602              // reset: interval_s: 1, max_count: 1
   1603              {
   1604                object: "reset",
   1605                extra: {
   1606                  eventDate: "4000",
   1607                  intervalSeconds: "1",
   1608                  maxCount: "1",
   1609                  startDate: "3000",
   1610                  impressionDate: "2000",
   1611                  count: "0",
   1612                  type: "nonsponsored",
   1613                  eventCount: "1",
   1614                },
   1615              },
   1616            ],
   1617          },
   1618        },
   1619        // 5s: 1 new impression; 4 impressions total
   1620        5: {
   1621          results: [[EXPECTED_NONSPONSORED_URLBAR_RESULT], []],
   1622          telemetry: {
   1623            events: [
   1624              // reset: interval_s: 1, max_count: 1
   1625              {
   1626                object: "reset",
   1627                extra: {
   1628                  eventDate: "5000",
   1629                  intervalSeconds: "1",
   1630                  maxCount: "1",
   1631                  startDate: "4000",
   1632                  impressionDate: "2000",
   1633                  count: "0",
   1634                  type: "nonsponsored",
   1635                  eventCount: "1",
   1636                },
   1637              },
   1638              // reset: interval_s: 5, max_count: 3
   1639              {
   1640                object: "reset",
   1641                extra: {
   1642                  eventDate: "5000",
   1643                  intervalSeconds: "5",
   1644                  maxCount: "3",
   1645                  startDate: "0",
   1646                  impressionDate: "2000",
   1647                  count: "3",
   1648                  type: "nonsponsored",
   1649                  eventCount: "1",
   1650                },
   1651              },
   1652              // hit: interval_s: 1, max_count: 1
   1653              {
   1654                object: "hit",
   1655                extra: {
   1656                  eventDate: "5000",
   1657                  intervalSeconds: "1",
   1658                  maxCount: "1",
   1659                  startDate: "5000",
   1660                  impressionDate: "5000",
   1661                  count: "1",
   1662                  type: "nonsponsored",
   1663                  eventCount: "1",
   1664                },
   1665              },
   1666              // hit: interval_s: Infinity, max_count: 4
   1667              {
   1668                object: "hit",
   1669                extra: {
   1670                  eventDate: "5000",
   1671                  intervalSeconds: "Infinity",
   1672                  maxCount: "4",
   1673                  startDate: "0",
   1674                  impressionDate: "5000",
   1675                  count: "4",
   1676                  type: "nonsponsored",
   1677                  eventCount: "1",
   1678                },
   1679              },
   1680            ],
   1681          },
   1682        },
   1683        // 6s: no new impressions; 4 impressions total
   1684        6: {
   1685          results: [[]],
   1686          telemetry: {
   1687            events: [
   1688              // reset: interval_s: 1, max_count: 1
   1689              {
   1690                object: "reset",
   1691                extra: {
   1692                  eventDate: "6000",
   1693                  intervalSeconds: "1",
   1694                  maxCount: "1",
   1695                  startDate: "5000",
   1696                  impressionDate: "5000",
   1697                  count: "1",
   1698                  type: "nonsponsored",
   1699                  eventCount: "1",
   1700                },
   1701              },
   1702            ],
   1703          },
   1704        },
   1705        // 7s: no new impressions; 4 impressions total
   1706        7: {
   1707          results: [[]],
   1708          telemetry: {
   1709            events: [
   1710              // reset: interval_s: 1, max_count: 1
   1711              {
   1712                object: "reset",
   1713                extra: {
   1714                  eventDate: "7000",
   1715                  intervalSeconds: "1",
   1716                  maxCount: "1",
   1717                  startDate: "6000",
   1718                  impressionDate: "5000",
   1719                  count: "0",
   1720                  type: "nonsponsored",
   1721                  eventCount: "1",
   1722                },
   1723              },
   1724            ],
   1725          },
   1726        },
   1727      });
   1728    },
   1729  });
   1730 });
   1731 
   1732 // Smoke test for sponsored and non-sponsored caps together. Most tasks use only
   1733 // sponsored results and caps, but sponsored and non-sponsored should behave the
   1734 // same since they use the same code paths.
   1735 add_task(async function sponsoredAndNonsponsored() {
   1736  await doTest({
   1737    config: {
   1738      impression_caps: {
   1739        sponsored: {
   1740          lifetime: 2,
   1741        },
   1742        nonsponsored: {
   1743          lifetime: 3,
   1744        },
   1745      },
   1746    },
   1747    callback: async () => {
   1748      // 1st searches
   1749      await checkSearch({
   1750        name: "sponsored 1",
   1751        searchString: "sponsored",
   1752        expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1753      });
   1754      await checkSearch({
   1755        name: "nonsponsored 1",
   1756        searchString: "nonsponsored",
   1757        expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1758      });
   1759 
   1760      // 2nd searches
   1761      await checkSearch({
   1762        name: "sponsored 2",
   1763        searchString: "sponsored",
   1764        expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1765      });
   1766      await checkSearch({
   1767        name: "nonsponsored 2",
   1768        searchString: "nonsponsored",
   1769        expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1770      });
   1771 
   1772      // 3rd searches
   1773      await checkSearch({
   1774        name: "sponsored 3",
   1775        searchString: "sponsored",
   1776        expectedResults: [],
   1777      });
   1778      await checkSearch({
   1779        name: "nonsponsored 3",
   1780        searchString: "nonsponsored",
   1781        expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1782      });
   1783 
   1784      // 4th searches
   1785      await checkSearch({
   1786        name: "sponsored 4",
   1787        searchString: "sponsored",
   1788        expectedResults: [],
   1789      });
   1790      await checkSearch({
   1791        name: "nonsponsored 4",
   1792        searchString: "nonsponsored",
   1793        expectedResults: [],
   1794      });
   1795    },
   1796  });
   1797 });
   1798 
   1799 // Tests with an empty config to make sure results are not capped.
   1800 add_task(async function emptyConfig() {
   1801  await doTest({
   1802    config: {},
   1803    callback: async () => {
   1804      for (let i = 0; i < 2; i++) {
   1805        await checkSearch({
   1806          name: "sponsored " + i,
   1807          searchString: "sponsored",
   1808          expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1809        });
   1810        await checkSearch({
   1811          name: "nonsponsored " + i,
   1812          searchString: "nonsponsored",
   1813          expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1814        });
   1815      }
   1816    },
   1817  });
   1818 });
   1819 
   1820 // Tests with sponsored caps disabled. Non-sponsored should still be capped.
   1821 add_task(async function sponsoredCapsDisabled() {
   1822  UrlbarPrefs.set("quicksuggest.impressionCaps.sponsoredEnabled", false);
   1823  await doTest({
   1824    config: {
   1825      impression_caps: {
   1826        sponsored: {
   1827          lifetime: 0,
   1828        },
   1829        nonsponsored: {
   1830          lifetime: 3,
   1831        },
   1832      },
   1833    },
   1834    callback: async () => {
   1835      for (let i = 0; i < 3; i++) {
   1836        await checkSearch({
   1837          name: "sponsored " + i,
   1838          searchString: "sponsored",
   1839          expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1840        });
   1841        await checkSearch({
   1842          name: "nonsponsored " + i,
   1843          searchString: "nonsponsored",
   1844          expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1845        });
   1846      }
   1847 
   1848      await checkSearch({
   1849        name: "sponsored additional",
   1850        searchString: "sponsored",
   1851        expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1852      });
   1853      await checkSearch({
   1854        name: "nonsponsored additional",
   1855        searchString: "nonsponsored",
   1856        expectedResults: [],
   1857      });
   1858    },
   1859  });
   1860  UrlbarPrefs.set("quicksuggest.impressionCaps.sponsoredEnabled", true);
   1861 });
   1862 
   1863 // Tests with non-sponsored caps disabled. Sponsored should still be capped.
   1864 add_task(async function nonsponsoredCapsDisabled() {
   1865  UrlbarPrefs.set("quicksuggest.impressionCaps.nonSponsoredEnabled", false);
   1866  await doTest({
   1867    config: {
   1868      impression_caps: {
   1869        sponsored: {
   1870          lifetime: 3,
   1871        },
   1872        nonsponsored: {
   1873          lifetime: 0,
   1874        },
   1875      },
   1876    },
   1877    callback: async () => {
   1878      for (let i = 0; i < 3; i++) {
   1879        await checkSearch({
   1880          name: "sponsored " + i,
   1881          searchString: "sponsored",
   1882          expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1883        });
   1884        await checkSearch({
   1885          name: "nonsponsored " + i,
   1886          searchString: "nonsponsored",
   1887          expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1888        });
   1889      }
   1890 
   1891      await checkSearch({
   1892        name: "sponsored additional",
   1893        searchString: "sponsored",
   1894        expectedResults: [],
   1895      });
   1896      await checkSearch({
   1897        name: "nonsponsored additional",
   1898        searchString: "nonsponsored",
   1899        expectedResults: [EXPECTED_NONSPONSORED_URLBAR_RESULT],
   1900      });
   1901    },
   1902  });
   1903  UrlbarPrefs.set("quicksuggest.impressionCaps.nonSponsoredEnabled", true);
   1904 });
   1905 
   1906 // Tests a config change: 1 interval -> same interval with lower cap, with the
   1907 // old cap already reached
   1908 add_task(async function configChange_sameIntervalLowerCap_1() {
   1909  await doTest({
   1910    config: {
   1911      impression_caps: {
   1912        sponsored: {
   1913          custom: [{ interval_s: 3, max_count: 3 }],
   1914        },
   1915      },
   1916    },
   1917    callback: async () => {
   1918      await doTimedCallbacks({
   1919        0: async () => {
   1920          for (let i = 0; i < 3; i++) {
   1921            await checkSearch({
   1922              name: "0s " + i,
   1923              searchString: "sponsored",
   1924              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1925            });
   1926          }
   1927          await checkSearch({
   1928            name: "0s additional",
   1929            searchString: "sponsored",
   1930            expectedResults: [],
   1931          });
   1932          await QuickSuggestTestUtils.setConfig({
   1933            impression_caps: {
   1934              sponsored: {
   1935                custom: [{ interval_s: 3, max_count: 1 }],
   1936              },
   1937            },
   1938          });
   1939        },
   1940        1: async () => {
   1941          await checkSearch({
   1942            name: "1s",
   1943            searchString: "sponsored",
   1944            expectedResults: [],
   1945          });
   1946        },
   1947        3: async () => {
   1948          await checkSearch({
   1949            name: "3s 0",
   1950            searchString: "sponsored",
   1951            expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1952          });
   1953          await checkSearch({
   1954            name: "3s additional",
   1955            searchString: "sponsored",
   1956            expectedResults: [],
   1957          });
   1958        },
   1959      });
   1960    },
   1961  });
   1962 });
   1963 
   1964 // Tests a config change: 1 interval -> same interval with lower cap, with the
   1965 // old cap not reached
   1966 add_task(async function configChange_sameIntervalLowerCap_2() {
   1967  await doTest({
   1968    config: {
   1969      impression_caps: {
   1970        sponsored: {
   1971          custom: [{ interval_s: 3, max_count: 3 }],
   1972        },
   1973      },
   1974    },
   1975    callback: async () => {
   1976      await doTimedCallbacks({
   1977        0: async () => {
   1978          for (let i = 0; i < 2; i++) {
   1979            await checkSearch({
   1980              name: "0s " + i,
   1981              searchString: "sponsored",
   1982              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   1983            });
   1984          }
   1985          await QuickSuggestTestUtils.setConfig({
   1986            impression_caps: {
   1987              sponsored: {
   1988                custom: [{ interval_s: 3, max_count: 1 }],
   1989              },
   1990            },
   1991          });
   1992        },
   1993        1: async () => {
   1994          await checkSearch({
   1995            name: "1s",
   1996            searchString: "sponsored",
   1997            expectedResults: [],
   1998          });
   1999        },
   2000        3: async () => {
   2001          await checkSearch({
   2002            name: "3s 0",
   2003            searchString: "sponsored",
   2004            expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2005          });
   2006          await checkSearch({
   2007            name: "3s additional",
   2008            searchString: "sponsored",
   2009            expectedResults: [],
   2010          });
   2011        },
   2012      });
   2013    },
   2014  });
   2015 });
   2016 
   2017 // Tests a config change: 1 interval -> same interval with higher cap
   2018 add_task(async function configChange_sameIntervalHigherCap() {
   2019  await doTest({
   2020    config: {
   2021      impression_caps: {
   2022        sponsored: {
   2023          custom: [{ interval_s: 3, max_count: 3 }],
   2024        },
   2025      },
   2026    },
   2027    callback: async () => {
   2028      await doTimedCallbacks({
   2029        0: async () => {
   2030          for (let i = 0; i < 3; i++) {
   2031            await checkSearch({
   2032              name: "0s " + i,
   2033              searchString: "sponsored",
   2034              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2035            });
   2036          }
   2037          await checkSearch({
   2038            name: "0s additional",
   2039            searchString: "sponsored",
   2040            expectedResults: [],
   2041          });
   2042          await QuickSuggestTestUtils.setConfig({
   2043            impression_caps: {
   2044              sponsored: {
   2045                custom: [{ interval_s: 3, max_count: 5 }],
   2046              },
   2047            },
   2048          });
   2049        },
   2050        1: async () => {
   2051          for (let i = 0; i < 2; i++) {
   2052            await checkSearch({
   2053              name: "1s " + i,
   2054              searchString: "sponsored",
   2055              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2056            });
   2057          }
   2058          await checkSearch({
   2059            name: "1s additional",
   2060            searchString: "sponsored",
   2061            expectedResults: [],
   2062          });
   2063        },
   2064        3: async () => {
   2065          for (let i = 0; i < 5; i++) {
   2066            await checkSearch({
   2067              name: "3s " + i,
   2068              searchString: "sponsored",
   2069              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2070            });
   2071          }
   2072          await checkSearch({
   2073            name: "3s additional",
   2074            searchString: "sponsored",
   2075            expectedResults: [],
   2076          });
   2077        },
   2078      });
   2079    },
   2080  });
   2081 });
   2082 
   2083 // Tests a config change: 1 interval -> 2 new intervals with higher timeouts.
   2084 // Impression counts for the old interval should contribute to the new
   2085 // intervals.
   2086 add_task(async function configChange_1IntervalTo2NewIntervalsHigher() {
   2087  await doTest({
   2088    config: {
   2089      impression_caps: {
   2090        sponsored: {
   2091          custom: [{ interval_s: 3, max_count: 3 }],
   2092        },
   2093      },
   2094    },
   2095    callback: async () => {
   2096      await doTimedCallbacks({
   2097        0: async () => {
   2098          for (let i = 0; i < 3; i++) {
   2099            await checkSearch({
   2100              name: "0s " + i,
   2101              searchString: "sponsored",
   2102              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2103            });
   2104          }
   2105          await QuickSuggestTestUtils.setConfig({
   2106            impression_caps: {
   2107              sponsored: {
   2108                custom: [
   2109                  { interval_s: 5, max_count: 3 },
   2110                  { interval_s: 10, max_count: 5 },
   2111                ],
   2112              },
   2113            },
   2114          });
   2115        },
   2116        3: async () => {
   2117          await checkSearch({
   2118            name: "3s",
   2119            searchString: "sponsored",
   2120            expectedResults: [],
   2121          });
   2122        },
   2123        4: async () => {
   2124          await checkSearch({
   2125            name: "4s",
   2126            searchString: "sponsored",
   2127            expectedResults: [],
   2128          });
   2129        },
   2130        5: async () => {
   2131          for (let i = 0; i < 2; i++) {
   2132            await checkSearch({
   2133              name: "5s " + i,
   2134              searchString: "sponsored",
   2135              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2136            });
   2137          }
   2138          await checkSearch({
   2139            name: "5s additional",
   2140            searchString: "sponsored",
   2141            expectedResults: [],
   2142          });
   2143        },
   2144      });
   2145    },
   2146  });
   2147 });
   2148 
   2149 // Tests a config change: 2 intervals -> 1 new interval with higher timeout.
   2150 // Impression counts for the old intervals should contribute to the new
   2151 // interval.
   2152 add_task(async function configChange_2IntervalsTo1NewIntervalHigher() {
   2153  await doTest({
   2154    config: {
   2155      impression_caps: {
   2156        sponsored: {
   2157          custom: [
   2158            { interval_s: 2, max_count: 2 },
   2159            { interval_s: 4, max_count: 4 },
   2160          ],
   2161        },
   2162      },
   2163    },
   2164    callback: async () => {
   2165      await doTimedCallbacks({
   2166        0: async () => {
   2167          for (let i = 0; i < 2; i++) {
   2168            await checkSearch({
   2169              name: "0s " + i,
   2170              searchString: "sponsored",
   2171              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2172            });
   2173          }
   2174        },
   2175        2: async () => {
   2176          for (let i = 0; i < 2; i++) {
   2177            await checkSearch({
   2178              name: "2s " + i,
   2179              searchString: "sponsored",
   2180              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2181            });
   2182          }
   2183          await QuickSuggestTestUtils.setConfig({
   2184            impression_caps: {
   2185              sponsored: {
   2186                custom: [{ interval_s: 6, max_count: 5 }],
   2187              },
   2188            },
   2189          });
   2190        },
   2191        4: async () => {
   2192          await checkSearch({
   2193            name: "4s 0",
   2194            searchString: "sponsored",
   2195            expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2196          });
   2197          await checkSearch({
   2198            name: "4s 1",
   2199            searchString: "sponsored",
   2200            expectedResults: [],
   2201          });
   2202        },
   2203        5: async () => {
   2204          await checkSearch({
   2205            name: "5s",
   2206            searchString: "sponsored",
   2207            expectedResults: [],
   2208          });
   2209        },
   2210        6: async () => {
   2211          for (let i = 0; i < 5; i++) {
   2212            await checkSearch({
   2213              name: "6s " + i,
   2214              searchString: "sponsored",
   2215              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2216            });
   2217          }
   2218          await checkSearch({
   2219            name: "6s additional",
   2220            searchString: "sponsored",
   2221            expectedResults: [],
   2222          });
   2223        },
   2224      });
   2225    },
   2226  });
   2227 });
   2228 
   2229 // Tests a config change: 1 interval -> 1 new interval with lower timeout.
   2230 // Impression counts for the old interval should not contribute to the new
   2231 // interval since the new interval has a lower timeout.
   2232 add_task(async function configChange_1IntervalTo1NewIntervalLower() {
   2233  await doTest({
   2234    config: {
   2235      impression_caps: {
   2236        sponsored: {
   2237          custom: [{ interval_s: 5, max_count: 3 }],
   2238        },
   2239      },
   2240    },
   2241    callback: async () => {
   2242      await doTimedCallbacks({
   2243        0: async () => {
   2244          for (let i = 0; i < 3; i++) {
   2245            await checkSearch({
   2246              name: "0s " + i,
   2247              searchString: "sponsored",
   2248              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2249            });
   2250          }
   2251          await QuickSuggestTestUtils.setConfig({
   2252            impression_caps: {
   2253              sponsored: {
   2254                custom: [{ interval_s: 3, max_count: 3 }],
   2255              },
   2256            },
   2257          });
   2258        },
   2259        1: async () => {
   2260          for (let i = 0; i < 3; i++) {
   2261            await checkSearch({
   2262              name: "3s " + i,
   2263              searchString: "sponsored",
   2264              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2265            });
   2266          }
   2267          await checkSearch({
   2268            name: "3s additional",
   2269            searchString: "sponsored",
   2270            expectedResults: [],
   2271          });
   2272        },
   2273      });
   2274    },
   2275  });
   2276 });
   2277 
   2278 // Tests a config change: 1 interval -> lifetime.
   2279 // Impression counts for the old interval should contribute to the new lifetime
   2280 // cap.
   2281 add_task(async function configChange_1IntervalToLifetime() {
   2282  await doTest({
   2283    config: {
   2284      impression_caps: {
   2285        sponsored: {
   2286          custom: [{ interval_s: 3, max_count: 3 }],
   2287        },
   2288      },
   2289    },
   2290    callback: async () => {
   2291      await doTimedCallbacks({
   2292        0: async () => {
   2293          for (let i = 0; i < 3; i++) {
   2294            await checkSearch({
   2295              name: "0s " + i,
   2296              searchString: "sponsored",
   2297              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2298            });
   2299          }
   2300          await QuickSuggestTestUtils.setConfig({
   2301            impression_caps: {
   2302              sponsored: {
   2303                lifetime: 3,
   2304              },
   2305            },
   2306          });
   2307        },
   2308        3: async () => {
   2309          await checkSearch({
   2310            name: "3s",
   2311            searchString: "sponsored",
   2312            expectedResults: [],
   2313          });
   2314        },
   2315      });
   2316    },
   2317  });
   2318 });
   2319 
   2320 // Tests a config change: lifetime cap -> higher lifetime cap
   2321 add_task(async function configChange_lifetimeCapHigher() {
   2322  await doTest({
   2323    config: {
   2324      impression_caps: {
   2325        sponsored: {
   2326          lifetime: 3,
   2327        },
   2328      },
   2329    },
   2330    callback: async () => {
   2331      await doTimedCallbacks({
   2332        0: async () => {
   2333          for (let i = 0; i < 3; i++) {
   2334            await checkSearch({
   2335              name: "0s " + i,
   2336              searchString: "sponsored",
   2337              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2338            });
   2339          }
   2340          await checkSearch({
   2341            name: "0s additional",
   2342            searchString: "sponsored",
   2343            expectedResults: [],
   2344          });
   2345          await QuickSuggestTestUtils.setConfig({
   2346            impression_caps: {
   2347              sponsored: {
   2348                lifetime: 5,
   2349              },
   2350            },
   2351          });
   2352        },
   2353        1: async () => {
   2354          for (let i = 0; i < 2; i++) {
   2355            await checkSearch({
   2356              name: "1s " + i,
   2357              searchString: "sponsored",
   2358              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2359            });
   2360          }
   2361          await checkSearch({
   2362            name: "1s additional",
   2363            searchString: "sponsored",
   2364            expectedResults: [],
   2365          });
   2366        },
   2367      });
   2368    },
   2369  });
   2370 });
   2371 
   2372 // Tests a config change: lifetime cap -> lower lifetime cap
   2373 add_task(async function configChange_lifetimeCapLower() {
   2374  await doTest({
   2375    config: {
   2376      impression_caps: {
   2377        sponsored: {
   2378          lifetime: 3,
   2379        },
   2380      },
   2381    },
   2382    callback: async () => {
   2383      await doTimedCallbacks({
   2384        0: async () => {
   2385          for (let i = 0; i < 3; i++) {
   2386            await checkSearch({
   2387              name: "0s " + i,
   2388              searchString: "sponsored",
   2389              expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2390            });
   2391          }
   2392          await checkSearch({
   2393            name: "0s additional",
   2394            searchString: "sponsored",
   2395            expectedResults: [],
   2396          });
   2397          await QuickSuggestTestUtils.setConfig({
   2398            impression_caps: {
   2399              sponsored: {
   2400                lifetime: 1,
   2401              },
   2402            },
   2403          });
   2404        },
   2405        1: async () => {
   2406          await checkSearch({
   2407            name: "1s",
   2408            searchString: "sponsored",
   2409            expectedResults: [],
   2410          });
   2411        },
   2412      });
   2413    },
   2414  });
   2415 });
   2416 
   2417 // Makes sure stats are serialized to and from the pref correctly.
   2418 add_task(async function prefSync() {
   2419  await doTest({
   2420    config: {
   2421      impression_caps: {
   2422        sponsored: {
   2423          lifetime: 5,
   2424          custom: [
   2425            { interval_s: 3, max_count: 2 },
   2426            { interval_s: 5, max_count: 4 },
   2427          ],
   2428        },
   2429      },
   2430    },
   2431    callback: async () => {
   2432      for (let i = 0; i < 2; i++) {
   2433        await checkSearch({
   2434          name: i,
   2435          searchString: "sponsored",
   2436          expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2437        });
   2438      }
   2439 
   2440      let json = UrlbarPrefs.get("quicksuggest.impressionCaps.stats");
   2441      Assert.ok(json, "JSON is non-empty");
   2442      Assert.deepEqual(
   2443        JSON.parse(json),
   2444        {
   2445          sponsored: [
   2446            {
   2447              intervalSeconds: 3,
   2448              count: 2,
   2449              maxCount: 2,
   2450              startDateMs: 0,
   2451              impressionDateMs: 0,
   2452            },
   2453            {
   2454              intervalSeconds: 5,
   2455              count: 2,
   2456              maxCount: 4,
   2457              startDateMs: 0,
   2458              impressionDateMs: 0,
   2459            },
   2460            {
   2461              intervalSeconds: null,
   2462              count: 2,
   2463              maxCount: 5,
   2464              startDateMs: 0,
   2465              impressionDateMs: 0,
   2466            },
   2467          ],
   2468        },
   2469        "JSON is correct"
   2470      );
   2471 
   2472      QuickSuggest.impressionCaps._test_reloadStats();
   2473      Assert.deepEqual(
   2474        QuickSuggest.impressionCaps._test_stats,
   2475        {
   2476          sponsored: [
   2477            {
   2478              intervalSeconds: 3,
   2479              count: 2,
   2480              maxCount: 2,
   2481              startDateMs: 0,
   2482              impressionDateMs: 0,
   2483            },
   2484            {
   2485              intervalSeconds: 5,
   2486              count: 2,
   2487              maxCount: 4,
   2488              startDateMs: 0,
   2489              impressionDateMs: 0,
   2490            },
   2491            {
   2492              intervalSeconds: Infinity,
   2493              count: 2,
   2494              maxCount: 5,
   2495              startDateMs: 0,
   2496              impressionDateMs: 0,
   2497            },
   2498          ],
   2499        },
   2500        "Impression stats were properly restored from the pref"
   2501      );
   2502    },
   2503  });
   2504 });
   2505 
   2506 // Tests direct changes to the stats pref.
   2507 add_task(async function prefDirectlyChanged() {
   2508  await doTest({
   2509    config: {
   2510      impression_caps: {
   2511        sponsored: {
   2512          lifetime: 5,
   2513          custom: [{ interval_s: 3, max_count: 3 }],
   2514        },
   2515      },
   2516    },
   2517    callback: async () => {
   2518      let expectedStats = {
   2519        sponsored: [
   2520          {
   2521            intervalSeconds: 3,
   2522            count: 0,
   2523            maxCount: 3,
   2524            startDateMs: 0,
   2525            impressionDateMs: 0,
   2526          },
   2527          {
   2528            intervalSeconds: Infinity,
   2529            count: 0,
   2530            maxCount: 5,
   2531            startDateMs: 0,
   2532            impressionDateMs: 0,
   2533          },
   2534        ],
   2535      };
   2536 
   2537      UrlbarPrefs.set("quicksuggest.impressionCaps.stats", "bogus");
   2538      Assert.deepEqual(
   2539        QuickSuggest.impressionCaps._test_stats,
   2540        expectedStats,
   2541        "Expected stats for 'bogus'"
   2542      );
   2543 
   2544      UrlbarPrefs.set("quicksuggest.impressionCaps.stats", JSON.stringify({}));
   2545      Assert.deepEqual(
   2546        QuickSuggest.impressionCaps._test_stats,
   2547        expectedStats,
   2548        "Expected stats for {}"
   2549      );
   2550 
   2551      UrlbarPrefs.set(
   2552        "quicksuggest.impressionCaps.stats",
   2553        JSON.stringify({ sponsored: "bogus" })
   2554      );
   2555      Assert.deepEqual(
   2556        QuickSuggest.impressionCaps._test_stats,
   2557        expectedStats,
   2558        "Expected stats for { sponsored: 'bogus' }"
   2559      );
   2560 
   2561      UrlbarPrefs.set(
   2562        "quicksuggest.impressionCaps.stats",
   2563        JSON.stringify({
   2564          sponsored: [
   2565            {
   2566              intervalSeconds: 3,
   2567              count: 0,
   2568              maxCount: 3,
   2569              startDateMs: 0,
   2570              impressionDateMs: 0,
   2571            },
   2572            {
   2573              intervalSeconds: "bogus",
   2574              count: 0,
   2575              maxCount: 99,
   2576              startDateMs: 0,
   2577              impressionDateMs: 0,
   2578            },
   2579            {
   2580              intervalSeconds: Infinity,
   2581              count: 0,
   2582              maxCount: 5,
   2583              startDateMs: 0,
   2584              impressionDateMs: 0,
   2585            },
   2586          ],
   2587        })
   2588      );
   2589      Assert.deepEqual(
   2590        QuickSuggest.impressionCaps._test_stats,
   2591        expectedStats,
   2592        "Expected stats with intervalSeconds: 'bogus'"
   2593      );
   2594 
   2595      UrlbarPrefs.set(
   2596        "quicksuggest.impressionCaps.stats",
   2597        JSON.stringify({
   2598          sponsored: [
   2599            {
   2600              intervalSeconds: 3,
   2601              count: 0,
   2602              maxCount: 123,
   2603              startDateMs: 0,
   2604              impressionDateMs: 0,
   2605            },
   2606            {
   2607              intervalSeconds: Infinity,
   2608              count: 0,
   2609              maxCount: 456,
   2610              startDateMs: 0,
   2611              impressionDateMs: 0,
   2612            },
   2613          ],
   2614        })
   2615      );
   2616      Assert.deepEqual(
   2617        QuickSuggest.impressionCaps._test_stats,
   2618        expectedStats,
   2619        "Expected stats with `maxCount` values different from caps"
   2620      );
   2621 
   2622      let stats = {
   2623        sponsored: [
   2624          {
   2625            intervalSeconds: 3,
   2626            count: 1,
   2627            maxCount: 3,
   2628            startDateMs: 99,
   2629            impressionDateMs: 99,
   2630          },
   2631          {
   2632            intervalSeconds: Infinity,
   2633            count: 7,
   2634            maxCount: 5,
   2635            startDateMs: 1337,
   2636            impressionDateMs: 1337,
   2637          },
   2638        ],
   2639      };
   2640      UrlbarPrefs.set(
   2641        "quicksuggest.impressionCaps.stats",
   2642        JSON.stringify(stats)
   2643      );
   2644      Assert.deepEqual(
   2645        QuickSuggest.impressionCaps._test_stats,
   2646        stats,
   2647        "Expected stats with valid JSON"
   2648      );
   2649    },
   2650  });
   2651 });
   2652 
   2653 // Tests multiple interval periods where the cap is not hit. Telemetry should be
   2654 // recorded for these periods.
   2655 add_task(async function intervalsElapsedButCapNotHit() {
   2656  await doTest({
   2657    config: {
   2658      impression_caps: {
   2659        sponsored: {
   2660          custom: [{ interval_s: 1, max_count: 3 }],
   2661        },
   2662      },
   2663    },
   2664    callback: async () => {
   2665      await doTimedCallbacks({
   2666        // 1s
   2667        1: async () => {
   2668          await checkSearch({
   2669            name: "1s",
   2670            searchString: "sponsored",
   2671            expectedResults: [EXPECTED_SPONSORED_URLBAR_RESULT],
   2672          });
   2673        },
   2674        // 10s
   2675        10: async () => {
   2676          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2677        },
   2678      });
   2679    },
   2680  });
   2681 });
   2682 
   2683 // Simulates reset events across a restart with the following:
   2684 //
   2685 //   S                      S                          R
   2686 //   >----|----|----|----|----|----|----|----|----|----|
   2687 //   0s   1    2    3    4    5    6    7    8    9   10
   2688 //
   2689 //   1. Startup at 0s
   2690 //   2. Caps and stats initialized with interval_s: 1
   2691 //   3. Startup at 4.5s
   2692 //   4. Reset triggered at 10s
   2693 //
   2694 // Expected:
   2695 //   At 10s: 6 batched resets for periods starting at 4s
   2696 add_task(async function restart_1() {
   2697  await doTest({
   2698    config: {
   2699      impression_caps: {
   2700        sponsored: {
   2701          custom: [{ interval_s: 1, max_count: 1 }],
   2702        },
   2703      },
   2704    },
   2705    callback: async () => {
   2706      gStartupDateMsStub.returns(4500);
   2707      await doTimedCallbacks({
   2708        // 10s: 6 batched resets for periods starting at 4s
   2709        10: async () => {
   2710          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2711        },
   2712      });
   2713    },
   2714  });
   2715  gStartupDateMsStub.returns(0);
   2716 });
   2717 
   2718 // Simulates reset events across a restart with the following:
   2719 //
   2720 //   S                        S                        R
   2721 //   >----|----|----|----|----|----|----|----|----|----|
   2722 //   0s   1    2    3    4    5    6    7    8    9   10
   2723 //
   2724 //   1. Startup at 0s
   2725 //   2. Caps and stats initialized with interval_s: 1
   2726 //   3. Startup at 5s
   2727 //   4. Reset triggered at 10s
   2728 //
   2729 // Expected:
   2730 //   At 10s: 5 batched resets for periods starting at 5s
   2731 add_task(async function restart_2() {
   2732  await doTest({
   2733    config: {
   2734      impression_caps: {
   2735        sponsored: {
   2736          custom: [{ interval_s: 1, max_count: 1 }],
   2737        },
   2738      },
   2739    },
   2740    callback: async () => {
   2741      gStartupDateMsStub.returns(5000);
   2742      await doTimedCallbacks({
   2743        // 10s: 5 batched resets for periods starting at 5s
   2744        10: async () => {
   2745          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2746        },
   2747      });
   2748    },
   2749  });
   2750  gStartupDateMsStub.returns(0);
   2751 });
   2752 
   2753 // Simulates reset events across a restart with the following:
   2754 //
   2755 //   S                           S                     R
   2756 //   >----|----|----|----|----|----|----|----|----|----|
   2757 //   0s   1    2    3    4    5    6    7    8    9   10
   2758 //
   2759 //   1. Startup at 0s
   2760 //   2. Caps and stats initialized with interval_s: 1
   2761 //   3. Startup at 5.5s
   2762 //   4. Reset triggered at 10s
   2763 //
   2764 // Expected:
   2765 //   At 10s: 5 batched resets for periods starting at 5s
   2766 add_task(async function restart_3() {
   2767  await doTest({
   2768    config: {
   2769      impression_caps: {
   2770        sponsored: {
   2771          custom: [{ interval_s: 1, max_count: 1 }],
   2772        },
   2773      },
   2774    },
   2775    callback: async () => {
   2776      gStartupDateMsStub.returns(5500);
   2777      await doTimedCallbacks({
   2778        // 10s: 5 batched resets for periods starting at 5s
   2779        10: async () => {
   2780          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2781        },
   2782      });
   2783    },
   2784  });
   2785  gStartupDateMsStub.returns(0);
   2786 });
   2787 
   2788 // Simulates reset events across a restart with the following:
   2789 //
   2790 //   S    S   RR        RR
   2791 //   >---------|---------|
   2792 //   0s       10        20
   2793 //
   2794 //   1. Startup at 0s
   2795 //   2. Caps and stats initialized with interval_s: 10
   2796 //   3. Startup at 5s
   2797 //   4. Resets triggered at 9s, 10s, 19s, 20s
   2798 //
   2799 // Expected:
   2800 //   At 10s: 1 reset for period starting at 0s
   2801 //   At 20s: 1 reset for period starting at 10s
   2802 add_task(async function restart_4() {
   2803  await doTest({
   2804    config: {
   2805      impression_caps: {
   2806        sponsored: {
   2807          custom: [{ interval_s: 10, max_count: 1 }],
   2808        },
   2809      },
   2810    },
   2811    callback: async () => {
   2812      gStartupDateMsStub.returns(5000);
   2813      await doTimedCallbacks({
   2814        // 9s: no resets
   2815        9: async () => {
   2816          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2817        },
   2818        // 10s: 1 reset for period starting at 0s
   2819        10: async () => {
   2820          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2821        },
   2822        // 19s: no resets
   2823        19: async () => {
   2824          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2825        },
   2826        // 20s: 1 reset for period starting at 10s
   2827        20: async () => {
   2828          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2829        },
   2830      });
   2831    },
   2832  });
   2833  gStartupDateMsStub.returns(0);
   2834 });
   2835 
   2836 // Simulates reset events across a restart with the following:
   2837 //
   2838 //   S    S              R
   2839 //   >---------|---------|
   2840 //   0s       10        20
   2841 //
   2842 //   1. Startup at 0s
   2843 //   2. Caps and stats initialized with interval_s: 10
   2844 //   3. Startup at 5s
   2845 //   4. Reset triggered at 20s
   2846 //
   2847 // Expected:
   2848 //   At 20s: 2 batched resets for periods starting at 0s
   2849 add_task(async function restart_5() {
   2850  await doTest({
   2851    config: {
   2852      impression_caps: {
   2853        sponsored: {
   2854          custom: [{ interval_s: 10, max_count: 1 }],
   2855        },
   2856      },
   2857    },
   2858    callback: async () => {
   2859      gStartupDateMsStub.returns(5000);
   2860      await doTimedCallbacks({
   2861        // 20s: 2 batches resets for periods starting at 0s
   2862        20: async () => {
   2863          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2864        },
   2865      });
   2866    },
   2867  });
   2868  gStartupDateMsStub.returns(0);
   2869 });
   2870 
   2871 // Simulates reset events across a restart with the following:
   2872 //
   2873 //   S              S   RR        RR
   2874 //   >---------|---------|---------|
   2875 //   0s       10        20        30
   2876 //
   2877 //   1. Startup at 0s
   2878 //   2. Caps and stats initialized with interval_s: 10
   2879 //   3. Startup at 15s
   2880 //   4. Resets triggered at 19s, 20s, 29s, 30s
   2881 //
   2882 // Expected:
   2883 //   At 20s: 1 reset for period starting at 10s
   2884 //   At 30s: 1 reset for period starting at 20s
   2885 add_task(async function restart_6() {
   2886  await doTest({
   2887    config: {
   2888      impression_caps: {
   2889        sponsored: {
   2890          custom: [{ interval_s: 10, max_count: 1 }],
   2891        },
   2892      },
   2893    },
   2894    callback: async () => {
   2895      gStartupDateMsStub.returns(15000);
   2896      await doTimedCallbacks({
   2897        // 19s: no resets
   2898        19: async () => {
   2899          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2900        },
   2901        // 20s: 1 reset for period starting at 10s
   2902        20: async () => {
   2903          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2904        },
   2905        // 29s: no resets
   2906        29: async () => {
   2907          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2908        },
   2909        // 30s: 1 reset for period starting at 20s
   2910        30: async () => {
   2911          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2912        },
   2913      });
   2914    },
   2915  });
   2916  gStartupDateMsStub.returns(0);
   2917 });
   2918 
   2919 // Simulates reset events across a restart with the following:
   2920 //
   2921 //   S              S              R
   2922 //   >---------|---------|---------|
   2923 //   0s       10        20        30
   2924 //
   2925 //   1. Startup at 0s
   2926 //   2. Caps and stats initialized with interval_s: 10
   2927 //   3. Startup at 15s
   2928 //   4. Reset triggered at 30s
   2929 //
   2930 // Expected:
   2931 //   At 30s: 2 batched resets for periods starting at 10s
   2932 add_task(async function restart_7() {
   2933  await doTest({
   2934    config: {
   2935      impression_caps: {
   2936        sponsored: {
   2937          custom: [{ interval_s: 10, max_count: 1 }],
   2938        },
   2939      },
   2940    },
   2941    callback: async () => {
   2942      gStartupDateMsStub.returns(15000);
   2943      await doTimedCallbacks({
   2944        // 30s: 2 batched resets for periods starting at 10s
   2945        30: async () => {
   2946          QuickSuggest.impressionCaps._test_resetElapsedCounters();
   2947        },
   2948      });
   2949    },
   2950  });
   2951  gStartupDateMsStub.returns(0);
   2952 });
   2953 
   2954 // Tests reset telemetry recorded on shutdown.
   2955 add_task(async function shutdown() {
   2956  await doTest({
   2957    config: {
   2958      impression_caps: {
   2959        sponsored: {
   2960          custom: [{ interval_s: 1, max_count: 1 }],
   2961        },
   2962      },
   2963    },
   2964    callback: async () => {
   2965      // Make `Date.now()` return 10s. Since the cap's `interval_s` is 1s and
   2966      // before this `Date.now()` returned 0s, 10 reset events should be
   2967      // recorded on shutdown.
   2968      gDateNowStub.returns(10000);
   2969 
   2970      // Simulate shutdown.
   2971      Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
   2972      AsyncShutdown.profileChangeTeardown._trigger();
   2973 
   2974      gDateNowStub.returns(0);
   2975      Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
   2976    },
   2977  });
   2978 });
   2979 
   2980 // Tests the reset interval in realtime.
   2981 add_task(async function resetInterval() {
   2982  // Remove the test stubs so we can test in realtime.
   2983  gDateNowStub.restore();
   2984  gStartupDateMsStub.restore();
   2985 
   2986  await doTest({
   2987    config: {
   2988      impression_caps: {
   2989        sponsored: {
   2990          custom: [{ interval_s: 0.1, max_count: 1 }],
   2991        },
   2992      },
   2993    },
   2994    callback: async () => {
   2995      // Restart the reset interval now with a 1s period. Since the cap's
   2996      // `interval_s` is 0.1s, at least 10 reset events should be recorded the
   2997      // first time the reset interval fires. The exact number depends on timing
   2998      // and the machine running the test: how many 0.1s intervals elapse
   2999      // between when the config is set to when the reset interval fires. For
   3000      // that reason, we allow some leeway when checking `eventCount` below to
   3001      // avoid intermittent failures.
   3002      QuickSuggest.impressionCaps._test_setCountersResetInterval(1000);
   3003 
   3004      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
   3005      await new Promise(r => setTimeout(r, 1100));
   3006 
   3007      // Restore the reset interval to its default.
   3008      QuickSuggest.impressionCaps._test_setCountersResetInterval();
   3009    },
   3010  });
   3011 
   3012  // Recreate the test stubs.
   3013  gDateNowStub = gSandbox.stub(
   3014    Cu.getGlobalForObject(UrlbarProviderQuickSuggest).Date,
   3015    "now"
   3016  );
   3017  gStartupDateMsStub = gSandbox.stub(
   3018    QuickSuggest.impressionCaps,
   3019    "_getStartupDateMs"
   3020  );
   3021  gStartupDateMsStub.returns(0);
   3022 });
   3023 
   3024 /**
   3025 * Main test helper. Sets up state, calls your callback, and resets state.
   3026 *
   3027 * @param {object} options
   3028 *   Options object.
   3029 * @param {object} options.config
   3030 *   The quick suggest config to use during the test.
   3031 * @param {Function} options.callback
   3032 *   The callback that will be run with the {@link config}
   3033 */
   3034 async function doTest({ config, callback }) {
   3035  // Make `Date.now()` return 0 to start with. It's necessary to do this before
   3036  // calling `withConfig()` because when a new config is set, the provider
   3037  // validates its impression stats, whose `startDateMs` values depend on
   3038  // `Date.now()`.
   3039  gDateNowStub.returns(0);
   3040 
   3041  info(`Clearing stats and setting config`);
   3042  UrlbarPrefs.clear("quicksuggest.impressionCaps.stats");
   3043  QuickSuggest.impressionCaps._test_reloadStats();
   3044  await QuickSuggestTestUtils.withConfig({ config, callback });
   3045 }
   3046 
   3047 /**
   3048 * Does a series of timed searches and checks their results. This
   3049 * function relies on `doTimedCallbacks()`, so it may be helpful to look at it
   3050 * too.
   3051 *
   3052 * @param {string} searchString
   3053 *   The query that should be timed
   3054 * @param {object} expectedBySecond
   3055 *   An object that maps from seconds to objects that describe the searches to
   3056 *   perform, their expected results, and the expected telemetry. For a given
   3057 *   entry `S -> E` in this object, searches are performed S seconds after this
   3058 *   function is called. `E` is an object that looks like this:
   3059 *
   3060 *     { results }
   3061 *
   3062 *     {array} results
   3063 *       An array of arrays. A search is performed for each sub-array in
   3064 *       `results`, and the contents of the sub-array are the expected results
   3065 *       for that search.
   3066 *
   3067 *   Example:
   3068 *
   3069 *     {
   3070 *       0: {
   3071 *         results: [[R1], []],
   3072 *       }
   3073 *       1: {
   3074 *         results: [[]],
   3075 *       },
   3076 *     }
   3077 *
   3078 *     0 seconds after `doTimedSearches()` is called, two searches are
   3079 *     performed. The first one is expected to return a single result R1, and
   3080 *     the second search is expected to return no results.
   3081 *
   3082 *     1 second after `doTimedSearches()` is called, one search is performed.
   3083 *     It's expected to return no results.
   3084 */
   3085 async function doTimedSearches(searchString, expectedBySecond) {
   3086  await doTimedCallbacks(
   3087    Object.entries(expectedBySecond).reduce((memo, [second, { results }]) => {
   3088      memo[second] = async () => {
   3089        for (let i = 0; i < results.length; i++) {
   3090          let expectedResults = results[i];
   3091          await checkSearch({
   3092            searchString,
   3093            expectedResults,
   3094            name: `${second}s search ${i + 1} of ${results.length}`,
   3095          });
   3096        }
   3097      };
   3098      return memo;
   3099    }, {})
   3100  );
   3101 }
   3102 
   3103 /**
   3104 * Takes a series a callbacks and times at which they should be called, and
   3105 * calls them accordingly. This function is specifically designed for
   3106 * UrlbarProviderQuickSuggest and its impression capping implementation because
   3107 * it works by stubbing `Date.now()` within UrlbarProviderQuickSuggest. The
   3108 * callbacks are not actually called at the given times but instead `Date.now()`
   3109 * is stubbed so that UrlbarProviderQuickSuggest will think they are being
   3110 * called at the given times.
   3111 *
   3112 * A more general implementation of this helper function that isn't tailored to
   3113 * UrlbarProviderQuickSuggest is commented out below, and unfortunately it
   3114 * doesn't work properly on macOS.
   3115 *
   3116 * @param {object} callbacksBySecond
   3117 *   An object that maps from seconds to callback functions. For a given entry
   3118 *   `S -> F` in this object, the callback F is called S seconds after
   3119 *   `doTimedCallbacks()` is called.
   3120 */
   3121 async function doTimedCallbacks(callbacksBySecond) {
   3122  let entries = Object.entries(callbacksBySecond).sort(([t1], [t2]) => t1 - t2);
   3123  for (let [timeoutSeconds, callback] of entries) {
   3124    gDateNowStub.returns(1000 * timeoutSeconds);
   3125    await callback();
   3126  }
   3127 }
   3128 
   3129 /*
   3130 // This is the original implementation of `doTimedCallbacks()`, left here for
   3131 // reference or in case the macOS problem described below is fixed. Instead of
   3132 // stubbing `Date.now()` within UrlbarProviderQuickSuggest, it starts parallel
   3133 // timers so that the callbacks are actually called at appropriate times. This
   3134 // version of `doTimedCallbacks()` is therefore more generally useful, but it
   3135 // has the drawback that your test has to run in real time. e.g., if one of your
   3136 // callbacks needs to run 10s from now, the test must actually wait 10s.
   3137 //
   3138 // Unfortunately macOS seems to have some kind of limit of ~33 total 1-second
   3139 // timers during any xpcshell test -- not 33 simultaneous timers but 33 total
   3140 // timers. After that, timers fire randomly and with huge timeout periods that
   3141 // are often exactly 10s greater than the specified period, as if some 10s
   3142 // timeout internal to macOS is being hit. This problem does not seem to happen
   3143 // when running the full browser, only during xpcshell tests. In fact the
   3144 // problem can be reproduced in an xpcshell test that simply creates an interval
   3145 // timer whose period is 1s (e.g., using `setInterval()` from Timer.sys.mjs).
   3146 // After ~33 ticks, the timer's period jumps to ~10s.
   3147 async function doTimedCallbacks(callbacksBySecond) {
   3148  await Promise.all(
   3149    Object.entries(callbacksBySecond).map(
   3150      ([timeoutSeconds, callback]) => new Promise(
   3151        resolve => setTimeout(
   3152          () => callback().then(resolve),
   3153          1000 * parseInt(timeoutSeconds)
   3154        )
   3155      )
   3156    )
   3157  );
   3158 }
   3159 */
   3160 
   3161 /**
   3162 * Does a search, triggers an engagement, and checks the results.
   3163 *
   3164 * @param {object} options
   3165 *   Options object.
   3166 * @param {string} options.name
   3167 *   This value is the name of the search and will be logged in messages to make
   3168 *   debugging easier.
   3169 * @param {string} options.searchString
   3170 *   The query that should be searched.
   3171 * @param {Array} options.expectedResults
   3172 *   The results that are expected from the search.
   3173 */
   3174 async function checkSearch({ name, searchString, expectedResults }) {
   3175  let quickSuggestProviderInstance = UrlbarProvidersManager.getProvider(
   3176    UrlbarProviderQuickSuggest.name
   3177  );
   3178  info(`Preparing search "${name}" with search string "${searchString}"`);
   3179  let context = createContext(searchString, {
   3180    providers: [UrlbarProviderQuickSuggest.name],
   3181    isPrivate: false,
   3182  });
   3183  info(`Doing search: ${name}`);
   3184  await check_results({
   3185    context,
   3186    matches: expectedResults,
   3187  });
   3188  info(`Finished search: ${name}`);
   3189 
   3190  // Impression stats are updated only on engagement, so force one now.
   3191  // `selIndex` doesn't really matter but since we're not trying to simulate a
   3192  // click on the suggestion, pass in -1 to ensure we don't record a click.
   3193  if (quickSuggestProviderInstance._resultFromLastQuery) {
   3194    quickSuggestProviderInstance._resultFromLastQuery.isVisible = true;
   3195  }
   3196  const controller = UrlbarTestUtils.newMockController({
   3197    input: {
   3198      isPrivate: true,
   3199      onFirstResult() {
   3200        return false;
   3201      },
   3202      getSearchSource() {
   3203        return "dummy-search-source";
   3204      },
   3205      window: {
   3206        location: {
   3207          href: AppConstants.BROWSER_CHROME_URL,
   3208        },
   3209      },
   3210    },
   3211  });
   3212  controller.setView({
   3213    get visibleResults() {
   3214      return context.results;
   3215    },
   3216    controller: {
   3217      removeResult() {},
   3218    },
   3219  });
   3220 
   3221  // If this test is ever re-enabled, this line will need to be updated for the
   3222  // new engagement API (onEngagement())
   3223  quickSuggestProviderInstance.onLegacyEngagement(
   3224    "engagement",
   3225    context,
   3226    {
   3227      selIndex: -1,
   3228    },
   3229    controller
   3230  );
   3231 }