tor-browser

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

test_urlTelemetry_generic.js (16575B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 ChromeUtils.defineESModuleGetters(this, {
      5  BrowserSearchTelemetry:
      6    "moz-src:///browser/components/search/BrowserSearchTelemetry.sys.mjs",
      7  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
      8  SearchSERPTelemetry:
      9    "moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
     10  SearchSERPTelemetryUtils:
     11    "moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
     12  TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
     13  sinon: "resource://testing-common/Sinon.sys.mjs",
     14 });
     15 
     16 const TEST_PROVIDER_INFO = [
     17  {
     18    telemetryId: "example",
     19    searchPageRegexp: /^https:\/\/www\.example\.com\/search/,
     20    queryParamNames: ["q"],
     21    codeParamName: "abc",
     22    taggedCodes: ["ff", "tb"],
     23    expectedOrganicCodes: ["baz"],
     24    organicCodes: ["foo"],
     25    followOnParamNames: ["a"],
     26    extraAdServersRegexps: [/^https:\/\/www\.example\.com\/ad2/],
     27    shoppingTab: {
     28      regexp: "&site=shop",
     29    },
     30    searchMode: {
     31      mode: "image_search",
     32    },
     33    components: [
     34      {
     35        type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
     36        default: true,
     37      },
     38    ],
     39  },
     40  {
     41    telemetryId: "example2",
     42    searchPageRegexp: /^https:\/\/www\.example2\.com\/search/,
     43    queryParamNames: ["a", "q"],
     44    codeParamName: "abc",
     45    taggedCodes: ["ff", "tb"],
     46    expectedOrganicCodes: ["baz"],
     47    organicCodes: ["foo"],
     48    followOnParamNames: ["a"],
     49    extraAdServersRegexps: [/^https:\/\/www\.example\.com\/ad2/],
     50    components: [
     51      {
     52        type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
     53        default: true,
     54      },
     55    ],
     56  },
     57  {
     58    telemetryId: "example3",
     59    searchPageRegexp: /^https:\/\/www\.example3\.com\/search/,
     60    queryParamNames: ["a", "q"],
     61    codeParamName: "abc",
     62    taggedCodes: ["ff", "tb"],
     63    expectedOrganicCodes: ["baz"],
     64    organicCodes: ["foo"],
     65    followOnParamNames: ["a"],
     66    followOnCookies: [
     67      {
     68        host: "www.example3.com",
     69        name: "_dummyCookieName",
     70        codeParamName: "abc",
     71        extraCodePrefixes: ["xyz"],
     72        extraCodeParamName: "dummyExtraCodeParamName",
     73      },
     74    ],
     75    extraAdServersRegexps: [/^https:\/\/www\.example\.com\/ad2/],
     76    components: [
     77      {
     78        type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
     79        default: true,
     80      },
     81    ],
     82  },
     83  {
     84    telemetryId: "example4",
     85    searchPageRegexp: /^https:\/\/www\.example4\.com\/search/,
     86    queryParamNames: ["a", "q"],
     87    codeParamName: "abc",
     88    taggedCodes: ["ff", "tb"],
     89    expectedOrganicCodes: ["baz"],
     90    organicCodes: ["foo"],
     91    followOnParamNames: ["a"],
     92    followOnCookies: [
     93      {
     94        host: "www.example4.com",
     95        name: "_dummyCookieName",
     96        codeParamName: "abc",
     97        extraCodePrefixes: ["xyz"],
     98        extraCodeParamName: "dummyExtraCodeParamName",
     99      },
    100    ],
    101    extraAdServersRegexps: [/^https:\/\/www\.example\.com\/ad2/],
    102    components: [
    103      {
    104        type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
    105        default: true,
    106      },
    107    ],
    108  },
    109  {
    110    telemetryId: "example5",
    111    searchPageRegexp: /^https:\/\/www\.example5\.com\/search/,
    112    queryParamNames: ["a", "q"],
    113    codeParamName: "abc",
    114    taggedCodes: ["ff", "tb"],
    115    expectedOrganicCodes: ["baz"],
    116    organicCodes: ["foo"],
    117    followOnParamNames: ["a"],
    118    followOnCookies: [
    119      {
    120        host: "www.example5.com",
    121        name: "_dummyCookieName",
    122        codeParamName: "abc",
    123        // No required extra code param/prefixes.
    124        extraCodePrefixes: [],
    125        extraCodeParamName: "",
    126      },
    127    ],
    128    extraAdServersRegexps: [/^https:\/\/www\.example\.com\/ad2/],
    129    components: [
    130      {
    131        type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
    132        default: true,
    133      },
    134    ],
    135  },
    136 ];
    137 
    138 const TESTS = [
    139  {
    140    title: "Tagged search",
    141    trackingUrl: "https://www.example.com/search?q=test&abc=ff",
    142    expectedSearchCountEntry: "example:tagged:ff",
    143    expectedAdKey: "example:tagged",
    144    adUrls: ["https://www.example.com/ad2"],
    145    nonAdUrls: ["https://www.example.com/ad3"],
    146    impression: {
    147      provider: "example",
    148      tagged: "true",
    149      partner_code: "ff",
    150      source: "unknown",
    151      is_shopping_page: "false",
    152      is_private: "false",
    153      shopping_tab_displayed: "false",
    154      is_signed_in: "false",
    155    },
    156  },
    157  {
    158    title: "Tagged search with shopping",
    159    trackingUrl: "https://www.example.com/search?q=test&abc=ff&site=shop",
    160    expectedSearchCountEntry: "example:tagged:ff",
    161    expectedAdKey: "example:tagged",
    162    adUrls: ["https://www.example.com/ad2"],
    163    nonAdUrls: ["https://www.example.com/ad3"],
    164    impression: {
    165      provider: "example",
    166      tagged: "true",
    167      partner_code: "ff",
    168      source: "unknown",
    169      is_shopping_page: "true",
    170      is_private: "false",
    171      shopping_tab_displayed: "false",
    172      is_signed_in: "false",
    173    },
    174  },
    175  {
    176    title: "Tagged image search",
    177    trackingUrl: "https://www.example.com/search?q=test&abc=ff&mode=image",
    178    expectedSearchCountEntry: "example:tagged:ff",
    179    expectedAdKey: "example:tagged",
    180    adUrls: ["https://www.example.com/ad2"],
    181    nonAdUrls: ["https://www.example.com/ad3"],
    182    impression: {
    183      provider: "example",
    184      tagged: "true",
    185      partner_code: "ff",
    186      search_mode: "image_search",
    187      source: "unknown",
    188      is_shopping_page: "false",
    189      is_private: "false",
    190      shopping_tab_displayed: "false",
    191      is_signed_in: "false",
    192    },
    193  },
    194  {
    195    title: "Tagged follow-on",
    196    trackingUrl: "https://www.example.com/search?q=test&abc=tb&a=next",
    197    expectedSearchCountEntry: "example:tagged-follow-on:tb",
    198    expectedAdKey: "example:tagged-follow-on",
    199    adUrls: ["https://www.example.com/ad2"],
    200    nonAdUrls: ["https://www.example.com/ad3"],
    201    impression: {
    202      provider: "example",
    203      tagged: "true",
    204      partner_code: "tb",
    205      source: "unknown",
    206      is_shopping_page: "false",
    207      is_private: "false",
    208      shopping_tab_displayed: "false",
    209      is_signed_in: "false",
    210    },
    211  },
    212  {
    213    setUp() {
    214      Services.cookies.removeAll();
    215      const cv = Services.cookies.add(
    216        "www.example3.com",
    217        "/",
    218        "_dummyCookieName",
    219        "abc=tb&def=ghi",
    220        false,
    221        false,
    222        false,
    223        Date.now() + 10 * 60 * 60 * 1000,
    224        {},
    225        Ci.nsICookie.SAMESITE_UNSET,
    226        Ci.nsICookie.SCHEME_HTTPS
    227      );
    228      Assert.equal(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie");
    229    },
    230    tearDown() {
    231      Services.cookies.removeAll();
    232    },
    233    title: "Tagged follow-on with cookie",
    234    trackingUrl:
    235      "https://www.example3.com/search?q=test&a=next&dummyExtraCodeParamName=xyz",
    236    expectedSearchCountEntry: "example3:tagged-follow-on:tb",
    237    expectedAdKey: "example3:tagged-follow-on",
    238    adUrls: ["https://www.example.com/ad2"],
    239    nonAdUrls: ["https://www.example.com/ad3"],
    240    impression: {
    241      provider: "example3",
    242      tagged: "true",
    243      partner_code: "tb",
    244      source: "unknown",
    245      is_shopping_page: "false",
    246      is_private: "false",
    247      shopping_tab_displayed: "false",
    248      is_signed_in: "false",
    249    },
    250  },
    251  {
    252    setUp() {
    253      Services.cookies.removeAll();
    254      const cv = Services.cookies.add(
    255        "www.example4.com",
    256        "/",
    257        "_dummyCookieName",
    258        "abc=tb&def=ghi",
    259        false,
    260        false,
    261        false,
    262        Date.now() + 10 * 60 * 60 * 1000,
    263        {},
    264        Ci.nsICookie.SAMESITE_UNSET,
    265        Ci.nsICookie.SCHEME_HTTPS
    266      );
    267      Assert.equal(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie");
    268    },
    269    tearDown() {
    270      Services.cookies.removeAll();
    271    },
    272    title:
    273      "Tagged follow-on with cookie and unexpected extraCodeParam casing in URL",
    274    trackingUrl:
    275      "https://www.example4.com/search?q=test&a=next&DUMMYEXTRACODEPARAMNAME=xyz",
    276    expectedSearchCountEntry: "example4:tagged-follow-on:tb",
    277    expectedAdKey: "example4:tagged-follow-on",
    278    adUrls: ["https://www.example.com/ad2"],
    279    nonAdUrls: ["https://www.example.com/ad3"],
    280    impression: {
    281      provider: "example4",
    282      tagged: "true",
    283      partner_code: "tb",
    284      source: "unknown",
    285      is_shopping_page: "false",
    286      is_private: "false",
    287      shopping_tab_displayed: "false",
    288      is_signed_in: "false",
    289    },
    290  },
    291  {
    292    setUp() {
    293      Services.cookies.removeAll();
    294      Services.cookies.add(
    295        "www.example5.com",
    296        "/",
    297        "_dummyCookieName",
    298        "abc=tb&def=ghi",
    299        false,
    300        false,
    301        false,
    302        Date.now() + 10 * 60 * 60 * 1000,
    303        {},
    304        Ci.nsICookie.SAMESITE_UNSET,
    305        Ci.nsICookie.SCHEME_HTTPS
    306      );
    307    },
    308    tearDown() {
    309      Services.cookies.removeAll();
    310    },
    311    title: "Tagged follow-on with cookie and no required url param",
    312    trackingUrl: "https://www.example5.com/search?q=test&a=next",
    313    expectedSearchCountEntry: "example5:tagged-follow-on:tb",
    314    expectedAdKey: "example5:tagged-follow-on",
    315    adUrls: ["https://www.example.com/ad2"],
    316    nonAdUrls: ["https://www.example.com/ad3"],
    317    impression: {
    318      provider: "example5",
    319      tagged: "true",
    320      partner_code: "tb",
    321      source: "unknown",
    322      is_shopping_page: "false",
    323      is_private: "false",
    324      shopping_tab_displayed: "false",
    325      is_signed_in: "false",
    326    },
    327  },
    328  {
    329    title: "Organic search matched code",
    330    trackingUrl: "https://www.example.com/search?q=test&abc=foo",
    331    expectedSearchCountEntry: "example:organic:foo",
    332    expectedAdKey: "example:organic",
    333    adUrls: ["https://www.example.com/ad2"],
    334    nonAdUrls: ["https://www.example.com/ad3"],
    335    impression: {
    336      provider: "example",
    337      tagged: "false",
    338      partner_code: "foo",
    339      source: "unknown",
    340      is_shopping_page: "false",
    341      is_private: "false",
    342      shopping_tab_displayed: "false",
    343      is_signed_in: "false",
    344    },
    345  },
    346  {
    347    title: "Organic search non-matched code",
    348    trackingUrl: "https://www.example.com/search?q=test&abc=ff123",
    349    expectedSearchCountEntry: "example:organic:other",
    350    expectedAdKey: "example:organic",
    351    adUrls: ["https://www.example.com/ad2"],
    352    nonAdUrls: ["https://www.example.com/ad3"],
    353    impression: {
    354      provider: "example",
    355      tagged: "false",
    356      partner_code: "other",
    357      source: "unknown",
    358      is_shopping_page: "false",
    359      is_private: "false",
    360      shopping_tab_displayed: "false",
    361      is_signed_in: "false",
    362    },
    363  },
    364  {
    365    title: "Organic search non-matched code 2",
    366    trackingUrl: "https://www.example.com/search?q=test&abc=foo123",
    367    expectedSearchCountEntry: "example:organic:other",
    368    expectedAdKey: "example:organic",
    369    adUrls: ["https://www.example.com/ad2"],
    370    nonAdUrls: ["https://www.example.com/ad3"],
    371    impression: {
    372      provider: "example",
    373      tagged: "false",
    374      partner_code: "other",
    375      source: "unknown",
    376      is_shopping_page: "false",
    377      is_private: "false",
    378      shopping_tab_displayed: "false",
    379      is_signed_in: "false",
    380    },
    381  },
    382  {
    383    title: "Organic search expected organic matched code",
    384    trackingUrl: "https://www.example.com/search?q=test&abc=baz",
    385    expectedSearchCountEntry: "example:organic:none",
    386    expectedAdKey: "example:organic",
    387    adUrls: ["https://www.example.com/ad2"],
    388    nonAdUrls: ["https://www.example.com/ad3"],
    389    impression: {
    390      provider: "example",
    391      tagged: "false",
    392      partner_code: "",
    393      source: "unknown",
    394      is_shopping_page: "false",
    395      is_private: "false",
    396      shopping_tab_displayed: "false",
    397      is_signed_in: "false",
    398    },
    399  },
    400  {
    401    title: "Organic search no codes",
    402    trackingUrl: "https://www.example.com/search?q=test",
    403    expectedSearchCountEntry: "example:organic:none",
    404    expectedAdKey: "example:organic",
    405    adUrls: ["https://www.example.com/ad2"],
    406    nonAdUrls: ["https://www.example.com/ad3"],
    407    impression: {
    408      provider: "example",
    409      tagged: "false",
    410      partner_code: "",
    411      source: "unknown",
    412      is_shopping_page: "false",
    413      is_private: "false",
    414      shopping_tab_displayed: "false",
    415      is_signed_in: "false",
    416    },
    417  },
    418  {
    419    title: "Different engines using the same adUrl",
    420    trackingUrl: "https://www.example2.com/search?q=test",
    421    expectedSearchCountEntry: "example2:organic:none",
    422    expectedAdKey: "example2:organic",
    423    adUrls: ["https://www.example.com/ad2"],
    424    nonAdUrls: ["https://www.example.com/ad3"],
    425    impression: {
    426      provider: "example2",
    427      tagged: "false",
    428      partner_code: "",
    429      source: "unknown",
    430      is_shopping_page: "false",
    431      is_private: "false",
    432      shopping_tab_displayed: "false",
    433      is_signed_in: "false",
    434    },
    435  },
    436 ];
    437 
    438 /**
    439 * This function is primarily for testing the Ad URL regexps that are triggered
    440 * when a URL is clicked on. These regexps are also used for the `withads`
    441 * probe. However, we test the adclicks route as that is easier to hit.
    442 *
    443 * @param {string} serpUrl
    444 *   The url to simulate where the page the click came from.
    445 * @param {string} adUrl
    446 *   The ad url to simulate being clicked.
    447 * @param {string} [expectedAdKey]
    448 *   The expected key to be logged for the scalar. Omit if no scalar should be
    449 *   logged.
    450 */
    451 async function testAdUrlClicked(serpUrl, adUrl, expectedAdKey) {
    452  info(`Testing Ad URL: ${adUrl}`);
    453  let channel = NetUtil.newChannel({
    454    uri: NetUtil.newURI(adUrl),
    455    triggeringPrincipal: Services.scriptSecurityManager.createContentPrincipal(
    456      NetUtil.newURI(serpUrl),
    457      {}
    458    ),
    459    loadUsingSystemPrincipal: true,
    460  });
    461  SearchSERPTelemetry._contentHandler.observeActivity(
    462    channel,
    463    Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION,
    464    Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
    465  );
    466  // Since the content handler takes a moment to allow the channel information
    467  // to settle down, wait the same amount of time here.
    468  await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
    469 
    470  const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
    471  if (!expectedAdKey) {
    472    Assert.ok(
    473      !("browser.search.adclicks.unknown" in scalars),
    474      "Should not have recorded an ad click"
    475    );
    476  } else {
    477    TelemetryTestUtils.assertKeyedScalar(
    478      scalars,
    479      "browser.search.adclicks.unknown",
    480      expectedAdKey,
    481      1
    482    );
    483  }
    484 }
    485 
    486 do_get_profile();
    487 
    488 add_setup(async function () {
    489  Services.fog.initializeFOG();
    490  await SearchSERPTelemetry.init();
    491  SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO);
    492  sinon.stub(BrowserSearchTelemetry, "shouldRecordSearchCount").returns(true);
    493 
    494  registerCleanupFunction(async () => {
    495    sinon.restore();
    496  });
    497 });
    498 
    499 add_task(async function test_parsing_search_urls() {
    500  for (const test of TESTS) {
    501    info(`Running ${test.title}`);
    502    if (test.setUp) {
    503      test.setUp();
    504    }
    505    let browser = {
    506      getTabBrowser: () => {},
    507      // There is no concept of browsing in unit tests, so assume in tests that we
    508      // are not in private browsing mode. We have browser tests that check when
    509      // private browsing is used.
    510      contentPrincipal: {
    511        originAttributes: {
    512          privateBrowsingId: 0,
    513        },
    514      },
    515    };
    516    SearchSERPTelemetry.updateTrackingStatus(browser, test.trackingUrl);
    517    SearchSERPTelemetry.reportPageImpression(
    518      {
    519        url: test.trackingUrl,
    520        shoppingTabDisplayed: false,
    521      },
    522      browser
    523    );
    524    let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
    525    TelemetryTestUtils.assertKeyedScalar(
    526      scalars,
    527      "browser.search.content.unknown",
    528      test.expectedSearchCountEntry,
    529      1
    530    );
    531 
    532    if ("adUrls" in test) {
    533      for (const adUrl of test.adUrls) {
    534        await testAdUrlClicked(test.trackingUrl, adUrl, test.expectedAdKey);
    535      }
    536      for (const nonAdUrls of test.nonAdUrls) {
    537        await testAdUrlClicked(test.trackingUrl, nonAdUrls);
    538      }
    539    }
    540 
    541    let recordedEvents = Glean.serp.impression.testGetValue();
    542 
    543    Assert.equal(
    544      recordedEvents.length,
    545      1,
    546      "should only see one impression event"
    547    );
    548 
    549    // To allow deep equality.
    550    test.impression.impression_id = recordedEvents[0].extra.impression_id;
    551    Assert.deepEqual(recordedEvents[0].extra, test.impression);
    552 
    553    if (test.tearDown) {
    554      test.tearDown();
    555    }
    556 
    557    // We need to clear Glean events so they don't accumulate for each iteration.
    558    Services.fog.testResetFOG();
    559  }
    560 });