test_TelemetryFeed.js (83046B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { updateAppInfo } = ChromeUtils.importESModule( 7 "resource://testing-common/AppInfo.sys.mjs" 8 ); 9 10 ChromeUtils.defineESModuleGetters(this, { 11 AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs", 12 ContextId: "moz-src:///browser/modules/ContextId.sys.mjs", 13 actionCreators: "resource://newtab/common/Actions.mjs", 14 actionTypes: "resource://newtab/common/Actions.mjs", 15 ExtensionSettingsStore: 16 "resource://gre/modules/ExtensionSettingsStore.sys.mjs", 17 HomePage: "resource:///modules/HomePage.sys.mjs", 18 JsonSchemaValidator: 19 "resource://gre/modules/components-utils/JsonSchemaValidator.sys.mjs", 20 NewTabContentPing: "resource://newtab/lib/NewTabContentPing.sys.mjs", 21 sinon: "resource://testing-common/Sinon.sys.mjs", 22 TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs", 23 TelemetryFeed: "resource://newtab/lib/TelemetryFeed.sys.mjs", 24 TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", 25 USER_PREFS_ENCODING: "resource://newtab/lib/TelemetryFeed.sys.mjs", 26 UTEventReporting: "resource://newtab/lib/UTEventReporting.sys.mjs", 27 }); 28 29 const FAKE_UUID = "{foo-123-foo}"; 30 const PREF_IMPRESSION_ID = "browser.newtabpage.activity-stream.impressionId"; 31 const PREF_TELEMETRY = "browser.newtabpage.activity-stream.telemetry"; 32 const PREF_PRIVATE_PING_ENABLED = 33 "browser.newtabpage.activity-stream.telemetry.privatePing.enabled"; 34 const PREF_REDACT_NEWTAB_PING_ENABLED = 35 "browser.newtabpage.activity-stream.telemetry.privatePing.redactNewtabPing.enabled"; 36 const PREF_EVENT_TELEMETRY = 37 "browser.newtabpage.activity-stream.telemetry.ut.events"; 38 39 let BasePingSchemaPromise; 40 let SessionPingSchemaPromise; 41 let UserEventPingSchemaPromise; 42 43 function assertPingMatchesSchema(pingKind, ping, schema) { 44 // Unlike the validator from JsonSchema.sys.mjs, JsonSchemaValidator 45 // lets us opt-in to having "undefined" properties, which are then 46 // ignored. This is fine because the ping is sent as a JSON string 47 // over an XHR, and undefined properties are culled as part of the 48 // JSON encoding process. 49 let result = JsonSchemaValidator.validate(ping, schema, { 50 allowExplicitUndefinedProperties: true, 51 }); 52 53 if (!result.valid) { 54 info(`${pingKind} failed to validate against the schema: ${result.error}`); 55 } 56 57 Assert.ok(result.valid, `${pingKind} is valid against the schema.`); 58 } 59 60 async function assertSessionPingValid(ping) { 61 let schema = await SessionPingSchemaPromise; 62 assertPingMatchesSchema("SessionPing", ping, schema); 63 } 64 65 async function assertBasePingValid(ping) { 66 let schema = await BasePingSchemaPromise; 67 assertPingMatchesSchema("BasePing", ping, schema); 68 } 69 70 async function assertUserEventPingValid(ping) { 71 let schema = await UserEventPingSchemaPromise; 72 assertPingMatchesSchema("UserEventPing", ping, schema); 73 } 74 75 add_setup(async function setup() { 76 BasePingSchemaPromise = IOUtils.readJSON( 77 do_get_file("../schemas/base_ping.schema.json").path 78 ); 79 80 SessionPingSchemaPromise = IOUtils.readJSON( 81 do_get_file("../schemas/session_ping.schema.json").path 82 ); 83 84 UserEventPingSchemaPromise = IOUtils.readJSON( 85 do_get_file("../schemas/user_event_ping.schema.json").path 86 ); 87 88 do_get_profile(); 89 // FOG needs to be initialized in order for data to flow. 90 Services.fog.initializeFOG(); 91 92 await TelemetryController.testReset(); 93 94 updateAppInfo({ 95 name: "XPCShell", 96 ID: "xpcshell@tests.mozilla.org", 97 version: "122", 98 platformVersion: "122", 99 }); 100 101 let sandbox = sinon.createSandbox(); 102 sandbox.stub(ContextId, "requestSynchronously").returns(FAKE_UUID); 103 104 registerCleanupFunction(() => { 105 sandbox.restore(); 106 }); 107 }); 108 109 add_task(async function test_construction() { 110 let testInstance = new TelemetryFeed(); 111 Assert.ok( 112 testInstance, 113 "Should have been able to create an instance of TelemetryFeed." 114 ); 115 Assert.ok( 116 testInstance.utEvents instanceof UTEventReporting, 117 "Should add .utEvents, a UTEventReporting instance." 118 ); 119 Assert.ok( 120 testInstance._impressionId, 121 "Should create impression id if none exists" 122 ); 123 }); 124 125 add_task(async function test_load_impressionId() { 126 info( 127 "Constructing a TelemetryFeed should use a saved impression ID if one exists." 128 ); 129 const FAKE_IMPRESSION_ID = "{some-fake-impression-ID}"; 130 const IMPRESSION_PREF = "browser.newtabpage.activity-stream.impressionId"; 131 Services.prefs.setCharPref(IMPRESSION_PREF, FAKE_IMPRESSION_ID); 132 Assert.equal(new TelemetryFeed()._impressionId, FAKE_IMPRESSION_ID); 133 Services.prefs.clearUserPref(IMPRESSION_PREF); 134 }); 135 136 add_task(async function test_init() { 137 info( 138 "init should make this.browserOpenNewtabStart() observe browser-open-newtab-start" 139 ); 140 let sandbox = sinon.createSandbox(); 141 sandbox 142 .stub(TelemetryFeed.prototype, "getOrCreateImpressionId") 143 .returns(FAKE_UUID); 144 145 let instance = new TelemetryFeed(); 146 sandbox.stub(instance, "browserOpenNewtabStart"); 147 instance.init(); 148 149 Services.obs.notifyObservers(null, "browser-open-newtab-start"); 150 Assert.ok( 151 instance.browserOpenNewtabStart.calledOnce, 152 "browserOpenNewtabStart called once." 153 ); 154 155 info("init should create impression id if none exists"); 156 Assert.equal(instance._impressionId, FAKE_UUID); 157 158 instance.uninit(); 159 sandbox.restore(); 160 }); 161 162 add_task(async function test_saved_impression_id() { 163 const FAKE_IMPRESSION_ID = "fakeImpressionId"; 164 Services.prefs.setCharPref(PREF_IMPRESSION_ID, FAKE_IMPRESSION_ID); 165 Assert.equal(new TelemetryFeed()._impressionId, FAKE_IMPRESSION_ID); 166 Services.prefs.clearUserPref(PREF_IMPRESSION_ID); 167 }); 168 169 add_task(async function test_telemetry_prefs() { 170 info("Telemetry pref changes from false to true"); 171 Services.prefs.setBoolPref(PREF_TELEMETRY, false); 172 let instance = new TelemetryFeed(); 173 Assert.ok(!instance.telemetryEnabled, "Telemetry disabled"); 174 175 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 176 Assert.ok(instance.telemetryEnabled, "Telemetry enabled"); 177 178 info("Event telemetry pref changes from false to true"); 179 180 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, false); 181 Assert.ok(!instance.eventTelemetryEnabled, "Event telemetry disabled"); 182 183 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, true); 184 Assert.ok(instance.eventTelemetryEnabled, "Event telemetry enabled"); 185 186 Services.prefs.clearUserPref(PREF_EVENT_TELEMETRY); 187 Services.prefs.clearUserPref(PREF_TELEMETRY); 188 }); 189 190 add_task(async function test_deletionRequest_scalars() { 191 info("TelemetryFeed.init should set two scalars for deletion-request"); 192 193 Services.telemetry.clearScalars(); 194 let instance = new TelemetryFeed(); 195 instance.init(); 196 197 let snapshot = Services.telemetry.getSnapshotForScalars( 198 "deletion-request", 199 false 200 ).parent; 201 TelemetryTestUtils.assertScalar( 202 snapshot, 203 "deletion.request.impression_id", 204 instance._impressionId 205 ); 206 207 // We'll only set the context_id in the deletion request if rotation is 208 // disabled. 209 if (!ContextId.rotationEnabled) { 210 TelemetryTestUtils.assertScalar( 211 snapshot, 212 "deletion.request.context_id", 213 FAKE_UUID 214 ); 215 } 216 217 instance.uninit(); 218 }); 219 220 add_task(async function test_metrics_on_initialization() { 221 info("TelemetryFeed.init should record initial metrics from newtab prefs"); 222 Services.fog.testResetFOG(); 223 const ENABLED_SETTING = true; 224 const TOP_SITES_ROWS = 3; 225 const BLOCKED_SPONSORS = ["mozilla"]; 226 227 Services.prefs.setBoolPref( 228 "browser.newtabpage.activity-stream.feeds.topsites", 229 ENABLED_SETTING 230 ); 231 Services.prefs.setIntPref( 232 "browser.newtabpage.activity-stream.topSitesRows", 233 TOP_SITES_ROWS 234 ); 235 Services.prefs.setCharPref( 236 "browser.topsites.blockedSponsors", 237 JSON.stringify(BLOCKED_SPONSORS) 238 ); 239 240 let instance = new TelemetryFeed(); 241 instance.init(); 242 243 Assert.equal(Glean.topsites.enabled.testGetValue(), ENABLED_SETTING); 244 Assert.equal(Glean.topsites.rows.testGetValue(), TOP_SITES_ROWS); 245 Assert.deepEqual( 246 Glean.newtab.blockedSponsors.testGetValue(), 247 BLOCKED_SPONSORS 248 ); 249 250 instance.uninit(); 251 252 Services.prefs.clearUserPref( 253 "browser.newtabpage.activity-stream.feeds.topsites" 254 ); 255 Services.prefs.clearUserPref( 256 "browser.newtabpage.activity-stream.topSitesRows" 257 ); 258 Services.prefs.clearUserPref("browser.topsites.blockedSponsors"); 259 }); 260 261 add_task(async function test_metrics_with_bad_json() { 262 info( 263 "TelemetryFeed.init should not record blocked sponsor metrics when " + 264 "bad json string is passed" 265 ); 266 Services.fog.testResetFOG(); 267 Services.prefs.setCharPref("browser.topsites.blockedSponsors", "BAD[JSON]"); 268 269 let instance = new TelemetryFeed(); 270 instance.init(); 271 272 Assert.equal(Glean.newtab.blockedSponsors.testGetValue(), null); 273 274 instance.uninit(); 275 276 Services.prefs.clearUserPref("browser.topsites.blockedSponsors"); 277 }); 278 279 add_task(async function test_metrics_on_pref_changes() { 280 info("TelemetryFeed.init should record new metrics for newtab pref changes"); 281 const INITIAL_TOP_SITES_ROWS = 3; 282 const INITIAL_BLOCKED_SPONSORS = []; 283 Services.fog.testResetFOG(); 284 Services.prefs.setIntPref( 285 "browser.newtabpage.activity-stream.topSitesRows", 286 INITIAL_TOP_SITES_ROWS 287 ); 288 Services.prefs.setCharPref( 289 "browser.topsites.blockedSponsors", 290 JSON.stringify(INITIAL_BLOCKED_SPONSORS) 291 ); 292 293 let instance = new TelemetryFeed(); 294 instance.init(); 295 296 Assert.equal(Glean.topsites.rows.testGetValue(), INITIAL_TOP_SITES_ROWS); 297 Assert.deepEqual( 298 Glean.newtab.blockedSponsors.testGetValue(), 299 INITIAL_BLOCKED_SPONSORS 300 ); 301 302 const NEXT_TOP_SITES_ROWS = 2; 303 const NEXT_BLOCKED_SPONSORS = ["mozilla"]; 304 305 Services.prefs.setIntPref( 306 "browser.newtabpage.activity-stream.topSitesRows", 307 NEXT_TOP_SITES_ROWS 308 ); 309 310 Services.prefs.setStringPref( 311 "browser.topsites.blockedSponsors", 312 JSON.stringify(NEXT_BLOCKED_SPONSORS) 313 ); 314 315 Assert.equal(Glean.topsites.rows.testGetValue(), NEXT_TOP_SITES_ROWS); 316 Assert.deepEqual( 317 Glean.newtab.blockedSponsors.testGetValue(), 318 NEXT_BLOCKED_SPONSORS 319 ); 320 321 instance.uninit(); 322 323 Services.prefs.clearUserPref( 324 "browser.newtabpage.activity-stream.topSitesRows" 325 ); 326 Services.prefs.clearUserPref("browser.topsites.blockedSponsors"); 327 }); 328 329 add_task(async function test_events_on_pref_changes() { 330 info("TelemetryFeed.init should record events for some newtab pref changes"); 331 // We only record events for browser.newtabpage.activity-stream.feeds.topsites and 332 // browser.newtabpage.activity-stream.showSponsoredTopSites being changed. 333 const INITIAL_TOPSITES_ENABLED = false; 334 const INITIAL_SHOW_SPONSORED_TOP_SITES = true; 335 Services.fog.testResetFOG(); 336 Services.prefs.setBoolPref( 337 "browser.newtabpage.activity-stream.feeds.topsites", 338 INITIAL_TOPSITES_ENABLED 339 ); 340 Services.prefs.setBoolPref( 341 "browser.newtabpage.activity-stream.showSponsoredTopSites", 342 INITIAL_SHOW_SPONSORED_TOP_SITES 343 ); 344 345 let instance = new TelemetryFeed(); 346 instance.init(); 347 348 const NEXT_TOPSITES_ENABLED = true; 349 const NEXT_SHOW_SPONSORED_TOP_SITES = false; 350 351 Services.prefs.setBoolPref( 352 "browser.newtabpage.activity-stream.feeds.topsites", 353 NEXT_TOPSITES_ENABLED 354 ); 355 Services.prefs.setBoolPref( 356 "browser.newtabpage.activity-stream.showSponsoredTopSites", 357 NEXT_SHOW_SPONSORED_TOP_SITES 358 ); 359 360 let prefChangeEvents = Glean.topsites.prefChanged.testGetValue(); 361 Assert.deepEqual(prefChangeEvents[0].extra, { 362 pref_name: "browser.newtabpage.activity-stream.feeds.topsites", 363 new_value: String(NEXT_TOPSITES_ENABLED), 364 }); 365 Assert.deepEqual(prefChangeEvents[1].extra, { 366 pref_name: "browser.newtabpage.activity-stream.showSponsoredTopSites", 367 new_value: String(NEXT_SHOW_SPONSORED_TOP_SITES), 368 }); 369 370 instance.uninit(); 371 372 Services.prefs.clearUserPref( 373 "browser.newtabpage.activity-stream.feeds.topsites" 374 ); 375 Services.prefs.clearUserPref( 376 "browser.newtabpage.activity-stream.showSponsoredTopSites" 377 ); 378 }); 379 380 add_task(async function test_browserOpenNewtabStart() { 381 info( 382 "TelemetryFeed.browserOpenNewtabStart should call " + 383 "ChromeUtils.addProfilerMarker with browser-open-newtab-start" 384 ); 385 386 let instance = new TelemetryFeed(); 387 388 let entries = 10000; 389 let interval = 1; 390 let threads = ["GeckoMain"]; 391 let features = []; 392 await Services.profiler.StartProfiler(entries, interval, features, threads); 393 instance.browserOpenNewtabStart(); 394 395 let profileArrayBuffer = 396 await Services.profiler.getProfileDataAsArrayBuffer(); 397 await Services.profiler.StopProfiler(); 398 399 let profileUint8Array = new Uint8Array(profileArrayBuffer); 400 let textDecoder = new TextDecoder("utf-8", { fatal: true }); 401 let profileString = textDecoder.decode(profileUint8Array); 402 let profile = JSON.parse(profileString); 403 Assert.ok(profile.threads); 404 Assert.equal(profile.threads.length, 1); 405 406 let foundMarker = profile.threads[0].markers.data.find(marker => { 407 return marker[5]?.name === "browser-open-newtab-start"; 408 }); 409 410 Assert.ok(foundMarker, "Found the browser-open-newtab-start marker"); 411 }); 412 413 add_task(async function test_addSession_and_get_session() { 414 info("TelemetryFeed.addSession should add a session and return it"); 415 let instance = new TelemetryFeed(); 416 let session = instance.addSession("foo"); 417 418 Assert.equal(instance.sessions.get("foo"), session); 419 420 info("TelemetryFeed.addSession should set a session_id"); 421 Assert.ok(session.session_id, "Should have a session_id set"); 422 }); 423 424 add_task(async function test_addSession_url_param() { 425 info("TelemetryFeed.addSession should set the page if a url param is given"); 426 let instance = new TelemetryFeed(); 427 let session = instance.addSession("foo", "about:monkeys"); 428 Assert.equal(session.page, "about:monkeys"); 429 430 info( 431 "TelemetryFeed.assSession should set the page prop to 'unknown' " + 432 "if no URL param given" 433 ); 434 session = instance.addSession("test2"); 435 Assert.equal(session.page, "unknown"); 436 }); 437 438 add_task(async function test_addSession_perf_properties() { 439 info( 440 "TelemetryFeed.addSession should set the perf type when lacking " + 441 "timestamp" 442 ); 443 let instance = new TelemetryFeed(); 444 let session = instance.addSession("foo"); 445 Assert.equal(session.perf.load_trigger_type, "unexpected"); 446 447 info( 448 "TelemetryFeed.addSession should set load_trigger_type to " + 449 "first_window_opened on the first about:home seen" 450 ); 451 session = instance.addSession("test2", "about:home"); 452 Assert.equal(session.perf.load_trigger_type, "first_window_opened"); 453 454 info( 455 "TelemetryFeed.addSession should set load_trigger_ts to the " + 456 "value of the process start timestamp" 457 ); 458 Assert.equal( 459 session.perf.load_trigger_ts, 460 Services.startup.getStartupInfo().process.getTime(), 461 "Should have set a timestamp to be the process start time" 462 ); 463 464 info( 465 "TelemetryFeed.addSession should NOT set load_trigger_type to " + 466 "first_window_opened on the second about:home seen" 467 ); 468 let session2 = instance.addSession("test2", "about:home"); 469 Assert.notEqual(session2.perf.load_trigger_type, "first_window_opened"); 470 }); 471 472 add_task(async function test_addSession_valid_ping_on_first_abouthome() { 473 info( 474 "TelemetryFeed.addSession should create a valid session ping " + 475 "on the first about:home seen" 476 ); 477 let instance = new TelemetryFeed(); 478 // Add a session 479 const PORT_ID = "foo"; 480 let session = instance.addSession(PORT_ID, "about:home"); 481 482 // Create a ping referencing the session 483 let ping = instance.createSessionEndEvent(session); 484 await assertSessionPingValid(ping); 485 }); 486 487 add_task(async function test_addSession_valid_ping_data_late_by_ms() { 488 info( 489 "TelemetryFeed.addSession should create a valid session ping " + 490 "with the data_late_by_ms perf" 491 ); 492 let instance = new TelemetryFeed(); 493 // Add a session 494 const PORT_ID = "foo"; 495 let session = instance.addSession(PORT_ID, "about:home"); 496 497 const TOPSITES_LATE_BY_MS = 10; 498 const HIGHLIGHTS_LATE_BY_MS = 20; 499 instance.saveSessionPerfData("foo", { 500 topsites_data_late_by_ms: TOPSITES_LATE_BY_MS, 501 }); 502 instance.saveSessionPerfData("foo", { 503 highlights_data_late_by_ms: HIGHLIGHTS_LATE_BY_MS, 504 }); 505 506 // Create a ping referencing the session 507 let ping = instance.createSessionEndEvent(session); 508 await assertSessionPingValid(ping); 509 Assert.equal(session.perf.topsites_data_late_by_ms, TOPSITES_LATE_BY_MS); 510 Assert.equal(session.perf.highlights_data_late_by_ms, HIGHLIGHTS_LATE_BY_MS); 511 }); 512 513 add_task(async function test_addSession_valid_ping_topsites_stats_perf() { 514 info( 515 "TelemetryFeed.addSession should create a valid session ping " + 516 "with the topsites stats perf" 517 ); 518 let instance = new TelemetryFeed(); 519 // Add a session 520 const PORT_ID = "foo"; 521 let session = instance.addSession(PORT_ID, "about:home"); 522 523 const SCREENSHOT_WITH_ICON = 2; 524 const TOPSITES_PINNED = 3; 525 const TOPSITES_SEARCH_SHORTCUTS = 2; 526 527 instance.saveSessionPerfData("foo", { 528 topsites_icon_stats: { 529 custom_screenshot: 0, 530 screenshot_with_icon: SCREENSHOT_WITH_ICON, 531 screenshot: 1, 532 tippytop: 2, 533 rich_icon: 1, 534 no_image: 0, 535 }, 536 topsites_pinned: TOPSITES_PINNED, 537 topsites_search_shortcuts: TOPSITES_SEARCH_SHORTCUTS, 538 }); 539 540 // Create a ping referencing the session 541 let ping = instance.createSessionEndEvent(session); 542 await assertSessionPingValid(ping); 543 Assert.equal( 544 instance.sessions.get("foo").perf.topsites_icon_stats.screenshot_with_icon, 545 SCREENSHOT_WITH_ICON 546 ); 547 Assert.equal( 548 instance.sessions.get("foo").perf.topsites_pinned, 549 TOPSITES_PINNED 550 ); 551 Assert.equal( 552 instance.sessions.get("foo").perf.topsites_search_shortcuts, 553 TOPSITES_SEARCH_SHORTCUTS 554 ); 555 }); 556 557 add_task(async function test_endSession_no_throw_on_bad_session() { 558 info( 559 "TelemetryFeed.endSession should not throw if there is no " + 560 "session for a given port ID" 561 ); 562 let instance = new TelemetryFeed(); 563 try { 564 instance.endSession("doesn't exist"); 565 Assert.ok(true, "Did not throw."); 566 } catch (e) { 567 Assert.ok(false, "Should not have thrown."); 568 } 569 }); 570 571 add_task(async function test_endSession_session_duration() { 572 info( 573 "TelemetryFeed.endSession should add a session_duration integer " + 574 "if there is a visibility_event_rcvd_ts" 575 ); 576 let instance = new TelemetryFeed(); 577 let session = instance.addSession("foo"); 578 session.perf.visibility_event_rcvd_ts = 444.4732; 579 instance.endSession("foo"); 580 581 Assert.ok( 582 Number.isInteger(session.session_duration), 583 "session_duration should be an integer" 584 ); 585 }); 586 587 add_task(async function test_endSession_no_ping_on_no_visibility_event() { 588 info( 589 "TelemetryFeed.endSession shouldn't send session ping if there's " + 590 "no visibility_event_rcvd_ts" 591 ); 592 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 593 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, true); 594 let instance = new TelemetryFeed(); 595 596 let sandbox = sinon.createSandbox(); 597 sandbox.stub(instance, "configureContentPing"); 598 599 instance.addSession("foo"); 600 601 Services.telemetry.clearEvents(); 602 instance.endSession("foo"); 603 TelemetryTestUtils.assertNumberOfEvents(0); 604 605 info("TelemetryFeed.endSession should remove the session from .sessions"); 606 Assert.ok(!instance.sessions.has("foo")); 607 608 Services.prefs.clearUserPref(PREF_TELEMETRY); 609 Services.prefs.clearUserPref(PREF_EVENT_TELEMETRY); 610 611 sandbox.restore(); 612 }); 613 614 add_task(async function test_endSession_send_ping() { 615 info( 616 "TelemetryFeed.endSession should call createSessionSendEvent with the " + 617 "session if visibilty_event_rcvd_ts was set" 618 ); 619 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 620 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, true); 621 let instance = new TelemetryFeed(); 622 623 let sandbox = sinon.createSandbox(); 624 sandbox.stub(instance, "createSessionEndEvent"); 625 sandbox.stub(instance.utEvents, "sendSessionEndEvent"); 626 sandbox.stub(instance, "configureContentPing"); 627 628 let session = instance.addSession("foo"); 629 630 session.perf.visibility_event_rcvd_ts = 444.4732; 631 instance.endSession("foo"); 632 633 Assert.ok(instance.createSessionEndEvent.calledWith(session)); 634 let sessionEndEvent = instance.createSessionEndEvent.firstCall.returnValue; 635 Assert.ok(instance.utEvents.sendSessionEndEvent.calledWith(sessionEndEvent)); 636 637 info("TelemetryFeed.endSession should remove the session from .sessions"); 638 Assert.ok(!instance.sessions.has("foo")); 639 640 Services.prefs.clearUserPref(PREF_TELEMETRY); 641 Services.prefs.clearUserPref(PREF_EVENT_TELEMETRY); 642 643 sandbox.restore(); 644 }); 645 646 add_task(async function test_createPing_valid_base_if_no_portID() { 647 info( 648 "TelemetryFeed.createPing should create a valid base ping " + 649 "without a session if no portID is supplied" 650 ); 651 let instance = new TelemetryFeed(); 652 let ping = await instance.createPing(); 653 await assertBasePingValid(ping); 654 Assert.ok(!ping.session_id); 655 Assert.ok(!ping.page); 656 }); 657 658 add_task(async function test_createPing_valid_base_if_portID() { 659 info( 660 "TelemetryFeed.createPing should create a valid base ping " + 661 "with session info if a portID is supplied" 662 ); 663 // Add a session 664 const PORT_ID = "foo"; 665 let instance = new TelemetryFeed(); 666 instance.addSession(PORT_ID, "about:home"); 667 let sessionID = instance.sessions.get(PORT_ID).session_id; 668 669 // Create a ping referencing the session 670 let ping = await instance.createPing(PORT_ID); 671 await assertBasePingValid(ping); 672 673 // Make sure we added the right session-related stuff to the ping 674 Assert.equal(ping.session_id, sessionID); 675 Assert.equal(ping.page, "about:home"); 676 }); 677 678 add_task(async function test_createPing_no_session_yet_portID() { 679 info( 680 "TelemetryFeed.createPing should create an 'unexpected' base ping " + 681 "if no session yet portID is supplied" 682 ); 683 let instance = new TelemetryFeed(); 684 let ping = await instance.createPing("foo"); 685 await assertBasePingValid(ping); 686 687 Assert.equal(ping.page, "unknown"); 688 Assert.equal( 689 instance.sessions.get("foo").perf.load_trigger_type, 690 "unexpected" 691 ); 692 }); 693 694 add_task(async function test_createPing_includes_userPrefs() { 695 info("TelemetryFeed.createPing should create a base ping with user_prefs"); 696 let expectedUserPrefs = 0; 697 698 for (let pref of Object.keys(USER_PREFS_ENCODING)) { 699 Services.prefs.setBoolPref( 700 `browser.newtabpage.activity-stream.${pref}`, 701 true 702 ); 703 expectedUserPrefs |= USER_PREFS_ENCODING[pref]; 704 } 705 706 let instance = new TelemetryFeed(); 707 let ping = await instance.createPing("foo"); 708 await assertBasePingValid(ping); 709 Assert.equal(ping.user_prefs, expectedUserPrefs); 710 711 for (const pref of Object.keys(USER_PREFS_ENCODING)) { 712 Services.prefs.clearUserPref(`browser.newtabpage.activity-stream.${pref}`); 713 } 714 }); 715 716 add_task(async function test_createUserEvent_is_valid() { 717 info( 718 "TelemetryFeed.createUserEvent should create a valid user event ping " + 719 "with the right session_id" 720 ); 721 const PORT_ID = "foo"; 722 723 let instance = new TelemetryFeed(); 724 let data = { source: "TOP_SITES", event: "CLICK" }; 725 let action = actionCreators.AlsoToMain( 726 actionCreators.UserEvent(data), 727 PORT_ID 728 ); 729 let session = instance.addSession(PORT_ID); 730 731 let ping = await instance.createUserEvent(action); 732 733 // Is it valid? 734 await assertUserEventPingValid(ping); 735 // Does it have the right session_id? 736 Assert.equal(ping.session_id, session.session_id); 737 }); 738 739 add_task(async function test_createSessionEndEvent_is_valid() { 740 info( 741 "TelemetryFeed.createSessionEndEvent should create a valid session ping" 742 ); 743 const FAKE_DURATION = 12345; 744 let instance = new TelemetryFeed(); 745 let ping = await instance.createSessionEndEvent({ 746 session_id: FAKE_UUID, 747 page: "about:newtab", 748 session_duration: FAKE_DURATION, 749 perf: { 750 load_trigger_ts: 10, 751 load_trigger_type: "menu_plus_or_keyboard", 752 visibility_event_rcvd_ts: 20, 753 is_preloaded: true, 754 }, 755 }); 756 757 // Is it valid? 758 await assertSessionPingValid(ping); 759 Assert.equal(ping.session_id, FAKE_UUID); 760 Assert.equal(ping.page, "about:newtab"); 761 Assert.equal(ping.session_duration, FAKE_DURATION); 762 }); 763 764 add_task(async function test_createSessionEndEvent_with_unexpected_is_valid() { 765 info( 766 "TelemetryFeed.createSessionEndEvent should create a valid 'unexpected' " + 767 "session ping" 768 ); 769 const FAKE_DURATION = 12345; 770 const FAKE_TRIGGER_TYPE = "unexpected"; 771 772 let instance = new TelemetryFeed(); 773 let ping = await instance.createSessionEndEvent({ 774 session_id: FAKE_UUID, 775 page: "about:newtab", 776 session_duration: FAKE_DURATION, 777 perf: { 778 load_trigger_type: FAKE_TRIGGER_TYPE, 779 is_preloaded: true, 780 }, 781 }); 782 783 // Is it valid? 784 await assertSessionPingValid(ping); 785 Assert.equal(ping.session_id, FAKE_UUID); 786 Assert.equal(ping.page, "about:newtab"); 787 Assert.equal(ping.session_duration, FAKE_DURATION); 788 Assert.equal(ping.perf.load_trigger_type, FAKE_TRIGGER_TYPE); 789 }); 790 791 add_task(async function test_sendUTEvent_call_right_function() { 792 info("TelemetryFeed.sendUTEvent should call the UT event function passed in"); 793 let sandbox = sinon.createSandbox(); 794 795 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 796 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, true); 797 798 let event = {}; 799 let instance = new TelemetryFeed(); 800 sandbox.stub(instance.utEvents, "sendUserEvent"); 801 instance.addSession("foo"); 802 803 await instance.sendUTEvent(event, instance.utEvents.sendUserEvent); 804 Assert.ok(instance.utEvents.sendUserEvent.calledWith(event)); 805 806 Services.prefs.clearUserPref(PREF_TELEMETRY); 807 Services.prefs.clearUserPref(PREF_EVENT_TELEMETRY); 808 sandbox.restore(); 809 }); 810 811 add_task(async function test_setLoadTriggerInfo() { 812 info( 813 "TelemetryFeed.setLoadTriggerInfo should call saveSessionPerfData " + 814 "w/load_trigger_{ts,type} data" 815 ); 816 let sandbox = sinon.createSandbox(); 817 let instance = new TelemetryFeed(); 818 sandbox.stub(instance, "saveSessionPerfData"); 819 820 instance.browserOpenNewtabStart(); 821 instance.addSession("port123"); 822 instance.setLoadTriggerInfo("port123"); 823 824 Assert.ok( 825 instance.saveSessionPerfData.calledWith( 826 "port123", 827 sinon.match({ 828 load_trigger_type: "menu_plus_or_keyboard", 829 load_trigger_ts: sinon.match.number, 830 }) 831 ), 832 "TelemetryFeed.saveSessionPerfData was called with the right arguments" 833 ); 834 835 sandbox.restore(); 836 }); 837 838 add_task(async function test_setLoadTriggerInfo_no_saveSessionPerfData() { 839 info( 840 "TelemetryFeed.setLoadTriggerInfo should not call saveSessionPerfData " + 841 "when getting mark throws" 842 ); 843 let sandbox = sinon.createSandbox(); 844 let instance = new TelemetryFeed(); 845 sandbox.stub(instance, "saveSessionPerfData"); 846 847 instance.addSession("port123"); 848 instance.setLoadTriggerInfo("port123"); 849 850 Assert.ok( 851 instance.saveSessionPerfData.notCalled, 852 "TelemetryFeed.saveSessionPerfData was not called" 853 ); 854 855 sandbox.restore(); 856 }); 857 858 add_task(async function test_saveSessionPerfData_updates_session_with_data() { 859 info( 860 "TelemetryFeed.saveSessionPerfData should update the given session " + 861 "with the given data" 862 ); 863 let sandbox = sinon.createSandbox(); 864 let instance = new TelemetryFeed(); 865 866 instance.addSession("port123"); 867 Assert.equal(instance.sessions.get("port123").fake_ts, undefined); 868 let data = { fake_ts: 456, other_fake_ts: 789 }; 869 instance.saveSessionPerfData("port123", data); 870 871 let sessionPerfData = instance.sessions.get("port123").perf; 872 Assert.equal(sessionPerfData.fake_ts, 456); 873 Assert.equal(sessionPerfData.other_fake_ts, 789); 874 875 sandbox.restore(); 876 }); 877 878 add_task(async function test_saveSessionPerfData_calls_setLoadTriggerInfo() { 879 info( 880 "TelemetryFeed.saveSessionPerfData should call setLoadTriggerInfo if " + 881 "data has visibility_event_rcvd_ts" 882 ); 883 let sandbox = sinon.createSandbox(); 884 let instance = new TelemetryFeed(); 885 886 sandbox.stub(instance, "setLoadTriggerInfo"); 887 instance.addSession("port123"); 888 let data = { visibility_event_rcvd_ts: 444455 }; 889 890 instance.saveSessionPerfData("port123", data); 891 892 Assert.ok( 893 instance.setLoadTriggerInfo.calledOnce, 894 "TelemetryFeed.setLoadTriggerInfo was called once" 895 ); 896 Assert.ok(instance.setLoadTriggerInfo.calledWithExactly("port123")); 897 898 Assert.equal( 899 instance.sessions.get("port123").perf.visibility_event_rcvd_ts, 900 444455 901 ); 902 903 sandbox.restore(); 904 }); 905 906 add_task( 907 async function test_saveSessionPerfData_does_not_call_setLoadTriggerInfo() { 908 info( 909 "TelemetryFeed.saveSessionPerfData shouldn't call setLoadTriggerInfo if " + 910 "data has no visibility_event_rcvd_ts" 911 ); 912 let sandbox = sinon.createSandbox(); 913 let instance = new TelemetryFeed(); 914 915 sandbox.stub(instance, "setLoadTriggerInfo"); 916 instance.addSession("port123"); 917 instance.saveSessionPerfData("port123", { monkeys_ts: 444455 }); 918 919 Assert.ok( 920 instance.setLoadTriggerInfo.notCalled, 921 "TelemetryFeed.setLoadTriggerInfo was not called" 922 ); 923 924 sandbox.restore(); 925 } 926 ); 927 928 add_task( 929 async function test_saveSessionPerfData_does_not_call_setLoadTriggerInfo_about_home() { 930 info( 931 "TelemetryFeed.saveSessionPerfData should not call setLoadTriggerInfo when " + 932 "url is about:home" 933 ); 934 let sandbox = sinon.createSandbox(); 935 let instance = new TelemetryFeed(); 936 937 sandbox.stub(instance, "setLoadTriggerInfo"); 938 instance.addSession("port123", "about:home"); 939 let data = { visibility_event_rcvd_ts: 444455 }; 940 instance.saveSessionPerfData("port123", data); 941 942 Assert.ok( 943 instance.setLoadTriggerInfo.notCalled, 944 "TelemetryFeed.setLoadTriggerInfo was not called" 945 ); 946 947 sandbox.restore(); 948 } 949 ); 950 951 add_task( 952 async function test_saveSessionPerfData_calls_maybeRecordTopsitesPainted() { 953 info( 954 "TelemetryFeed.saveSessionPerfData should call maybeRecordTopsitesPainted " + 955 "when url is about:home and topsites_first_painted_ts is given" 956 ); 957 let sandbox = sinon.createSandbox(); 958 let instance = new TelemetryFeed(); 959 960 const TOPSITES_FIRST_PAINTED_TS = 44455; 961 let data = { topsites_first_painted_ts: TOPSITES_FIRST_PAINTED_TS }; 962 963 sandbox.stub(AboutNewTab, "maybeRecordTopsitesPainted"); 964 instance.addSession("port123", "about:home"); 965 instance.saveSessionPerfData("port123", data); 966 967 Assert.ok( 968 AboutNewTab.maybeRecordTopsitesPainted.calledOnce, 969 "AboutNewTab.maybeRecordTopsitesPainted called once" 970 ); 971 Assert.ok( 972 AboutNewTab.maybeRecordTopsitesPainted.calledWith( 973 TOPSITES_FIRST_PAINTED_TS 974 ) 975 ); 976 sandbox.restore(); 977 } 978 ); 979 980 add_task( 981 async function test_saveSessionPerfData_records_Glean_newtab_opened_event() { 982 info( 983 "TelemetryFeed.saveSessionPerfData should record a Glean newtab.opened event " + 984 "with the correct visit_id when visibility event received" 985 ); 986 let sandbox = sinon.createSandbox(); 987 let instance = new TelemetryFeed(); 988 Services.fog.testResetFOG(); 989 990 const SESSION_ID = "decafc0ffee"; 991 const PAGE = "about:newtab"; 992 let session = { page: PAGE, perf: {}, session_id: SESSION_ID }; 993 let data = { visibility_event_rcvd_ts: 444455 }; 994 995 sandbox.stub(instance.sessions, "get").returns(session); 996 instance.saveSessionPerfData("port123", data); 997 998 let newtabOpenedEvents = Glean.newtab.opened.testGetValue(); 999 Assert.deepEqual(newtabOpenedEvents[0].extra, { 1000 newtab_visit_id: SESSION_ID, 1001 source: PAGE, 1002 }); 1003 1004 sandbox.restore(); 1005 } 1006 ); 1007 1008 add_task(async function test_uninit_deregisters_observer() { 1009 info( 1010 "TelemetryFeed.uninit should make this.browserOpenNewtabStart() stop " + 1011 "observing browser-open-newtab-start" 1012 ); 1013 let sandbox = sinon.createSandbox(); 1014 let instance = new TelemetryFeed(); 1015 let countObservers = () => { 1016 return [...Services.obs.enumerateObservers("browser-open-newtab-start")] 1017 .length; 1018 }; 1019 1020 const ORIGINAL_COUNT = countObservers(); 1021 instance.init(); 1022 Assert.equal(countObservers(), ORIGINAL_COUNT + 1, "Observer was added"); 1023 1024 instance.uninit(); 1025 Assert.equal(countObservers(), ORIGINAL_COUNT, "Observer was removed"); 1026 1027 sandbox.restore(); 1028 }); 1029 1030 add_task(async function test_onAction_basic_actions() { 1031 let browser = Services.appShell 1032 .createWindowlessBrowser(false) 1033 .document.createElement("browser"); 1034 1035 let testOnAction = (setupFn, action, checkFn) => { 1036 let sandbox = sinon.createSandbox(); 1037 let instance = new TelemetryFeed(); 1038 setupFn(sandbox, instance); 1039 1040 instance.onAction(action); 1041 checkFn(instance); 1042 sandbox.restore(); 1043 }; 1044 1045 info("TelemetryFeed.onAction should call .init() on an INIT action"); 1046 testOnAction( 1047 (sandbox, instance) => { 1048 sandbox.stub(instance, "init"); 1049 sandbox.stub(instance, "sendPageTakeoverData"); 1050 }, 1051 { type: actionTypes.INIT }, 1052 instance => { 1053 Assert.ok(instance.init.calledOnce, "TelemetryFeed.init called once"); 1054 Assert.ok( 1055 instance.sendPageTakeoverData.calledOnce, 1056 "TelemetryFeed.sendPageTakeoverData called once" 1057 ); 1058 } 1059 ); 1060 1061 info("TelemetryFeed.onAction should call .uninit() on an UNINIT action"); 1062 testOnAction( 1063 (sandbox, instance) => { 1064 sandbox.stub(instance, "uninit"); 1065 }, 1066 { type: actionTypes.UNINIT }, 1067 instance => { 1068 Assert.ok(instance.uninit.calledOnce, "TelemetryFeed.uninit called once"); 1069 } 1070 ); 1071 1072 info( 1073 "TelemetryFeed.onAction should call .handleNewTabInit on a " + 1074 "NEW_TAB_INIT action" 1075 ); 1076 testOnAction( 1077 (sandbox, instance) => { 1078 sandbox.stub(instance, "handleNewTabInit"); 1079 }, 1080 actionCreators.AlsoToMain({ 1081 type: actionTypes.NEW_TAB_INIT, 1082 data: { url: "about:newtab", browser }, 1083 }), 1084 instance => { 1085 Assert.ok( 1086 instance.handleNewTabInit.calledOnce, 1087 "TelemetryFeed.handleNewTabInit called once" 1088 ); 1089 } 1090 ); 1091 1092 info( 1093 "TelemetryFeed.onAction should call .addSession() on a " + 1094 "NEW_TAB_INIT action" 1095 ); 1096 testOnAction( 1097 (sandbox, instance) => { 1098 sandbox.stub(instance, "addSession").returns({ perf: {} }); 1099 sandbox.stub(instance, "setLoadTriggerInfo"); 1100 }, 1101 actionCreators.AlsoToMain( 1102 { 1103 type: actionTypes.NEW_TAB_INIT, 1104 data: { url: "about:monkeys", browser }, 1105 }, 1106 "port123" 1107 ), 1108 instance => { 1109 Assert.ok( 1110 instance.addSession.calledOnce, 1111 "TelemetryFeed.addSession called once" 1112 ); 1113 Assert.ok(instance.addSession.calledWith("port123", "about:monkeys")); 1114 } 1115 ); 1116 1117 info( 1118 "TelemetryFeed.onAction should call .endSession() on a " + 1119 "NEW_TAB_UNLOAD action" 1120 ); 1121 testOnAction( 1122 (sandbox, instance) => { 1123 sandbox.stub(instance, "endSession"); 1124 }, 1125 actionCreators.AlsoToMain({ type: actionTypes.NEW_TAB_UNLOAD }, "port123"), 1126 instance => { 1127 Assert.ok( 1128 instance.endSession.calledOnce, 1129 "TelemetryFeed.endSession called once" 1130 ); 1131 Assert.ok(instance.endSession.calledWith("port123")); 1132 } 1133 ); 1134 1135 info( 1136 "TelemetryFeed.onAction should call .saveSessionPerfData " + 1137 "on SAVE_SESSION_PERF_DATA" 1138 ); 1139 testOnAction( 1140 (sandbox, instance) => { 1141 sandbox.stub(instance, "saveSessionPerfData"); 1142 }, 1143 actionCreators.AlsoToMain( 1144 { type: actionTypes.SAVE_SESSION_PERF_DATA, data: { some_ts: 10 } }, 1145 "port123" 1146 ), 1147 instance => { 1148 Assert.ok( 1149 instance.saveSessionPerfData.calledOnce, 1150 "TelemetryFeed.saveSessionPerfData called once" 1151 ); 1152 Assert.ok( 1153 instance.saveSessionPerfData.calledWith("port123", { some_ts: 10 }) 1154 ); 1155 } 1156 ); 1157 1158 info( 1159 "TelemetryFeed.onAction should send an event on a TELEMETRY_USER_EVENT " + 1160 "action" 1161 ); 1162 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, true); 1163 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1164 testOnAction( 1165 (sandbox, instance) => { 1166 sandbox.stub(instance, "createUserEvent"); 1167 sandbox.stub(instance.utEvents, "sendUserEvent"); 1168 }, 1169 { type: actionTypes.TELEMETRY_USER_EVENT }, 1170 instance => { 1171 Assert.ok( 1172 instance.createUserEvent.calledOnce, 1173 "TelemetryFeed.createUserEvent called once" 1174 ); 1175 Assert.ok( 1176 instance.createUserEvent.calledWith({ 1177 type: actionTypes.TELEMETRY_USER_EVENT, 1178 }) 1179 ); 1180 Assert.ok( 1181 instance.utEvents.sendUserEvent.calledOnce, 1182 "TelemetryFeed.utEvents.sendUserEvent called once" 1183 ); 1184 Assert.ok( 1185 instance.utEvents.sendUserEvent.calledWith( 1186 instance.createUserEvent.returnValue 1187 ) 1188 ); 1189 } 1190 ); 1191 Services.prefs.clearUserPref(PREF_EVENT_TELEMETRY); 1192 Services.prefs.clearUserPref(PREF_TELEMETRY); 1193 1194 info( 1195 "TelemetryFeed.onAction should send an event on a " + 1196 "DISCOVERY_STREAM_USER_EVENT action" 1197 ); 1198 Services.prefs.setBoolPref(PREF_EVENT_TELEMETRY, true); 1199 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1200 testOnAction( 1201 (sandbox, instance) => { 1202 sandbox.stub(instance, "createUserEvent"); 1203 sandbox.stub(instance.utEvents, "sendUserEvent"); 1204 }, 1205 { type: actionTypes.DISCOVERY_STREAM_USER_EVENT }, 1206 instance => { 1207 Assert.ok( 1208 instance.createUserEvent.calledOnce, 1209 "TelemetryFeed.createUserEvent called once" 1210 ); 1211 Assert.ok( 1212 instance.utEvents.sendUserEvent.calledOnce, 1213 "TelemetryFeed.utEvents.sendUserEvent called once" 1214 ); 1215 Assert.ok( 1216 instance.utEvents.sendUserEvent.calledWith( 1217 instance.createUserEvent.returnValue 1218 ) 1219 ); 1220 } 1221 ); 1222 Services.prefs.clearUserPref(PREF_EVENT_TELEMETRY); 1223 Services.prefs.clearUserPref(PREF_TELEMETRY); 1224 }); 1225 1226 add_task( 1227 async function test_onAction_calls_handleDiscoveryStreamImpressionStats_ds() { 1228 info( 1229 "TelemetryFeed.onAction should call " + 1230 ".handleDiscoveryStreamImpressionStats on a " + 1231 "DISCOVERY_STREAM_IMPRESSION_STATS action" 1232 ); 1233 let sandbox = sinon.createSandbox(); 1234 let instance = new TelemetryFeed(); 1235 1236 let session = {}; 1237 sandbox.stub(instance.sessions, "get").returns(session); 1238 let data = { source: "foo", tiles: [{ id: 1 }] }; 1239 let action = { type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS, data }; 1240 sandbox.spy(instance, "handleDiscoveryStreamImpressionStats"); 1241 1242 instance.onAction(actionCreators.AlsoToMain(action, "port123")); 1243 1244 Assert.ok( 1245 instance.handleDiscoveryStreamImpressionStats.calledWith("port123", data) 1246 ); 1247 1248 sandbox.restore(); 1249 } 1250 ); 1251 1252 add_task( 1253 async function test_onAction_calls_handleTopSitesSponsoredImpressionStats() { 1254 info( 1255 "TelemetryFeed.onAction should call " + 1256 ".handleTopSitesSponsoredImpressionStats on a " + 1257 "TOP_SITES_SPONSORED_IMPRESSION_STATS action" 1258 ); 1259 let sandbox = sinon.createSandbox(); 1260 let instance = new TelemetryFeed(); 1261 1262 let session = {}; 1263 sandbox.stub(instance.sessions, "get").returns(session); 1264 let data = { type: "impression", tile_id: 42, position: 1 }; 1265 let action = { 1266 type: actionTypes.TOP_SITES_SPONSORED_IMPRESSION_STATS, 1267 data, 1268 }; 1269 sandbox.spy(instance, "handleTopSitesSponsoredImpressionStats"); 1270 1271 instance.onAction(actionCreators.AlsoToMain(action)); 1272 1273 Assert.ok( 1274 instance.handleTopSitesSponsoredImpressionStats.calledOnce, 1275 "TelemetryFeed.handleTopSitesSponsoredImpressionStats called once" 1276 ); 1277 Assert.deepEqual( 1278 instance.handleTopSitesSponsoredImpressionStats.firstCall.args[0].data, 1279 data 1280 ); 1281 1282 sandbox.restore(); 1283 } 1284 ); 1285 1286 add_task(async function test_onAction_calls_handleAboutSponsoredTopSites() { 1287 info( 1288 "TelemetryFeed.onAction should call " + 1289 ".handleAboutSponsoredTopSites on a " + 1290 "ABOUT_SPONSORED_TOP_SITES action" 1291 ); 1292 let sandbox = sinon.createSandbox(); 1293 let instance = new TelemetryFeed(); 1294 1295 let data = { position: 0, advertiser_name: "moo", tile_id: 42 }; 1296 let action = { type: actionTypes.ABOUT_SPONSORED_TOP_SITES, data }; 1297 sandbox.spy(instance, "handleAboutSponsoredTopSites"); 1298 1299 instance.onAction(actionCreators.AlsoToMain(action)); 1300 1301 Assert.ok( 1302 instance.handleAboutSponsoredTopSites.calledOnce, 1303 "TelemetryFeed.handleAboutSponsoredTopSites called once" 1304 ); 1305 1306 sandbox.restore(); 1307 }); 1308 1309 add_task(async function test_onAction_calls_handleBlockUrl() { 1310 info( 1311 "TelemetryFeed.onAction should call #handleBlockUrl on a BLOCK_URL action" 1312 ); 1313 let sandbox = sinon.createSandbox(); 1314 let instance = new TelemetryFeed(); 1315 1316 let data = { position: 0, advertiser_name: "moo", tile_id: 42 }; 1317 let action = { type: actionTypes.BLOCK_URL, data }; 1318 sandbox.spy(instance, "handleBlockUrl"); 1319 1320 instance.onAction(actionCreators.AlsoToMain(action)); 1321 1322 Assert.ok( 1323 instance.handleBlockUrl.calledOnce, 1324 "TelemetryFeed.handleBlockUrl called once" 1325 ); 1326 1327 sandbox.restore(); 1328 }); 1329 1330 add_task( 1331 async function test_onAction_calls_handleTopSitesOrganicImpressionStats() { 1332 info( 1333 "TelemetryFeed.onAction should call .handleTopSitesOrganicImpressionStats " + 1334 "on a TOP_SITES_ORGANIC_IMPRESSION_STATS action" 1335 ); 1336 let sandbox = sinon.createSandbox(); 1337 let instance = new TelemetryFeed(); 1338 1339 let session = {}; 1340 sandbox.stub(instance.sessions, "get").returns(session); 1341 1342 let data = { type: "impression", position: 1 }; 1343 let action = { type: actionTypes.TOP_SITES_ORGANIC_IMPRESSION_STATS, data }; 1344 sandbox.spy(instance, "handleTopSitesOrganicImpressionStats"); 1345 1346 instance.onAction(actionCreators.AlsoToMain(action)); 1347 1348 Assert.ok( 1349 instance.handleTopSitesOrganicImpressionStats.calledOnce, 1350 "TelemetryFeed.handleTopSitesOrganicImpressionStats called once" 1351 ); 1352 Assert.deepEqual( 1353 instance.handleTopSitesOrganicImpressionStats.firstCall.args[0].data, 1354 data 1355 ); 1356 1357 sandbox.restore(); 1358 } 1359 ); 1360 1361 add_task(async function test_handleNewTabInit_sets_preloaded_session() { 1362 info( 1363 "TelemetryFeed.handleNewTabInit should set the session as preloaded " + 1364 "if the browser is preloaded" 1365 ); 1366 let sandbox = sinon.createSandbox(); 1367 let instance = new TelemetryFeed(); 1368 1369 let session = { perf: {} }; 1370 let preloadedBrowser = { 1371 getAttribute() { 1372 return "preloaded"; 1373 }, 1374 }; 1375 sandbox.stub(instance, "addSession").returns(session); 1376 1377 instance.onAction( 1378 actionCreators.AlsoToMain({ 1379 type: actionTypes.NEW_TAB_INIT, 1380 data: { url: "about:newtab", browser: preloadedBrowser }, 1381 }) 1382 ); 1383 1384 Assert.ok(session.perf.is_preloaded, "is_preloaded property was set"); 1385 1386 sandbox.restore(); 1387 }); 1388 1389 add_task(async function test_handleNewTabInit_sets_nonpreloaded_session() { 1390 info( 1391 "TelemetryFeed.handleNewTabInit should set the session as non-preloaded " + 1392 "if the browser is non-preloaded" 1393 ); 1394 let sandbox = sinon.createSandbox(); 1395 let instance = new TelemetryFeed(); 1396 1397 let session = { perf: {} }; 1398 let preloadedBrowser = { 1399 getAttribute() { 1400 return ""; 1401 }, 1402 }; 1403 sandbox.stub(instance, "addSession").returns(session); 1404 1405 instance.onAction( 1406 actionCreators.AlsoToMain({ 1407 type: actionTypes.NEW_TAB_INIT, 1408 data: { url: "about:newtab", browser: preloadedBrowser }, 1409 }) 1410 ); 1411 1412 Assert.ok(!session.perf.is_preloaded, "is_preloaded property is not true"); 1413 1414 sandbox.restore(); 1415 }); 1416 1417 add_task(async function test_sendPageTakeoverData_homepage_category() { 1418 info( 1419 "TelemetryFeed.sendPageTakeoverData should call " + 1420 "handleASRouterUserEvent" 1421 ); 1422 let sandbox = sinon.createSandbox(); 1423 let instance = new TelemetryFeed(); 1424 Services.fog.testResetFOG(); 1425 1426 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1427 sandbox.stub(HomePage, "get").returns("https://searchprovider.com"); 1428 sandbox.stub(instance, "configureContentPing"); 1429 instance._classifySite = () => Promise.resolve("other"); 1430 1431 await instance.sendPageTakeoverData(); 1432 Assert.equal(Glean.newtab.homepageCategory.testGetValue(), "other"); 1433 1434 Services.prefs.clearUserPref(PREF_TELEMETRY); 1435 sandbox.restore(); 1436 }); 1437 1438 add_task(async function test_sendPageTakeoverData_newtab_category_custom() { 1439 info( 1440 "TelemetryFeed.sendPageTakeoverData should send correct newtab " + 1441 "category for about:newtab set to custom URL" 1442 ); 1443 let sandbox = sinon.createSandbox(); 1444 let instance = new TelemetryFeed(); 1445 Services.fog.testResetFOG(); 1446 1447 sandbox.stub(AboutNewTab, "newTabURLOverridden").get(() => true); 1448 sandbox 1449 .stub(AboutNewTab, "newTabURL") 1450 .get(() => "https://searchprovider.com"); 1451 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1452 sandbox.stub(instance, "configureContentPing"); 1453 instance._classifySite = () => Promise.resolve("other"); 1454 1455 await instance.sendPageTakeoverData(); 1456 Assert.equal(Glean.newtab.newtabCategory.testGetValue(), "other"); 1457 1458 Services.prefs.clearUserPref(PREF_TELEMETRY); 1459 sandbox.restore(); 1460 }); 1461 1462 add_task(async function test_sendPageTakeoverData_newtab_category_custom() { 1463 info( 1464 "TelemetryFeed.sendPageTakeoverData should not set home|newtab " + 1465 "category if neither about:{home,newtab} are set to custom URL" 1466 ); 1467 let sandbox = sinon.createSandbox(); 1468 let instance = new TelemetryFeed(); 1469 Services.fog.testResetFOG(); 1470 1471 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1472 sandbox.stub(instance, "configureContentPing"); 1473 instance._classifySite = () => Promise.resolve("other"); 1474 1475 await instance.sendPageTakeoverData(); 1476 Assert.equal(Glean.newtab.newtabCategory.testGetValue(), "enabled"); 1477 Assert.equal(Glean.newtab.homepageCategory.testGetValue(), "enabled"); 1478 1479 Services.prefs.clearUserPref(PREF_TELEMETRY); 1480 sandbox.restore(); 1481 }); 1482 1483 add_task(async function test_sendPageTakeoverData_newtab_category_extension() { 1484 info( 1485 "TelemetryFeed.sendPageTakeoverData should set correct home|newtab " + 1486 "category when changed by extension" 1487 ); 1488 let sandbox = sinon.createSandbox(); 1489 let instance = new TelemetryFeed(); 1490 Services.fog.testResetFOG(); 1491 1492 const ID = "{abc-foo-bar}"; 1493 sandbox.stub(ExtensionSettingsStore, "getSetting").returns({ id: ID }); 1494 1495 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1496 sandbox.stub(instance, "configureContentPing"); 1497 instance._classifySite = () => Promise.resolve("other"); 1498 1499 await instance.sendPageTakeoverData(); 1500 Assert.equal(Glean.newtab.newtabCategory.testGetValue(), "extension"); 1501 Assert.equal(Glean.newtab.homepageCategory.testGetValue(), "extension"); 1502 1503 Services.prefs.clearUserPref(PREF_TELEMETRY); 1504 sandbox.restore(); 1505 }); 1506 1507 add_task(async function test_sendPageTakeoverData_newtab_disabled() { 1508 info( 1509 "TelemetryFeed.sendPageTakeoverData instruments when newtab is disabled" 1510 ); 1511 let sandbox = sinon.createSandbox(); 1512 let instance = new TelemetryFeed(); 1513 Services.fog.testResetFOG(); 1514 1515 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1516 Services.prefs.setBoolPref("browser.newtabpage.enabled", false); 1517 sandbox.stub(instance, "configureContentPing"); 1518 instance._classifySite = () => Promise.resolve("other"); 1519 1520 await instance.sendPageTakeoverData(); 1521 Assert.equal(Glean.newtab.newtabCategory.testGetValue(), "disabled"); 1522 1523 Services.prefs.clearUserPref(PREF_TELEMETRY); 1524 Services.prefs.clearUserPref("browser.newtabpage.enabled"); 1525 sandbox.restore(); 1526 }); 1527 1528 add_task(async function test_sendPageTakeoverData_homepage_disabled() { 1529 info( 1530 "TelemetryFeed.sendPageTakeoverData instruments when homepage is disabled" 1531 ); 1532 let sandbox = sinon.createSandbox(); 1533 let instance = new TelemetryFeed(); 1534 Services.fog.testResetFOG(); 1535 1536 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1537 sandbox.stub(HomePage, "overridden").get(() => true); 1538 sandbox.stub(instance, "configureContentPing"); 1539 1540 await instance.sendPageTakeoverData(); 1541 Assert.equal(Glean.newtab.homepageCategory.testGetValue(), "disabled"); 1542 1543 Services.prefs.clearUserPref(PREF_TELEMETRY); 1544 sandbox.restore(); 1545 }); 1546 1547 add_task(async function test_sendPageTakeoverData_newtab_ping() { 1548 info("TelemetryFeed.sendPageTakeoverData should send a 'newtab' ping"); 1549 let sandbox = sinon.createSandbox(); 1550 let instance = new TelemetryFeed(); 1551 Services.fog.testResetFOG(); 1552 1553 Services.prefs.setBoolPref(PREF_TELEMETRY, true); 1554 sandbox.stub(instance, "configureContentPing"); 1555 1556 let pingSubmitted = new Promise(resolve => { 1557 GleanPings.newtab.testBeforeNextSubmit(reason => { 1558 Assert.equal(reason, "component_init"); 1559 resolve(); 1560 }); 1561 }); 1562 1563 await instance.sendPageTakeoverData(); 1564 await pingSubmitted; 1565 1566 Services.prefs.clearUserPref(PREF_TELEMETRY); 1567 sandbox.restore(); 1568 }); 1569 1570 add_task( 1571 async function test_handleDiscoveryStreamImpressionStats_should_throw() { 1572 info( 1573 "TelemetryFeed.handleDiscoveryStreamImpressionStats should throw " + 1574 "for a missing session" 1575 ); 1576 1577 let instance = new TelemetryFeed(); 1578 try { 1579 instance.handleDiscoveryStreamImpressionStats("a_missing_port", {}); 1580 Assert.ok(false, "Should not have reached here."); 1581 } catch (e) { 1582 Assert.ok(true, "Should have thrown for a missing session."); 1583 } 1584 } 1585 ); 1586 1587 add_task( 1588 async function test_handleDiscoveryStreamImpressionStats_instrument_pocket_impressions() { 1589 info( 1590 "TelemetryFeed.handleDiscoveryStreamImpressionStats should throw " + 1591 "for a missing session" 1592 ); 1593 1594 let sandbox = sinon.createSandbox(); 1595 let instance = new TelemetryFeed(); 1596 Services.fog.testResetFOG(); 1597 1598 const SESSION_ID = "1337cafe"; 1599 const POS_1 = 1; 1600 const POS_2 = 4; 1601 const SHIM = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ="; 1602 const FETCH_TIMESTAMP = new Date("March 22, 2024 10:15:20"); 1603 const NEWTAB_CREATION_TIMESTAMP = new Date("March 23, 2024 11:10:30"); 1604 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1605 1606 let pingSubmitted = new Promise(resolve => { 1607 GleanPings.spoc.testBeforeNextSubmit(reason => { 1608 Assert.equal(reason, "impression"); 1609 let pocketImpressions = Glean.pocket.impression.testGetValue(); 1610 Assert.equal(pocketImpressions.length, 2); 1611 Assert.deepEqual(pocketImpressions[0].extra, { 1612 newtab_visit_id: SESSION_ID, 1613 is_sponsored: String(false), 1614 position: String(POS_1), 1615 recommendation_id: "decaf-c0ff33", 1616 content_redacted: String(true), 1617 }); 1618 Assert.deepEqual(pocketImpressions[1].extra, { 1619 newtab_visit_id: SESSION_ID, 1620 is_sponsored: String(true), 1621 position: String(POS_2), 1622 tile_id: String(2), 1623 content_redacted: String(true), 1624 }); 1625 Assert.equal(Glean.pocket.shim.testGetValue(), SHIM); 1626 Assert.deepEqual( 1627 Glean.pocket.fetchTimestamp.testGetValue(), 1628 FETCH_TIMESTAMP 1629 ); 1630 Assert.deepEqual( 1631 Glean.pocket.newtabCreationTimestamp.testGetValue(), 1632 NEWTAB_CREATION_TIMESTAMP 1633 ); 1634 1635 resolve(); 1636 }); 1637 }); 1638 1639 instance.handleDiscoveryStreamImpressionStats("_", { 1640 source: "foo", 1641 tiles: [ 1642 { 1643 id: 1, 1644 pos: POS_1, 1645 type: "organic", 1646 recommendation_id: "decaf-c0ff33", 1647 }, 1648 { 1649 id: 2, 1650 pos: POS_2, 1651 type: "spoc", 1652 recommendation_id: undefined, 1653 shim: SHIM, 1654 fetchTimestamp: FETCH_TIMESTAMP.valueOf(), 1655 }, 1656 ], 1657 window_inner_width: 1000, 1658 window_inner_height: 900, 1659 firstVisibleTimestamp: NEWTAB_CREATION_TIMESTAMP.valueOf(), 1660 }); 1661 1662 await pingSubmitted; 1663 1664 sandbox.restore(); 1665 } 1666 ); 1667 1668 add_task( 1669 async function test_handleTopSitesSponsoredImpressionStats_add_keyed_scalar() { 1670 info( 1671 "TelemetryFeed.handleTopSitesSponsoredImpressionStats should add to " + 1672 "keyed scalar on an impression event" 1673 ); 1674 1675 let sandbox = sinon.createSandbox(); 1676 let instance = new TelemetryFeed(); 1677 Services.telemetry.clearScalars(); 1678 1679 let data = { 1680 type: "impression", 1681 tile_id: 42, 1682 source: "newtab", 1683 position: 0, 1684 reporting_url: "https://test.reporting.net/", 1685 }; 1686 await instance.handleTopSitesSponsoredImpressionStats({ data }); 1687 TelemetryTestUtils.assertKeyedScalar( 1688 TelemetryTestUtils.getProcessScalars("parent", true, true), 1689 "contextual.services.topsites.impression", 1690 "newtab_1", 1691 1 1692 ); 1693 1694 sandbox.restore(); 1695 } 1696 ); 1697 1698 add_task( 1699 async function test_handleTopSitesSponsoredImpressionStats_add_keyed_scalar_click() { 1700 info( 1701 "TelemetryFeed.handleTopSitesSponsoredImpressionStats should add to " + 1702 "keyed scalar on a click event" 1703 ); 1704 1705 let sandbox = sinon.createSandbox(); 1706 let instance = new TelemetryFeed(); 1707 Services.telemetry.clearScalars(); 1708 1709 let data = { 1710 type: "click", 1711 tile_id: 42, 1712 source: "newtab", 1713 position: 0, 1714 reporting_url: "https://test.reporting.net/", 1715 }; 1716 await instance.handleTopSitesSponsoredImpressionStats({ data }); 1717 TelemetryTestUtils.assertKeyedScalar( 1718 TelemetryTestUtils.getProcessScalars("parent", true, true), 1719 "contextual.services.topsites.click", 1720 "newtab_1", 1721 1 1722 ); 1723 1724 sandbox.restore(); 1725 } 1726 ); 1727 1728 add_task( 1729 async function test_handleTopSitesSponsoredImpressionStats_record_glean_impression() { 1730 info( 1731 "TelemetryFeed.handleTopSitesSponsoredImpressionStats should record a " + 1732 "Glean topsites.impression event on an impression event" 1733 ); 1734 1735 let sandbox = sinon.createSandbox(); 1736 let instance = new TelemetryFeed(); 1737 Services.fog.testResetFOG(); 1738 1739 let data = { 1740 type: "impression", 1741 tile_id: 42, 1742 source: "newtab", 1743 position: 1, 1744 reporting_url: "https://test.reporting.net/", 1745 advertiser: "adnoid ads", 1746 }; 1747 const SESSION_ID = "decafc0ffee"; 1748 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1749 await instance.handleTopSitesSponsoredImpressionStats({ data }); 1750 let impressions = Glean.topsites.impression.testGetValue(); 1751 Assert.equal(impressions.length, 1, "Should have recorded 1 impression"); 1752 1753 Assert.deepEqual(impressions[0].extra, { 1754 advertiser_name: "adnoid ads", 1755 tile_id: data.tile_id, 1756 newtab_visit_id: SESSION_ID, 1757 is_sponsored: String(true), 1758 position: String(1), 1759 }); 1760 1761 sandbox.restore(); 1762 } 1763 ); 1764 1765 add_task( 1766 async function test_handleTopSitesSponsoredImpressionStats_record_glean_click() { 1767 info( 1768 "TelemetryFeed.handleTopSitesSponsoredImpressionStats should record " + 1769 "a Glean topsites.click event on a click event" 1770 ); 1771 1772 let sandbox = sinon.createSandbox(); 1773 let instance = new TelemetryFeed(); 1774 Services.fog.testResetFOG(); 1775 1776 let data = { 1777 type: "click", 1778 advertiser: "test advertiser", 1779 tile_id: 42, 1780 source: "newtab", 1781 position: 0, 1782 reporting_url: "https://test.reporting.net/", 1783 }; 1784 const SESSION_ID = "decafc0ffee"; 1785 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1786 await instance.handleTopSitesSponsoredImpressionStats({ data }); 1787 let clicks = Glean.topsites.click.testGetValue(); 1788 Assert.equal(clicks.length, 1, "Should have recorded 1 click"); 1789 1790 Assert.deepEqual(clicks[0].extra, { 1791 advertiser_name: "test advertiser", 1792 tile_id: data.tile_id, 1793 newtab_visit_id: SESSION_ID, 1794 is_sponsored: String(true), 1795 position: String(0), 1796 }); 1797 1798 sandbox.restore(); 1799 } 1800 ); 1801 1802 add_task( 1803 async function test_handleTopSitesSponsoredImpressionStats_no_submit_unknown_pingType() { 1804 info( 1805 "TelemetryFeed.handleTopSitesSponsoredImpressionStats should not " + 1806 "submit on unknown pingTypes" 1807 ); 1808 1809 let sandbox = sinon.createSandbox(); 1810 let instance = new TelemetryFeed(); 1811 Services.fog.testResetFOG(); 1812 1813 let data = { type: "unknown_type" }; 1814 1815 await instance.handleTopSitesSponsoredImpressionStats({ data }); 1816 let impressions = Glean.topsites.impression.testGetValue(); 1817 Assert.ok(!impressions, "Should not have recorded any impressions"); 1818 1819 sandbox.restore(); 1820 } 1821 ); 1822 1823 add_task( 1824 async function test_handleTopSitesOrganicImpressionStats_record_glean_topsites_impression() { 1825 info( 1826 "TelemetryFeed.handleTopSitesOrganicImpressionStats should record a " + 1827 "Glean topsites.impression event on an impression event" 1828 ); 1829 1830 let sandbox = sinon.createSandbox(); 1831 let instance = new TelemetryFeed(); 1832 Services.fog.testResetFOG(); 1833 1834 let data = { 1835 type: "impression", 1836 source: "newtab", 1837 position: 0, 1838 isPinned: false, 1839 smartScores: { moo: 1 }, 1840 smartWeights: { moo: 0 }, 1841 }; 1842 const SESSION_ID = "decafc0ffee"; 1843 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1844 1845 await instance.handleTopSitesOrganicImpressionStats({ data }); 1846 let impressions = Glean.topsites.impression.testGetValue(); 1847 Assert.equal(impressions.length, 1, "Recorded 1 impression"); 1848 1849 Assert.deepEqual(impressions[0].extra, { 1850 is_pinned: String(false), 1851 newtab_visit_id: SESSION_ID, 1852 is_sponsored: String(false), 1853 position: String(0), 1854 smart_scores: JSON.stringify({ moo: 1 }), 1855 smart_weights: JSON.stringify({ moo: 0 }), 1856 }); 1857 1858 sandbox.restore(); 1859 } 1860 ); 1861 1862 add_task( 1863 async function test_handleTopSitesOrganicImpressionStats_record_glean_topsites_click() { 1864 info( 1865 "TelemetryFeed.handleTopSitesOrganicImpressionStats should record a " + 1866 "Glean topsites.click event on a click event" 1867 ); 1868 1869 let sandbox = sinon.createSandbox(); 1870 let instance = new TelemetryFeed(); 1871 Services.fog.testResetFOG(); 1872 1873 let data = { 1874 type: "click", 1875 source: "newtab", 1876 position: 0, 1877 isPinned: false, 1878 smartScores: { moo: 1 }, 1879 smartWeights: { moo: 0 }, 1880 }; 1881 const SESSION_ID = "decafc0ffee"; 1882 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1883 1884 await instance.handleTopSitesOrganicImpressionStats({ data }); 1885 let clicks = Glean.topsites.click.testGetValue(); 1886 Assert.equal(clicks.length, 1, "Recorded 1 click"); 1887 1888 Assert.deepEqual(clicks[0].extra, { 1889 newtab_visit_id: SESSION_ID, 1890 is_sponsored: String(false), 1891 position: String(0), 1892 is_pinned: String(false), 1893 smart_scores: JSON.stringify({ moo: 1 }), 1894 smart_weights: JSON.stringify({ moo: 0 }), 1895 }); 1896 1897 sandbox.restore(); 1898 } 1899 ); 1900 1901 add_task( 1902 async function test_handleTopSitesOrganicImpressionStats_no_recording() { 1903 info( 1904 "TelemetryFeed.handleTopSitesOrganicImpressionStats should not " + 1905 "record events on an unknown session" 1906 ); 1907 1908 let sandbox = sinon.createSandbox(); 1909 let instance = new TelemetryFeed(); 1910 Services.fog.testResetFOG(); 1911 1912 sandbox.stub(instance.sessions, "get").returns(false); 1913 1914 await instance.handleTopSitesOrganicImpressionStats({}); 1915 Assert.ok(!Glean.topsites.click.testGetValue(), "Click was not recorded"); 1916 Assert.ok( 1917 !Glean.topsites.impression.testGetValue(), 1918 "Impression was not recorded" 1919 ); 1920 1921 sandbox.restore(); 1922 } 1923 ); 1924 1925 add_task( 1926 async function test_handleTopSitesOrganicImpressionStats_no_recording_with_session() { 1927 info( 1928 "TelemetryFeed.handleTopSitesOrganicImpressionStats should not record " + 1929 "events on an unknown impressionStats action" 1930 ); 1931 1932 let sandbox = sinon.createSandbox(); 1933 let instance = new TelemetryFeed(); 1934 Services.fog.testResetFOG(); 1935 1936 const SESSION_ID = "decafc0ffee"; 1937 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1938 1939 await instance.handleTopSitesOrganicImpressionStats({ type: "unknown" }); 1940 Assert.ok(!Glean.topsites.click.testGetValue(), "Click was not recorded"); 1941 Assert.ok( 1942 !Glean.topsites.impression.testGetValue(), 1943 "Impression was not recorded" 1944 ); 1945 1946 sandbox.restore(); 1947 } 1948 ); 1949 1950 add_task( 1951 async function test_handleDiscoveryStreamUserEvent_no_recording_with_session() { 1952 info( 1953 "TelemetryFeed.handleDiscoveryStreamUserEvent correctly handles " + 1954 "action with no `data`" 1955 ); 1956 1957 let sandbox = sinon.createSandbox(); 1958 let instance = new TelemetryFeed(); 1959 Services.fog.testResetFOG(); 1960 1961 let action = actionCreators.DiscoveryStreamUserEvent(); 1962 const SESSION_ID = "decafc0ffee"; 1963 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 1964 1965 instance.handleDiscoveryStreamUserEvent(action); 1966 Assert.ok( 1967 !Glean.pocket.topicClick.testGetValue(), 1968 "Pocket topicClick was not recorded" 1969 ); 1970 Assert.ok( 1971 !Glean.pocket.click.testGetValue(), 1972 "Pocket click was not recorded" 1973 ); 1974 Assert.ok( 1975 !Glean.pocket.save.testGetValue(), 1976 "Pocket save was not recorded" 1977 ); 1978 1979 sandbox.restore(); 1980 } 1981 ); 1982 1983 add_task( 1984 async function test_handleDiscoveryStreamUserEvent_click_with_no_value() { 1985 info( 1986 "TelemetryFeed.handleDiscoveryStreamUserEvent correctly handles " + 1987 "CLICK data with no value" 1988 ); 1989 1990 let sandbox = sinon.createSandbox(); 1991 let instance = new TelemetryFeed(); 1992 Services.fog.testResetFOG(); 1993 1994 let action = actionCreators.DiscoveryStreamUserEvent({ 1995 event: "CLICK", 1996 source: "POPULAR_TOPICS", 1997 }); 1998 const SESSION_ID = "decafc0ffee"; 1999 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2000 2001 instance.handleDiscoveryStreamUserEvent(action); 2002 let topicClicks = Glean.pocket.topicClick.testGetValue(); 2003 Assert.equal(topicClicks.length, 1, "Recorded 1 click"); 2004 Assert.deepEqual(topicClicks[0].extra, { 2005 newtab_visit_id: SESSION_ID, 2006 }); 2007 2008 sandbox.restore(); 2009 } 2010 ); 2011 2012 add_task( 2013 async function test_handleDiscoveryStreamUserEvent_non_popular_click_with_no_value() { 2014 info( 2015 "TelemetryFeed.handleDiscoveryStreamUserEvent correctly handles " + 2016 "non-POPULAR_TOPICS CLICK data with no value" 2017 ); 2018 2019 let sandbox = sinon.createSandbox(); 2020 let instance = new TelemetryFeed(); 2021 Services.fog.testResetFOG(); 2022 2023 let action = actionCreators.DiscoveryStreamUserEvent({ 2024 event: "CLICK", 2025 source: "not-POPULAR_TOPICS", 2026 }); 2027 const SESSION_ID = "decafc0ffee"; 2028 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2029 2030 instance.handleDiscoveryStreamUserEvent(action); 2031 Assert.ok( 2032 !Glean.pocket.topicClick.testGetValue(), 2033 "Pocket topicClick was not recorded" 2034 ); 2035 Assert.ok( 2036 !Glean.pocket.click.testGetValue(), 2037 "Pocket click was not recorded" 2038 ); 2039 Assert.ok( 2040 !Glean.pocket.save.testGetValue(), 2041 "Pocket save was not recorded" 2042 ); 2043 2044 sandbox.restore(); 2045 } 2046 ); 2047 2048 add_task( 2049 async function test_handleDiscoveryStreamUserEvent_non_popular_click() { 2050 info( 2051 "TelemetryFeed.handleDiscoveryStreamUserEvent correctly handles " + 2052 "CLICK data with non-POPULAR_TOPICS source" 2053 ); 2054 2055 let sandbox = sinon.createSandbox(); 2056 let instance = new TelemetryFeed(); 2057 Services.fog.testResetFOG(); 2058 const TOPIC = "atopic"; 2059 let action = actionCreators.DiscoveryStreamUserEvent({ 2060 event: "CLICK", 2061 source: "not-POPULAR_TOPICS", 2062 value: { 2063 card_type: "topics_widget", 2064 topic: TOPIC, 2065 }, 2066 }); 2067 2068 const SESSION_ID = "decafc0ffee"; 2069 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2070 2071 instance.handleDiscoveryStreamUserEvent(action); 2072 let topicClicks = Glean.pocket.topicClick.testGetValue(); 2073 Assert.equal(topicClicks.length, 1, "Recorded 1 click"); 2074 Assert.deepEqual(topicClicks[0].extra, { 2075 newtab_visit_id: SESSION_ID, 2076 topic: TOPIC, 2077 }); 2078 2079 sandbox.restore(); 2080 } 2081 ); 2082 2083 add_task( 2084 async function test_handleDiscoveryStreamUserEvent_without_card_type() { 2085 info( 2086 "TelemetryFeed.handleDiscoveryStreamUserEvent doesn't instrument " + 2087 "a CLICK without a card_type" 2088 ); 2089 2090 let sandbox = sinon.createSandbox(); 2091 let instance = new TelemetryFeed(); 2092 Services.fog.testResetFOG(); 2093 let action = actionCreators.DiscoveryStreamUserEvent({ 2094 event: "CLICK", 2095 source: "not-POPULAR_TOPICS", 2096 value: { 2097 card_type: "not spoc, organic, or topics_widget", 2098 }, 2099 }); 2100 2101 const SESSION_ID = "decafc0ffee"; 2102 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2103 2104 instance.handleDiscoveryStreamUserEvent(action); 2105 2106 Assert.ok( 2107 !Glean.pocket.topicClick.testGetValue(), 2108 "Pocket topicClick was not recorded" 2109 ); 2110 Assert.ok( 2111 !Glean.pocket.click.testGetValue(), 2112 "Pocket click was not recorded" 2113 ); 2114 Assert.ok( 2115 !Glean.pocket.save.testGetValue(), 2116 "Pocket save was not recorded" 2117 ); 2118 2119 sandbox.restore(); 2120 } 2121 ); 2122 2123 add_task(async function test_handleDiscoveryStreamUserEvent_popular_click() { 2124 info( 2125 "TelemetryFeed.handleDiscoveryStreamUserEvent instruments a popular " + 2126 "topic click" 2127 ); 2128 2129 let sandbox = sinon.createSandbox(); 2130 let instance = new TelemetryFeed(); 2131 Services.fog.testResetFOG(); 2132 const TOPIC = "entertainment"; 2133 let action = actionCreators.DiscoveryStreamUserEvent({ 2134 event: "CLICK", 2135 source: "POPULAR_TOPICS", 2136 value: { 2137 card_type: "topics_widget", 2138 topic: TOPIC, 2139 }, 2140 }); 2141 2142 const SESSION_ID = "decafc0ffee"; 2143 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2144 2145 instance.handleDiscoveryStreamUserEvent(action); 2146 let topicClicks = Glean.pocket.topicClick.testGetValue(); 2147 Assert.equal(topicClicks.length, 1, "Recorded 1 click"); 2148 Assert.deepEqual(topicClicks[0].extra, { 2149 newtab_visit_id: SESSION_ID, 2150 topic: TOPIC, 2151 }); 2152 2153 sandbox.restore(); 2154 }); 2155 2156 add_task(async function test_handleDiscoveryStreamUserEvent_tooltip_click() { 2157 info( 2158 "TelemetryFeed.handleDiscoveryStreamUserEvent instruments a " + 2159 "tooltip click" 2160 ); 2161 2162 let sandbox = sinon.createSandbox(); 2163 let instance = new TelemetryFeed(); 2164 Services.fog.testResetFOG(); 2165 const feature = "FEATURE_HIGHLIGHT_DEFAULT"; 2166 let action = actionCreators.DiscoveryStreamUserEvent({ 2167 event: "CLICK", 2168 source: "FEATURE_HIGHLIGHT", 2169 value: { 2170 feature, 2171 }, 2172 }); 2173 2174 const SESSION_ID = "decafc0ffee"; 2175 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2176 2177 instance.handleDiscoveryStreamUserEvent(action); 2178 let tooltipClicks = Glean.newtab.tooltipClick.testGetValue(); 2179 Assert.equal(tooltipClicks.length, 1, "Recorded 1 click"); 2180 Assert.deepEqual(tooltipClicks[0].extra, { 2181 newtab_visit_id: SESSION_ID, 2182 feature, 2183 }); 2184 2185 sandbox.restore(); 2186 }); 2187 2188 add_task( 2189 async function test_handleDiscoveryStreamUserEvent_organic_top_stories_click() { 2190 info( 2191 "TelemetryFeed.handleDiscoveryStreamUserEvent instruments an organic " + 2192 "top stories click" 2193 ); 2194 Services.prefs.setBoolPref(PREF_PRIVATE_PING_ENABLED, false); 2195 Services.prefs.setBoolPref(PREF_REDACT_NEWTAB_PING_ENABLED, false); 2196 2197 let sandbox = sinon.createSandbox(); 2198 let instance = new TelemetryFeed(); 2199 Services.fog.testResetFOG(); 2200 const ACTION_POSITION = 42; 2201 let action = actionCreators.DiscoveryStreamUserEvent({ 2202 event: "CLICK", 2203 action_position: ACTION_POSITION, 2204 value: { 2205 card_type: "organic", 2206 corpus_item_id: "decaf-beef", 2207 scheduled_corpus_item_id: "dead-beef", 2208 tile_id: 314623757745896, 2209 }, 2210 }); 2211 2212 const SESSION_ID = "decafc0ffee"; 2213 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2214 2215 instance.handleDiscoveryStreamUserEvent(action); 2216 2217 let clicks = Glean.pocket.click.testGetValue(); 2218 Assert.equal(clicks.length, 1, "Recorded 1 click"); 2219 Assert.deepEqual(clicks[0].extra, { 2220 newtab_visit_id: SESSION_ID, 2221 is_sponsored: String(false), 2222 position: String(ACTION_POSITION), 2223 corpus_item_id: "decaf-beef", 2224 scheduled_corpus_item_id: "dead-beef", 2225 tile_id: String(314623757745896), 2226 }); 2227 2228 Assert.ok( 2229 !Glean.pocket.shim.testGetValue(), 2230 "Pocket shim was not recorded" 2231 ); 2232 2233 sandbox.restore(); 2234 Services.prefs.clearUserPref(PREF_PRIVATE_PING_ENABLED); 2235 Services.prefs.clearUserPref(PREF_REDACT_NEWTAB_PING_ENABLED); 2236 } 2237 ); 2238 2239 add_task( 2240 async function test_handleDiscoveryStreamUserEvent_private_ping_without_redactions_organic_top_stories_click() { 2241 info( 2242 "TelemetryFeed.handleDiscoveryStreamUserEvent instruments an organic " + 2243 "top stories click with private ping fully enabled" 2244 ); 2245 2246 Services.prefs.setBoolPref(PREF_PRIVATE_PING_ENABLED, true); 2247 Services.prefs.setBoolPref(PREF_REDACT_NEWTAB_PING_ENABLED, false); 2248 2249 let sandbox = sinon.createSandbox(); 2250 let instance = new TelemetryFeed(); 2251 Services.fog.testResetFOG(); 2252 const ACTION_POSITION = 42; 2253 let action = actionCreators.DiscoveryStreamUserEvent({ 2254 event: "CLICK", 2255 action_position: ACTION_POSITION, 2256 value: { 2257 card_type: "organic", 2258 corpus_item_id: "decaf-beef", 2259 scheduled_corpus_item_id: "dead-beef", 2260 tile_id: 314623757745896, 2261 }, 2262 }); 2263 2264 const SESSION_ID = "decafc0ffee"; 2265 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2266 sandbox.spy(instance.newtabContentPing, "recordEvent"); 2267 2268 instance.handleDiscoveryStreamUserEvent(action); 2269 2270 let clicks = Glean.pocket.click.testGetValue(); 2271 2272 Assert.equal(clicks.length, 1, "Recorded 1 content click"); 2273 Assert.equal(clicks.length, 1, "Recorded 1 private click"); 2274 Assert.deepEqual(clicks[0].extra, { 2275 newtab_visit_id: SESSION_ID, 2276 is_sponsored: String(false), 2277 corpus_item_id: "decaf-beef", 2278 scheduled_corpus_item_id: "dead-beef", 2279 position: String(ACTION_POSITION), 2280 tile_id: 314623757745896, 2281 }); 2282 2283 Assert.ok( 2284 instance.newtabContentPing.recordEvent.calledWith( 2285 "click", 2286 sinon.match({ 2287 newtab_visit_id: SESSION_ID, 2288 is_sponsored: false, 2289 position: ACTION_POSITION, 2290 tile_id: 314623757745896, 2291 corpus_item_id: "decaf-beef", 2292 scheduled_corpus_item_id: "dead-beef", 2293 }) 2294 ), 2295 "NewTabContentPing passed the expected arguments." 2296 ); 2297 2298 Assert.ok( 2299 !Glean.pocket.shim.testGetValue(), 2300 "Pocket shim was not recorded" 2301 ); 2302 2303 sandbox.restore(); 2304 Services.prefs.clearUserPref(PREF_PRIVATE_PING_ENABLED); 2305 Services.prefs.clearUserPref(PREF_REDACT_NEWTAB_PING_ENABLED); 2306 } 2307 ); 2308 2309 add_task( 2310 async function test_handleDiscoveryStreamUserEvent_private_ping_with_redactions_organic_top_stories_click() { 2311 info( 2312 "TelemetryFeed.handleDiscoveryStreamUserEvent instruments an organic " + 2313 "top stories click with private ping fully enabled" 2314 ); 2315 2316 Services.prefs.setBoolPref(PREF_PRIVATE_PING_ENABLED, true); 2317 Services.prefs.setBoolPref(PREF_REDACT_NEWTAB_PING_ENABLED, true); 2318 2319 let sandbox = sinon.createSandbox(); 2320 let instance = new TelemetryFeed(); 2321 Services.fog.testResetFOG(); 2322 const ACTION_POSITION = 42; 2323 let action = actionCreators.DiscoveryStreamUserEvent({ 2324 event: "CLICK", 2325 action_position: ACTION_POSITION, 2326 value: { 2327 card_type: "organic", 2328 corpus_item_id: "decaf-beef", 2329 scheduled_corpus_item_id: "dead-beef", 2330 tile_id: 314623757745896, 2331 content_redacted: true, 2332 }, 2333 }); 2334 2335 const SESSION_ID = "decafc0ffee"; 2336 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2337 sandbox.spy(instance.newtabContentPing, "recordEvent"); 2338 2339 instance.handleDiscoveryStreamUserEvent(action); 2340 2341 let clicks = Glean.pocket.click.testGetValue(); 2342 2343 Assert.equal(clicks.length, 1, "Recorded 1 content click"); 2344 Assert.equal(clicks.length, 1, "Recorded 1 private click"); 2345 Assert.deepEqual(clicks[0].extra, { 2346 content_redacted: String(true), 2347 newtab_visit_id: SESSION_ID, 2348 is_sponsored: String(false), 2349 position: String(ACTION_POSITION), 2350 }); 2351 2352 Assert.ok( 2353 instance.newtabContentPing.recordEvent.calledWith( 2354 "click", 2355 sinon.match({ 2356 newtab_visit_id: SESSION_ID, 2357 is_sponsored: false, 2358 position: ACTION_POSITION, 2359 corpus_item_id: "decaf-beef", 2360 scheduled_corpus_item_id: "dead-beef", 2361 tile_id: 314623757745896, 2362 }) 2363 ), 2364 "NewTabContentPing passed the expected arguments." 2365 ); 2366 2367 Assert.ok( 2368 !Glean.pocket.shim.testGetValue(), 2369 "Pocket shim was not recorded" 2370 ); 2371 2372 sandbox.restore(); 2373 Services.prefs.clearUserPref(PREF_PRIVATE_PING_ENABLED); 2374 Services.prefs.clearUserPref(PREF_REDACT_NEWTAB_PING_ENABLED); 2375 } 2376 ); 2377 2378 add_task( 2379 async function test_handleDiscoveryStreamUserEvent_sponsored_top_stories_click() { 2380 info( 2381 "TelemetryFeed.handleDiscoveryStreamUserEvent instruments a sponsored " + 2382 "top stories click" 2383 ); 2384 2385 let sandbox = sinon.createSandbox(); 2386 let instance = new TelemetryFeed(); 2387 Services.fog.testResetFOG(); 2388 const ACTION_POSITION = 42; 2389 const SHIM = "Y29uc2lkZXIgeW91ciBjdXJpb3NpdHkgcmV3YXJkZWQ="; 2390 const FETCH_TIMESTAMP = new Date("March 22, 2024 10:15:20"); 2391 const NEWTAB_CREATION_TIMESTAMP = new Date("March 23, 2024 11:10:30"); 2392 let action = actionCreators.DiscoveryStreamUserEvent({ 2393 event: "CLICK", 2394 action_position: ACTION_POSITION, 2395 value: { 2396 card_type: "spoc", 2397 recommendation_id: undefined, 2398 tile_id: 448685088, 2399 shim: SHIM, 2400 fetchTimestamp: FETCH_TIMESTAMP.valueOf(), 2401 firstVisibleTimestamp: NEWTAB_CREATION_TIMESTAMP.valueOf(), 2402 }, 2403 }); 2404 2405 const SESSION_ID = "decafc0ffee"; 2406 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2407 2408 let pingSubmitted = new Promise(resolve => { 2409 GleanPings.spoc.testBeforeNextSubmit(reason => { 2410 Assert.equal(reason, "click"); 2411 Assert.deepEqual( 2412 Glean.pocket.fetchTimestamp.testGetValue(), 2413 FETCH_TIMESTAMP 2414 ); 2415 Assert.deepEqual( 2416 Glean.pocket.newtabCreationTimestamp.testGetValue(), 2417 NEWTAB_CREATION_TIMESTAMP 2418 ); 2419 resolve(); 2420 }); 2421 }); 2422 2423 instance.handleDiscoveryStreamUserEvent(action); 2424 2425 let clicks = Glean.pocket.click.testGetValue(); 2426 Assert.equal(clicks.length, 1, "Recorded 1 click"); 2427 Assert.deepEqual(clicks[0].extra, { 2428 newtab_visit_id: SESSION_ID, 2429 is_sponsored: String(true), 2430 position: String(ACTION_POSITION), 2431 tile_id: String(448685088), 2432 content_redacted: String(true), 2433 }); 2434 2435 await pingSubmitted; 2436 2437 sandbox.restore(); 2438 } 2439 ); 2440 2441 add_task( 2442 async function test_handleAboutSponsoredTopSites_record_showPrivacyClick() { 2443 info( 2444 "TelemetryFeed.handleAboutSponsoredTopSites should record a Glean " + 2445 "topsites.showPrivacyClick event on action" 2446 ); 2447 2448 let sandbox = sinon.createSandbox(); 2449 let instance = new TelemetryFeed(); 2450 Services.fog.testResetFOG(); 2451 2452 let data = { 2453 position: 42, 2454 advertiser_name: "mozilla", 2455 tile_id: 4567, 2456 }; 2457 2458 const SESSION_ID = "decafc0ffee"; 2459 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2460 2461 instance.handleAboutSponsoredTopSites({ data }); 2462 2463 let clicks = Glean.topsites.showPrivacyClick.testGetValue(); 2464 Assert.equal(clicks.length, 1, "Recorded 1 click"); 2465 Assert.deepEqual(clicks[0].extra, { 2466 advertiser_name: data.advertiser_name, 2467 tile_id: String(data.tile_id), 2468 newtab_visit_id: SESSION_ID, 2469 position: String(data.position), 2470 }); 2471 2472 sandbox.restore(); 2473 } 2474 ); 2475 2476 add_task( 2477 async function test_handleAboutSponsoredTopSites_no_record_showPrivacyClick() { 2478 info( 2479 "TelemetryFeed.handleAboutSponsoredTopSites should not record a Glean " + 2480 "topsites.showPrivacyClick event if there's no session" 2481 ); 2482 2483 let sandbox = sinon.createSandbox(); 2484 let instance = new TelemetryFeed(); 2485 Services.fog.testResetFOG(); 2486 2487 let data = { 2488 position: 42, 2489 advertiser_name: "mozilla", 2490 tile_id: 4567, 2491 }; 2492 2493 sandbox.stub(instance.sessions, "get").returns(null); 2494 2495 instance.handleAboutSponsoredTopSites({ data }); 2496 2497 let clicks = Glean.topsites.showPrivacyClick.testGetValue(); 2498 Assert.ok(!clicks, "Did not record any clicks"); 2499 2500 sandbox.restore(); 2501 } 2502 ); 2503 2504 add_task(async function test_handleBlockUrl_no_record_dismisses() { 2505 info( 2506 "TelemetryFeed.handleBlockUrl shouldn't record events for pocket " + 2507 "cards' dismisses" 2508 ); 2509 2510 let sandbox = sinon.createSandbox(); 2511 let instance = new TelemetryFeed(); 2512 Services.fog.testResetFOG(); 2513 2514 const SESSION_ID = "decafc0ffee"; 2515 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2516 2517 let data = [ 2518 { 2519 // Shouldn't record anything for this one 2520 is_pocket_card: true, 2521 position: 43, 2522 tile_id: undefined, 2523 }, 2524 ]; 2525 2526 await instance.handleBlockUrl({ data }); 2527 2528 Assert.ok( 2529 !Glean.topsites.dismiss.testGetValue(), 2530 "Should not record a dismiss for Pocket cards" 2531 ); 2532 2533 sandbox.restore(); 2534 }); 2535 2536 add_task(async function test_handleBlockUrl_record_dismiss_on_action() { 2537 info( 2538 "TelemetryFeed.handleBlockUrl should record a topsites.dismiss event " + 2539 "on action" 2540 ); 2541 2542 let sandbox = sinon.createSandbox(); 2543 let instance = new TelemetryFeed(); 2544 Services.fog.testResetFOG(); 2545 2546 const SESSION_ID = "decafc0ffee"; 2547 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2548 2549 let data = [ 2550 { 2551 is_pocket_card: false, 2552 position: 42, 2553 advertiser_name: "mozilla", 2554 tile_id: 4567, 2555 isSponsoredTopSite: 1, // for some reason this is an int. 2556 }, 2557 ]; 2558 2559 await instance.handleBlockUrl({ data, source: "TOP_SITES" }); 2560 2561 let dismisses = Glean.topsites.dismiss.testGetValue(); 2562 Assert.equal(dismisses.length, 1, "Should have recorded 1 dismiss"); 2563 Assert.deepEqual(dismisses[0].extra, { 2564 advertiser_name: data[0].advertiser_name, 2565 tile_id: String(data[0].tile_id), 2566 newtab_visit_id: SESSION_ID, 2567 is_sponsored: String(!!data[0].isSponsoredTopSite), 2568 position: String(data[0].position), 2569 }); 2570 2571 sandbox.restore(); 2572 }); 2573 2574 add_task( 2575 async function test_handleBlockUrl_record_dismiss_on_nonsponsored_action() { 2576 info( 2577 "TelemetryFeed.handleBlockUrl should record a Glean topsites.dismiss " + 2578 "event on action on non-sponsored topsite" 2579 ); 2580 2581 let sandbox = sinon.createSandbox(); 2582 let instance = new TelemetryFeed(); 2583 Services.fog.testResetFOG(); 2584 2585 const SESSION_ID = "decafc0ffee"; 2586 sandbox.stub(instance.sessions, "get").returns({ session_id: SESSION_ID }); 2587 2588 let data = [ 2589 { 2590 is_pocket_card: false, 2591 position: 42, 2592 tile_id: undefined, 2593 }, 2594 ]; 2595 2596 await instance.handleBlockUrl({ data, source: "TOP_SITES" }); 2597 2598 let dismisses = Glean.topsites.dismiss.testGetValue(); 2599 Assert.equal(dismisses.length, 1, "Should have recorded 1 dismiss"); 2600 Assert.deepEqual(dismisses[0].extra, { 2601 newtab_visit_id: SESSION_ID, 2602 is_sponsored: String(false), 2603 position: String(data[0].position), 2604 }); 2605 2606 sandbox.restore(); 2607 } 2608 ); 2609 2610 add_task(async function test_handleBlockUrl_no_record_dismiss_on_no_session() { 2611 info( 2612 "TelemetryFeed.handleBlockUrl should not record a Glean " + 2613 "topsites.dismiss event if there's no session" 2614 ); 2615 2616 let sandbox = sinon.createSandbox(); 2617 let instance = new TelemetryFeed(); 2618 Services.fog.testResetFOG(); 2619 2620 sandbox.stub(instance.sessions, "get").returns(null); 2621 2622 let data = {}; 2623 2624 await instance.handleBlockUrl({ data }); 2625 2626 Assert.ok( 2627 !Glean.topsites.dismiss.testGetValue(), 2628 "Should not have recorded a dismiss" 2629 ); 2630 2631 sandbox.restore(); 2632 }); 2633 2634 add_task(function test_randomizeOrganicContentEvent() { 2635 info( 2636 "TelemetryFeed._randomizeOrganicContentEvent should return true or false" + 2637 " based on the given probability" 2638 ); 2639 let sandbox = sinon.createSandbox(); 2640 let instance = new TelemetryFeed(); 2641 2642 const computeRec = id => ({ 2643 corpus_item_id: `item-${id}`, 2644 topic: "a", 2645 is_sponsored: false, 2646 section_id: "section", 2647 section_position: 3, 2648 }); 2649 const allRecs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(computeRec); 2650 sandbox.stub(instance, "getRecommendationCount").returns(allRecs.length); 2651 sandbox.stub(instance, "getAllRecommendations").returns(allRecs); 2652 instance._privateRandomContentTelemetryProbablityValues = { epsilon: 30 }; 2653 let decideStub = sandbox.stub(NewTabContentPing, "decideWithProbability"); 2654 decideStub.returns(true); 2655 let result = instance.randomizeOrganicContentEvent(allRecs[0]); 2656 Assert.equal(result, allRecs[0]); 2657 Assert.ok(decideStub.calledOnce, "decideWithProbability was called once"); 2658 const [probUsed] = decideStub.firstCall.args; 2659 Assert.greater(probUsed, 0.9); // Epsilon 30 is very high probability 2660 Assert.less(probUsed, 1.01); 2661 2662 // Run again - randomization kicks in 2663 decideStub.returns(false); 2664 sandbox.stub(NewTabContentPing, "secureRandIntInRange").returns(3); 2665 result = instance.randomizeOrganicContentEvent(allRecs[0]); 2666 Assert.equal(probUsed, decideStub.lastCall.args[0]); 2667 Assert.deepEqual(result, allRecs[3]); 2668 2669 sandbox.restore(); 2670 }); 2671 2672 add_task(async function test_handleSpocPlaceholderDuration_records_metric() { 2673 info( 2674 "TelemetryFeed.handleSpocPlaceholderDuration should record the " + 2675 "spoc_placeholder_duration metric" 2676 ); 2677 2678 let instance = new TelemetryFeed(); 2679 Services.fog.testResetFOG(); 2680 2681 const DURATION_MS = 150; 2682 let action = { 2683 type: actionTypes.DISCOVERY_STREAM_SPOC_PLACEHOLDER_DURATION, 2684 data: { duration: DURATION_MS }, 2685 }; 2686 2687 instance.handleSpocPlaceholderDuration(action); 2688 2689 let recordedDuration = Glean.pocket.spocPlaceholderDuration.testGetValue(); 2690 Assert.ok(recordedDuration, "Metric should be recorded"); 2691 Assert.equal(recordedDuration.count, 1, "Should have 1 sample"); 2692 Assert.greaterOrEqual( 2693 recordedDuration.sum, 2694 DURATION_MS, 2695 "Duration should be at least the input value" 2696 ); 2697 }); 2698 2699 add_task(async function test_handleSpocPlaceholderDuration_ignores_negative() { 2700 info( 2701 "TelemetryFeed.handleSpocPlaceholderDuration should ignore negative durations" 2702 ); 2703 2704 let instance = new TelemetryFeed(); 2705 Services.fog.testResetFOG(); 2706 2707 let action = { 2708 type: actionTypes.DISCOVERY_STREAM_SPOC_PLACEHOLDER_DURATION, 2709 data: { duration: -1 }, 2710 }; 2711 2712 instance.handleSpocPlaceholderDuration(action); 2713 2714 let recordedDuration = Glean.pocket.spocPlaceholderDuration.testGetValue(); 2715 Assert.equal( 2716 recordedDuration, 2717 null, 2718 "Metric should not be recorded for negative duration" 2719 ); 2720 }); 2721 2722 add_task(async function test_handleSpocPlaceholderDuration_ignores_undefined() { 2723 info( 2724 "TelemetryFeed.handleSpocPlaceholderDuration should ignore undefined durations" 2725 ); 2726 2727 let instance = new TelemetryFeed(); 2728 Services.fog.testResetFOG(); 2729 2730 let action = { 2731 type: actionTypes.DISCOVERY_STREAM_SPOC_PLACEHOLDER_DURATION, 2732 data: {}, 2733 }; 2734 2735 instance.handleSpocPlaceholderDuration(action); 2736 2737 let recordedDuration = Glean.pocket.spocPlaceholderDuration.testGetValue(); 2738 Assert.equal( 2739 recordedDuration, 2740 null, 2741 "Metric should not be recorded for undefined duration" 2742 ); 2743 });