test_urlTelemetry.js (11385B)
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 TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", 11 sinon: "resource://testing-common/Sinon.sys.mjs", 12 }); 13 14 const TESTS = [ 15 { 16 title: "Google search access point", 17 trackingUrl: 18 "https://www.google.com/search?q=test&ie=utf-8&oe=utf-8&client=firefox-b-1-ab", 19 expectedSearchCountEntry: "google:tagged:firefox-b-1-ab", 20 expectedAdKey: "google:tagged", 21 adUrls: [ 22 "https://www.googleadservices.com/aclk=foobar", 23 "https://www.googleadservices.com/pagead/aclk=foobar", 24 "https://www.google.com/aclk=foobar", 25 "https://www.google.com/pagead/aclk=foobar", 26 ], 27 nonAdUrls: [ 28 "https://www.googleadservices.com/?aclk=foobar", 29 "https://www.googleadservices.com/bar", 30 "https://www.google.com/image", 31 ], 32 }, 33 { 34 title: "Google search access point follow-on", 35 trackingUrl: 36 "https://www.google.com/search?client=firefox-b-1-ab&ei=EI_VALUE&q=test2&oq=test2&gs_l=GS_L_VALUE", 37 expectedSearchCountEntry: "google:tagged-follow-on:firefox-b-1-ab", 38 }, 39 { 40 title: "Google organic", 41 trackingUrl: 42 "https://www.google.com/search?client=firefox-b-d-invalid&source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE", 43 expectedSearchCountEntry: "google:organic:other", 44 expectedAdKey: "google:organic", 45 adUrls: ["https://www.googleadservices.com/aclk=foobar"], 46 nonAdUrls: ["https://www.googleadservices.com/?aclk=foobar"], 47 }, 48 { 49 title: "Google organic no code", 50 trackingUrl: 51 "https://www.google.com/search?source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE", 52 expectedSearchCountEntry: "google:organic:none", 53 expectedAdKey: "google:organic", 54 adUrls: ["https://www.googleadservices.com/aclk=foobar"], 55 nonAdUrls: ["https://www.googleadservices.com/?aclk=foobar"], 56 }, 57 { 58 title: "Google organic UK", 59 trackingUrl: 60 "https://www.google.co.uk/search?source=hp&ei=EI_VALUE&q=test&oq=test&gs_l=GS_L_VALUE", 61 expectedSearchCountEntry: "google:organic:none", 62 }, 63 { 64 title: "Bing search access point", 65 trackingUrl: "https://www.bing.com/search?q=test&pc=MOZI&form=MOZLBR", 66 expectedSearchCountEntry: "bing:tagged:MOZI", 67 expectedAdKey: "bing:tagged", 68 adUrls: [ 69 "https://www.bing.com/aclick?ld=foo", 70 "https://www.bing.com/aclk?ld=foo", 71 ], 72 nonAdUrls: [ 73 "https://www.bing.com/fd/ls/ls.gif?IG=foo", 74 "https://www.bing.com/fd/ls/l?IG=bar", 75 "https://www.bing.com/aclook?", 76 "https://www.bing.com/fd/ls/GLinkPingPost.aspx?IG=baz&url=%2Fvideos%2Fsearch%3Fq%3Dfoo", 77 "https://www.bing.com/fd/ls/GLinkPingPost.aspx?IG=bar&url=https%3A%2F%2Fwww.bing.com%2Faclick", 78 "https://www.bing.com/fd/ls/GLinkPingPost.aspx?IG=bar&url=https%3A%2F%2Fwww.bing.com%2Faclk", 79 ], 80 }, 81 { 82 setUp() { 83 Services.cookies.removeAll(); 84 const cv = Services.cookies.add( 85 "www.bing.com", 86 "/", 87 "SRCHS", 88 "PC=MOZI", 89 false, 90 false, 91 false, 92 Date.now() + 1000 * 60 * 60, 93 {}, 94 Ci.nsICookie.SAMESITE_UNSET, 95 Ci.nsICookie.SCHEME_HTTPS 96 ); 97 Assert.equal(cv.result, Ci.nsICookieValidation.eOK, "Valid cookie"); 98 }, 99 tearDown() { 100 Services.cookies.removeAll(); 101 }, 102 title: "Bing search access point follow-on", 103 trackingUrl: 104 "https://www.bing.com/search?q=test&qs=n&form=QBRE&sp=-1&pq=&sc=0-0&sk=&cvid=CVID_VALUE", 105 expectedSearchCountEntry: "bing:tagged-follow-on:MOZI", 106 }, 107 { 108 title: "Bing organic", 109 trackingUrl: "https://www.bing.com/search?q=test&pc=MOZIfoo&form=MOZLBR", 110 expectedSearchCountEntry: "bing:organic:other", 111 expectedAdKey: "bing:organic", 112 adUrls: ["https://www.bing.com/aclick?ld=foo"], 113 nonAdUrls: ["https://www.bing.com/fd/ls/ls.gif?IG=foo"], 114 }, 115 { 116 title: "Bing organic no code", 117 trackingUrl: 118 "https://www.bing.com/search?q=test&qs=n&form=QBLH&sp=-1&pq=&sc=0-0&sk=&cvid=CVID_VALUE", 119 expectedSearchCountEntry: "bing:organic:none", 120 expectedAdKey: "bing:organic", 121 adUrls: ["https://www.bing.com/aclick?ld=foo"], 122 nonAdUrls: ["https://www.bing.com/fd/ls/ls.gif?IG=foo"], 123 }, 124 { 125 title: "DuckDuckGo search access point", 126 trackingUrl: "https://duckduckgo.com/?q=test&t=ffab", 127 expectedSearchCountEntry: "duckduckgo:tagged:ffab", 128 expectedAdKey: "duckduckgo:tagged", 129 adUrls: [ 130 "https://duckduckgo.com/y.js?ad_provider=foo", 131 "https://duckduckgo.com/y.js?f=bar&ad_provider=foo", 132 "https://www.amazon.co.uk/foo?tag=duckduckgo-ffab-uk-32-xk", 133 ], 134 nonAdUrls: [ 135 "https://duckduckgo.com/?q=foo&t=ffab&ia=images&iax=images", 136 "https://duckduckgo.com/y.js?ifu=foo", 137 "https://improving.duckduckgo.com/t/bar", 138 ], 139 }, 140 { 141 title: "DuckDuckGo organic", 142 trackingUrl: "https://duckduckgo.com/?q=test&t=other&ia=shopping", 143 expectedSearchCountEntry: "duckduckgo:organic:other", 144 expectedAdKey: "duckduckgo:organic", 145 adUrls: ["https://duckduckgo.com/y.js?ad_provider=foo"], 146 nonAdUrls: ["https://duckduckgo.com/?q=foo&t=ffab&ia=images&iax=images"], 147 }, 148 { 149 title: "DuckDuckGo expected organic code", 150 trackingUrl: "https://duckduckgo.com/?q=test&t=h_&ia=shopping", 151 expectedSearchCountEntry: "duckduckgo:organic:none", 152 expectedAdKey: "duckduckgo:organic", 153 adUrls: ["https://duckduckgo.com/y.js?ad_provider=foo"], 154 nonAdUrls: ["https://duckduckgo.com/?q=foo&t=ffab&ia=images&iax=images"], 155 }, 156 { 157 title: "DuckDuckGo expected organic code 2", 158 trackingUrl: "https://duckduckgo.com/?q=test&t=hz&ia=shopping", 159 expectedSearchCountEntry: "duckduckgo:organic:none", 160 expectedAdKey: "duckduckgo:organic", 161 adUrls: ["https://duckduckgo.com/y.js?ad_provider=foo"], 162 nonAdUrls: ["https://duckduckgo.com/?q=foo&t=ffab&ia=images&iax=images"], 163 }, 164 { 165 title: "DuckDuckGo organic no code", 166 trackingUrl: "https://duckduckgo.com/?q=test&ia=shopping", 167 expectedSearchCountEntry: "duckduckgo:organic:none", 168 expectedAdKey: "duckduckgo:organic", 169 adUrls: ["https://duckduckgo.com/y.js?ad_provider=foo"], 170 nonAdUrls: ["https://duckduckgo.com/?q=foo&t=ffab&ia=images&iax=images"], 171 }, 172 { 173 title: "Baidu search access point", 174 trackingUrl: "https://www.baidu.com/baidu?wd=test&tn=monline_7_dg&ie=utf-8", 175 expectedSearchCountEntry: "baidu:tagged:monline_7_dg", 176 expectedAdKey: "baidu:tagged", 177 adUrls: ["https://www.baidu.com/baidu.php?url=encoded"], 178 nonAdUrls: ["https://www.baidu.com/link?url=encoded"], 179 }, 180 { 181 title: "Baidu search access point follow-on", 182 trackingUrl: 183 "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=monline_7_dg&wd=test2&oq=test&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn&rsv_enter=1&rsv_sug3=2&rsv_sug2=0&inputT=227&rsv_sug4=397", 184 expectedSearchCountEntry: "baidu:tagged-follow-on:monline_7_dg", 185 }, 186 { 187 title: "Baidu organic", 188 trackingUrl: 189 "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&tn=baidu&bar=&wd=test&rn=&oq&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn", 190 expectedSearchCountEntry: "baidu:organic:other", 191 }, 192 { 193 title: "Baidu organic no code", 194 trackingUrl: 195 "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&ch=&bar=&wd=test&rn=&oq&rsv_pq=RSV_PQ_VALUE&rsv_t=RSV_T_VALUE&rqlang=cn", 196 expectedSearchCountEntry: "baidu:organic:none", 197 }, 198 { 199 title: "Ecosia search access point", 200 trackingUrl: "https://www.ecosia.org/search?tt=mzl&q=foo", 201 expectedSearchCountEntry: "ecosia:tagged:mzl", 202 expectedAdKey: "ecosia:tagged", 203 adUrls: ["https://www.bing.com/aclick?ld=foo"], 204 nonAdUrls: [], 205 }, 206 { 207 title: "Ecosia organic", 208 trackingUrl: "https://www.ecosia.org/search?method=index&q=foo", 209 expectedSearchCountEntry: "ecosia:organic:none", 210 expectedAdKey: "ecosia:organic", 211 adUrls: ["https://www.bing.com/aclick?ld=foo"], 212 nonAdUrls: [], 213 }, 214 ]; 215 216 /** 217 * This function is primarily for testing the Ad URL regexps that are triggered 218 * when a URL is clicked on. These regexps are also used for the `with_ads` 219 * probe. However, we test the ad_clicks route as that is easier to hit. 220 * 221 * @param {string} serpUrl 222 * The url to simulate where the page the click came from. 223 * @param {string} adUrl 224 * The ad url to simulate being clicked. 225 * @param {string} [expectedAdKey] 226 * The expected key to be logged for the scalar. Omit if no scalar should be 227 * logged. 228 */ 229 async function testAdUrlClicked(serpUrl, adUrl, expectedAdKey) { 230 info(`Testing Ad URL: ${adUrl}`); 231 let channel = NetUtil.newChannel({ 232 uri: NetUtil.newURI(adUrl), 233 triggeringPrincipal: Services.scriptSecurityManager.createContentPrincipal( 234 NetUtil.newURI(serpUrl), 235 {} 236 ), 237 loadUsingSystemPrincipal: true, 238 }); 239 SearchSERPTelemetry._contentHandler.observeActivity( 240 channel, 241 Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION, 242 Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE 243 ); 244 // Since the content handler takes a moment to allow the channel information 245 // to settle down, wait the same amount of time here. 246 await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); 247 248 const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); 249 if (!expectedAdKey) { 250 Assert.ok( 251 !("browser.search.adclicks.unknown" in scalars), 252 "Should not have recorded an ad click" 253 ); 254 } else { 255 TelemetryTestUtils.assertKeyedScalar( 256 scalars, 257 "browser.search.adclicks.unknown", 258 expectedAdKey, 259 1 260 ); 261 } 262 } 263 264 do_get_profile(); 265 266 add_setup(async function () { 267 await SearchSERPTelemetry.init(); 268 sinon.stub(BrowserSearchTelemetry, "shouldRecordSearchCount").returns(true); 269 270 registerCleanupFunction(async () => { 271 sinon.restore(); 272 }); 273 }); 274 275 add_task(async function test_parsing_search_urls() { 276 for (const test of TESTS) { 277 info(`Running ${test.title}`); 278 if (test.setUp) { 279 test.setUp(); 280 } 281 SearchSERPTelemetry.updateTrackingStatus( 282 { 283 getTabBrowser: () => {}, 284 // There is no concept of browsing in unit tests, so assume in tests that we 285 // are not in private browsing mode. We have browser tests that check when 286 // private browsing is used. 287 contentPrincipal: { 288 originAttributes: { 289 privateBrowsingId: 0, 290 }, 291 }, 292 }, 293 test.trackingUrl 294 ); 295 let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); 296 TelemetryTestUtils.assertKeyedScalar( 297 scalars, 298 "browser.search.content.unknown", 299 test.expectedSearchCountEntry, 300 1 301 ); 302 303 if ("adUrls" in test) { 304 for (const adUrl of test.adUrls) { 305 await testAdUrlClicked(test.trackingUrl, adUrl, test.expectedAdKey); 306 } 307 for (const nonAdUrls of test.nonAdUrls) { 308 await testAdUrlClicked(test.trackingUrl, nonAdUrls); 309 } 310 } 311 312 if (test.tearDown) { 313 test.tearDown(); 314 } 315 } 316 });