tor-browser

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

test_quicksuggest_exposures.js (16590B)


      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 Suggest exposure suggestions.
      6 
      7 // Notes on the following mock suggestions:
      8 //
      9 // * `rsSuggestionType` echoes the `suggestion_type` in the RS record so that
     10 //   the test can verify that matched results correspond to the expected
     11 //   suggestions/records. It's not a necessary or special property for exposure
     12 //   suggestions.
     13 // * `score` ensures a stable sort vs. other suggestion types for the test. It's
     14 //   not necessary for exposure suggestions.
     15 const REMOTE_SETTINGS_RECORDS = [
     16  {
     17    type: "dynamic-suggestions",
     18    suggestion_type: "test-exposure-aaa",
     19    score: 1.0,
     20    attachment: [
     21      {
     22        keywords: ["aaa keyword", "aaa bbb keyword", "wikipedia"],
     23        data: {
     24          result: {
     25            isHiddenExposure: true,
     26            payload: {
     27              rsSuggestionType: "test-exposure-aaa",
     28            },
     29          },
     30        },
     31      },
     32    ],
     33  },
     34  {
     35    type: "dynamic-suggestions",
     36    suggestion_type: "test-exposure-bbb",
     37    score: 1.0,
     38    attachment: {
     39      keywords: ["bbb keyword", "aaa bbb keyword", "wikipedia"],
     40      data: {
     41        result: {
     42          isHiddenExposure: true,
     43          payload: {
     44            rsSuggestionType: "test-exposure-bbb",
     45            telemetryType: "bbb_telemetry_type",
     46          },
     47        },
     48      },
     49    },
     50  },
     51  {
     52    type: QuickSuggestTestUtils.RS_TYPE.WIKIPEDIA,
     53    attachment: [QuickSuggestTestUtils.wikipediaRemoteSettings()],
     54  },
     55 ];
     56 
     57 const EXPECTED_AAA_RESULT = makeExpectedResult({
     58  rsSuggestionType: "test-exposure-aaa",
     59 });
     60 
     61 const EXPECTED_BBB_RESULT = makeExpectedResult({
     62  rsSuggestionType: "test-exposure-bbb",
     63  telemetryType: "bbb_telemetry_type",
     64 });
     65 
     66 const EXPECTED_WIKIPEDIA_RESULT = {
     67  ...QuickSuggestTestUtils.wikipediaResult(),
     68  exposureTelemetry: UrlbarUtils.EXPOSURE_TELEMETRY.NONE,
     69 };
     70 
     71 add_setup(async function () {
     72  // Add many exposure and AMP suggestions that have the "maxresults" keyword.
     73  let maxResults = UrlbarPrefs.get("maxRichResults");
     74  Assert.greater(maxResults, 0, "This test expects maxRichResults to be > 0");
     75  let ampRecord = {
     76    collection: QuickSuggestTestUtils.RS_COLLECTION.AMP,
     77    type: QuickSuggestTestUtils.RS_TYPE.AMP,
     78    attachment: [],
     79  };
     80  REMOTE_SETTINGS_RECORDS.push(ampRecord);
     81  for (let i = 0; i < maxResults; i++) {
     82    ampRecord.attachment.push(
     83      QuickSuggestTestUtils.ampRemoteSettings({
     84        keywords: ["maxresults"],
     85        title: "maxresults " + i,
     86        url: "https://example.com/maxresults/" + i,
     87      })
     88    );
     89    REMOTE_SETTINGS_RECORDS.push({
     90      type: "dynamic-suggestions",
     91      suggestion_type: "test-exposure-maxresults-" + i,
     92      score: 1.0,
     93      attachment: [
     94        {
     95          keywords: ["maxresults"],
     96          data: {
     97            result: {
     98              isHiddenExposure: true,
     99              payload: {
    100                rsSuggestionType: "test-exposure-maxresults-" + i,
    101              },
    102            },
    103          },
    104        },
    105      ],
    106    });
    107  }
    108 
    109  await QuickSuggestTestUtils.ensureQuickSuggestInit({
    110    remoteSettingsRecords: REMOTE_SETTINGS_RECORDS,
    111    prefs: [
    112      [
    113        "quicksuggest.dynamicSuggestionTypes",
    114        "test-exposure-aaa,test-exposure-bbb",
    115      ],
    116      ["suggest.quicksuggest.all", true],
    117      ["quicksuggest.ampTopPickCharThreshold", 0],
    118    ],
    119  });
    120 });
    121 
    122 add_task(async function telemetryType_default() {
    123  Assert.equal(
    124    QuickSuggest.getFeature("DynamicSuggestions").getSuggestionTelemetryType({
    125      data: {
    126        result: {
    127          isHiddenExposure: true,
    128        },
    129      },
    130    }),
    131    "exposure",
    132    "Telemetry type should be correct when using default"
    133  );
    134 });
    135 
    136 add_task(async function telemetryType_override() {
    137  Assert.equal(
    138    QuickSuggest.getFeature("DynamicSuggestions").getSuggestionTelemetryType({
    139      data: {
    140        result: {
    141          isHiddenExposure: true,
    142          payload: {
    143            telemetryType: "telemetry_type_override",
    144          },
    145        },
    146      },
    147    }),
    148    "telemetry_type_override",
    149    "Telemetry type should be correct when overridden"
    150  );
    151 });
    152 
    153 // Results for enabled exposure suggestions should always be added and have
    154 // hidden `exposureTelemetry` no matter the value of the `exposureResults` or
    155 // `showExposureResults` prefs.
    156 add_task(async function basic() {
    157  let queries = [
    158    {
    159      query: "no match",
    160      expected: [],
    161    },
    162    {
    163      query: "aaa keyword",
    164      expected: [EXPECTED_AAA_RESULT],
    165    },
    166    {
    167      query: "bbb keyword",
    168      expected: [EXPECTED_BBB_RESULT],
    169    },
    170    {
    171      query: "aaa bbb keyword",
    172      // The order of the results is reversed since they have the same suggested
    173      // index, due to how the muxer handles that.
    174      expected: [EXPECTED_BBB_RESULT, EXPECTED_AAA_RESULT],
    175    },
    176  ];
    177 
    178  let exposureResultsList = [
    179    undefined,
    180    "",
    181    "some_other_result",
    182    "exposure",
    183    "some_other_result,exposure",
    184  ];
    185 
    186  for (let exposureResults of exposureResultsList) {
    187    if (exposureResults === undefined) {
    188      UrlbarPrefs.clear("exposureResults");
    189    } else {
    190      UrlbarPrefs.set("exposureResults", exposureResults);
    191    }
    192 
    193    for (let showExposureResults of [undefined, true, false]) {
    194      if (showExposureResults === undefined) {
    195        UrlbarPrefs.clear("showExposureResults");
    196      } else {
    197        UrlbarPrefs.set("showExposureResults", showExposureResults);
    198      }
    199 
    200      info(
    201        "Doing basic subtest: " +
    202          JSON.stringify({
    203            exposureResults,
    204            showExposureResults,
    205          })
    206      );
    207 
    208      await doQueries(queries);
    209    }
    210  }
    211 
    212  UrlbarPrefs.clear("exposureResults");
    213  UrlbarPrefs.clear("showExposureResults");
    214 });
    215 
    216 // When only one exposure suggestion type is enabled, only its result should be
    217 // returned. This task assumes multiples types were added to remote settings in
    218 // the setup task.
    219 add_task(async function oneSuggestionType() {
    220  await withSuggestionTypesPref("test-exposure-bbb", async () => {
    221    await doQueries([
    222      {
    223        query: "aaa keyword",
    224        expected: [],
    225      },
    226      {
    227        query: "bbb keyword",
    228        expected: [EXPECTED_BBB_RESULT],
    229      },
    230      {
    231        query: "aaa bbb keyword",
    232        expected: [EXPECTED_BBB_RESULT],
    233      },
    234    ]);
    235  });
    236 });
    237 
    238 // When no exposure suggestion types are enabled, no results should be added.
    239 add_task(async function disabled() {
    240  await withSuggestionTypesPref("", async () => {
    241    await doQueries(
    242      ["aaa keyword", "bbb keyword", "aaa bbb keyword"].map(query => ({
    243        query,
    244        expected: [],
    245      }))
    246    );
    247  });
    248 });
    249 
    250 // When another visible suggestion also matches, both it and the exposure
    251 // suggestion should be added.
    252 add_task(async function otherVisibleSuggestionsAlsoMatched_1() {
    253  await withSuggestionTypesPref("test-exposure-aaa", async () => {
    254    await doQueries([
    255      {
    256        query: "aaa keyword",
    257        expected: [EXPECTED_AAA_RESULT],
    258      },
    259      {
    260        query: "wikipedia",
    261        expected: [EXPECTED_WIKIPEDIA_RESULT, EXPECTED_AAA_RESULT],
    262      },
    263    ]);
    264  });
    265 });
    266 
    267 // When another visible suggestion also matches, both it and all specified
    268 // exposure suggestions should be added.
    269 add_task(async function otherVisibleSuggestionsAlsoMatched_2() {
    270  await withSuggestionTypesPref(
    271    "test-exposure-aaa,test-exposure-bbb",
    272    async () => {
    273      await doQueries([
    274        {
    275          query: "aaa keyword",
    276          expected: [EXPECTED_AAA_RESULT],
    277        },
    278        {
    279          query: "bbb keyword",
    280          expected: [EXPECTED_BBB_RESULT],
    281        },
    282        {
    283          query: "aaa bbb keyword",
    284          expected: [EXPECTED_BBB_RESULT, EXPECTED_AAA_RESULT],
    285        },
    286        {
    287          query: "wikipedia",
    288          expected: [
    289            EXPECTED_WIKIPEDIA_RESULT,
    290            EXPECTED_BBB_RESULT,
    291            EXPECTED_AAA_RESULT,
    292          ],
    293        },
    294      ]);
    295    }
    296  );
    297 });
    298 
    299 // Tests with `maxResults` exposures. All should be added.
    300 add_task(async function maxResults_exposuresOnly() {
    301  let maxResults = UrlbarPrefs.get("maxRichResults");
    302  let exposureResults = [];
    303  for (let i = 0; i < maxResults; i++) {
    304    exposureResults.unshift(
    305      makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i })
    306    );
    307  }
    308 
    309  await doMaxResultsTest({
    310    expectedResults: [...exposureResults],
    311  });
    312 });
    313 
    314 // Tests with `maxResults` exposures and and `maxResults` history. All exposures
    315 // and history should be added since exposures are hidden.
    316 add_task(async function maxResults_exposuresHistory() {
    317  let maxResults = UrlbarPrefs.get("maxRichResults");
    318  let exposureResults = [];
    319  let historyResults = [];
    320  for (let i = 0; i < maxResults; i++) {
    321    exposureResults.unshift(
    322      makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i })
    323    );
    324    historyResults.push(
    325      new UrlbarResult({
    326        type: UrlbarUtils.RESULT_TYPE.URL,
    327        source: UrlbarUtils.RESULT_SOURCE.HISTORY,
    328        payload: { url: "http://example.com/history/" + i },
    329      })
    330    );
    331  }
    332 
    333  await doMaxResultsTest({
    334    includeHistory: true,
    335    expectedResults: [...historyResults, ...exposureResults],
    336  });
    337 });
    338 
    339 // Tests with `maxResults` exposures and and `maxResults` AMP suggestions. The
    340 // first AMP result should be added since it's visible and only one visible
    341 // Suggest result should be added. All exposures should be added since exposures
    342 // are hidden.
    343 add_task(async function maxResults_exposuresAmp() {
    344  let maxResults = UrlbarPrefs.get("maxRichResults");
    345  let exposureResults = [];
    346  for (let i = 0; i < maxResults; i++) {
    347    exposureResults.unshift(
    348      makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i })
    349    );
    350  }
    351 
    352  await doMaxResultsTest({
    353    includeAmp: true,
    354    expectedResults: [
    355      QuickSuggestTestUtils.ampResult({
    356        keyword: "maxresults",
    357        title: "maxresults 0",
    358        url: "https://example.com/maxresults/0",
    359        suggestedIndex: -1,
    360      }),
    361      ...exposureResults,
    362    ],
    363  });
    364 });
    365 
    366 // Tests with `maxResults` exposures, history, and AMP suggestions. The first
    367 // AMP result should be added since it's visible and only one visible Suggest
    368 // result should be added. `maxResults` - 1 history results should be added. All
    369 // exposures should be added since exposures are hidden.
    370 add_task(async function maxResults_exposuresHistoryAmp() {
    371  let maxResults = UrlbarPrefs.get("maxRichResults");
    372  let exposureResults = [];
    373  let historyResults = [];
    374  for (let i = 0; i < maxResults; i++) {
    375    exposureResults.unshift(
    376      makeExpectedResult({ rsSuggestionType: "test-exposure-maxresults-" + i })
    377    );
    378    historyResults.push(
    379      new UrlbarResult({
    380        type: UrlbarUtils.RESULT_TYPE.URL,
    381        source: UrlbarUtils.RESULT_SOURCE.HISTORY,
    382        payload: { url: "http://example.com/history/" + i },
    383      })
    384    );
    385  }
    386 
    387  await doMaxResultsTest({
    388    includeAmp: true,
    389    includeHistory: true,
    390    expectedResults: [
    391      ...historyResults.slice(0, maxResults - 1),
    392      QuickSuggestTestUtils.ampResult({
    393        keyword: "maxresults",
    394        title: "maxresults 0",
    395        url: "https://example.com/maxresults/0",
    396        suggestedIndex: -1,
    397      }),
    398      ...exposureResults,
    399    ],
    400  });
    401 });
    402 
    403 async function doMaxResultsTest({
    404  expectedResults,
    405  includeAmp = false,
    406  includeHistory = false,
    407 }) {
    408  let maxResults = UrlbarPrefs.get("maxRichResults");
    409  let providerNames = [UrlbarProviderQuickSuggest.name];
    410 
    411  // If history results should be included, register a test provider that adds a
    412  // bunch of history results.
    413  let historyProvider;
    414  let historyResults = [];
    415  if (includeHistory) {
    416    for (let i = 0; i < maxResults; i++) {
    417      historyResults.push(
    418        new UrlbarResult({
    419          type: UrlbarUtils.RESULT_TYPE.URL,
    420          source: UrlbarUtils.RESULT_SOURCE.HISTORY,
    421          payload: { url: "http://example.com/history/" + i },
    422        })
    423      );
    424    }
    425    historyProvider = new UrlbarTestUtils.TestProvider({
    426      results: historyResults,
    427    });
    428    UrlbarProvidersManager.registerProvider(historyProvider);
    429    providerNames.push(historyProvider.name);
    430  }
    431 
    432  // Enable sponsored suggestions according to whether AMP results should be
    433  // included.
    434  UrlbarPrefs.set("suggest.quicksuggest.sponsored", includeAmp);
    435  await QuickSuggestTestUtils.forceSync();
    436 
    437  // Generate the list of exposure suggestion types so we can trigger the
    438  // exposure suggestions.
    439  let exposureTypes = [];
    440  for (let i = 0; i < maxResults; i++) {
    441    exposureTypes.push("test-exposure-maxresults-" + i);
    442  }
    443 
    444  await withSuggestionTypesPref(exposureTypes.join(","), async () => {
    445    await check_results({
    446      context: createContext("maxresults", {
    447        providers: providerNames,
    448        isPrivate: false,
    449      }),
    450      matches: expectedResults,
    451    });
    452  });
    453 
    454  if (historyProvider) {
    455    UrlbarProvidersManager.unregisterProvider(historyProvider);
    456  }
    457  UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
    458  await QuickSuggestTestUtils.forceSync();
    459 }
    460 
    461 // Exposure suggestions should automatically bypass the usual `all` and
    462 // sponsored prefs.
    463 add_task(async function bypassPrefs() {
    464  UrlbarPrefs.set("suggest.quicksuggest.all", false);
    465  UrlbarPrefs.set("suggest.quicksuggest.sponsored", false);
    466 
    467  await withSuggestionTypesPref("test-exposure-aaa", async () => {
    468    await doQueries([
    469      {
    470        query: "aaa keyword",
    471        expected: [EXPECTED_AAA_RESULT],
    472      },
    473      {
    474        query: "aaa bbb keyword",
    475        expected: [EXPECTED_AAA_RESULT],
    476      },
    477      {
    478        query: "wikipedia",
    479        expected: [EXPECTED_AAA_RESULT],
    480      },
    481      {
    482        query: "doesn't match",
    483        expected: [],
    484      },
    485    ]);
    486  });
    487 
    488  UrlbarPrefs.set("suggest.quicksuggest.all", true);
    489  UrlbarPrefs.clear("suggest.quicksuggest.sponsored");
    490  await QuickSuggestTestUtils.forceSync();
    491 });
    492 
    493 // Tests the `quickSuggestDynamicSuggestionTypes` Nimbus variable with exposure
    494 // suggestions.
    495 add_task(async function nimbus() {
    496  // Clear `dynamicSuggestionTypes` to make sure the value comes from the Nimbus
    497  // variable and not the pref.
    498  await withSuggestionTypesPref("", async () => {
    499    let cleanup = await UrlbarTestUtils.initNimbusFeature({
    500      quickSuggestDynamicSuggestionTypes: "test-exposure-aaa,test-exposure-bbb",
    501    });
    502    await QuickSuggestTestUtils.forceSync();
    503    await doQueries([
    504      {
    505        query: "aaa keyword",
    506        expected: [EXPECTED_AAA_RESULT],
    507      },
    508      {
    509        query: "aaa bbb keyword",
    510        expected: [EXPECTED_BBB_RESULT, EXPECTED_AAA_RESULT],
    511      },
    512      {
    513        query: "wikipedia",
    514        expected: [
    515          EXPECTED_WIKIPEDIA_RESULT,
    516          EXPECTED_BBB_RESULT,
    517          EXPECTED_AAA_RESULT,
    518        ],
    519      },
    520      {
    521        query: "doesn't match",
    522        expected: [],
    523      },
    524    ]);
    525 
    526    await cleanup();
    527  });
    528 });
    529 
    530 async function doQueries(queries) {
    531  for (let { query, expected } of queries) {
    532    info(
    533      "Doing query: " +
    534        JSON.stringify({
    535          query,
    536          expected,
    537        })
    538    );
    539 
    540    await check_results({
    541      context: createContext(query, {
    542        providers: [UrlbarProviderQuickSuggest.name],
    543        isPrivate: false,
    544      }),
    545      matches: expected,
    546    });
    547  }
    548 }
    549 
    550 async function withSuggestionTypesPref(prefValue, callback) {
    551  // Use `Services` to get the original pref value since `UrlbarPrefs` will
    552  // parse the string value into a `Set`.
    553  let originalPrefValue = Services.prefs.getCharPref(
    554    "browser.urlbar.quicksuggest.dynamicSuggestionTypes"
    555  );
    556 
    557  // Changing the pref (or Nimbus variable) to a different value will trigger
    558  // ingest, so force sync afterward (or at least wait for ingest to finish).
    559  UrlbarPrefs.set("quicksuggest.dynamicSuggestionTypes", prefValue);
    560  await QuickSuggestTestUtils.forceSync();
    561 
    562  await callback();
    563 
    564  UrlbarPrefs.set("quicksuggest.dynamicSuggestionTypes", originalPrefValue);
    565  await QuickSuggestTestUtils.forceSync();
    566 }
    567 
    568 function makeExpectedResult({ rsSuggestionType, telemetryType = "exposure" }) {
    569  return {
    570    type: UrlbarUtils.RESULT_TYPE.DYNAMIC,
    571    source: UrlbarUtils.RESULT_SOURCE.SEARCH,
    572    heuristic: false,
    573    exposureTelemetry: UrlbarUtils.EXPOSURE_TELEMETRY.HIDDEN,
    574    payload: {
    575      telemetryType,
    576      rsSuggestionType,
    577      source: "rust",
    578      dynamicType: "exposure",
    579      provider: "Dynamic",
    580      suggestionType: rsSuggestionType,
    581      isSponsored: false,
    582    },
    583  };
    584 }