TelemetryFeed.test.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378
  1. /* global Services */
  2. import {actionCreators as ac, actionTypes as at, actionUtils as au} from "common/Actions.jsm";
  3. import {
  4. ASRouterEventPing,
  5. BasePing,
  6. ImpressionStatsPing,
  7. PerfPing,
  8. SessionPing,
  9. SpocsFillPing,
  10. UndesiredPing,
  11. UserEventPing,
  12. } from "test/schemas/pings";
  13. import {FakePrefs, GlobalOverrider} from "test/unit/utils";
  14. import {ASRouterPreferences} from "lib/ASRouterPreferences.jsm";
  15. import injector from "inject!lib/TelemetryFeed.jsm";
  16. const FAKE_UUID = "{foo-123-foo}";
  17. const FAKE_ROUTER_MESSAGE_PROVIDER = [{id: "cfr", enabled: true}];
  18. const FAKE_ROUTER_MESSAGE_PROVIDER_COHORT = [{id: "cfr", enabled: true, cohort: "cohort_group"}];
  19. describe("TelemetryFeed", () => {
  20. let globals;
  21. let sandbox;
  22. let expectedUserPrefs;
  23. let browser = {getAttribute() { return "true"; }};
  24. let instance;
  25. let clock;
  26. let fakeHomePageUrl;
  27. let fakeHomePage;
  28. let fakeExtensionSettingsStore;
  29. class PingCentre {sendPing() {} uninit() {} sendStructuredIngestionPing() {}}
  30. class UTEventReporting {
  31. sendUserEvent() {}
  32. sendSessionEndEvent() {}
  33. sendTrailheadEnrollEvent() {}
  34. uninit() {}
  35. }
  36. class PerfService {
  37. getMostRecentAbsMarkStartByName() { return 1234; }
  38. mark() {}
  39. absNow() { return 123; }
  40. get timeOrigin() { return 123456; }
  41. }
  42. const perfService = new PerfService();
  43. const {
  44. TelemetryFeed,
  45. USER_PREFS_ENCODING,
  46. PREF_IMPRESSION_ID,
  47. TELEMETRY_PREF,
  48. EVENTS_TELEMETRY_PREF,
  49. STRUCTURED_INGESTION_TELEMETRY_PREF,
  50. STRUCTURED_INGESTION_ENDPOINT_PREF,
  51. } = injector({
  52. "common/PerfService.jsm": {perfService},
  53. "lib/UTEventReporting.jsm": {UTEventReporting},
  54. });
  55. beforeEach(() => {
  56. globals = new GlobalOverrider();
  57. sandbox = globals.sandbox;
  58. clock = sinon.useFakeTimers();
  59. fakeHomePageUrl = "about:home";
  60. fakeHomePage = {
  61. get() {
  62. return fakeHomePageUrl;
  63. },
  64. };
  65. fakeExtensionSettingsStore = {
  66. initialize() {
  67. return Promise.resolve();
  68. },
  69. getSetting() {},
  70. };
  71. sandbox.spy(global.Cu, "reportError");
  72. globals.set("gUUIDGenerator", {generateUUID: () => FAKE_UUID});
  73. globals.set("aboutNewTabService", {
  74. overridden: false,
  75. newTabURL: "",
  76. });
  77. globals.set("HomePage", fakeHomePage);
  78. globals.set("ExtensionSettingsStore", fakeExtensionSettingsStore);
  79. globals.set("PingCentre", PingCentre);
  80. globals.set("UTEventReporting", UTEventReporting);
  81. sandbox.stub(ASRouterPreferences, "providers").get(() => FAKE_ROUTER_MESSAGE_PROVIDER);
  82. instance = new TelemetryFeed();
  83. });
  84. afterEach(() => {
  85. clock.restore();
  86. globals.restore();
  87. FakePrefs.prototype.prefs = {};
  88. ASRouterPreferences.uninit();
  89. });
  90. describe("#init", () => {
  91. it("should add .pingCentre, a PingCentre instance", () => {
  92. assert.instanceOf(instance.pingCentre, PingCentre);
  93. });
  94. it("should add .pingCentreForASRouter, a PingCentre instance", () => {
  95. assert.instanceOf(instance.pingCentreForASRouter, PingCentre);
  96. });
  97. it("should add .utEvents, a UTEventReporting instance", () => {
  98. assert.instanceOf(instance.utEvents, UTEventReporting);
  99. });
  100. it("should make this.browserOpenNewtabStart() observe browser-open-newtab-start", () => {
  101. sandbox.spy(Services.obs, "addObserver");
  102. instance.init();
  103. assert.calledTwice(Services.obs.addObserver);
  104. assert.calledWithExactly(Services.obs.addObserver,
  105. instance.browserOpenNewtabStart, "browser-open-newtab-start");
  106. });
  107. it("should add window open listener", () => {
  108. sandbox.spy(Services.obs, "addObserver");
  109. instance.init();
  110. assert.calledTwice(Services.obs.addObserver);
  111. assert.calledWithExactly(Services.obs.addObserver,
  112. instance._addWindowListeners, "domwindowopened");
  113. });
  114. it("should add TabPinned event listener on new windows", () => {
  115. const stub = {addEventListener: sandbox.stub()};
  116. sandbox.spy(Services.obs, "addObserver");
  117. instance.init();
  118. assert.calledTwice(Services.obs.addObserver);
  119. const [cb] = Services.obs.addObserver.secondCall.args;
  120. cb(stub);
  121. assert.calledTwice(stub.addEventListener);
  122. assert.calledWithExactly(stub.addEventListener, "unload", instance.handleEvent);
  123. assert.calledWithExactly(stub.addEventListener, "TabPinned", instance.handleEvent);
  124. });
  125. it("should create impression id if none exists", () => {
  126. assert.equal(instance._impressionId, FAKE_UUID);
  127. });
  128. it("should set impression id if it exists", () => {
  129. FakePrefs.prototype.prefs = {};
  130. FakePrefs.prototype.prefs[PREF_IMPRESSION_ID] = "fakeImpressionId";
  131. assert.equal(new TelemetryFeed()._impressionId, "fakeImpressionId");
  132. });
  133. it("should register listeners on existing windows", () => {
  134. const stub = sandbox.stub();
  135. globals.set({
  136. Services: {
  137. ...Services,
  138. wm: {getEnumerator: () => [{addEventListener: stub}]},
  139. },
  140. });
  141. instance.init();
  142. assert.calledTwice(stub);
  143. assert.calledWithExactly(stub, "unload", instance.handleEvent);
  144. assert.calledWithExactly(stub, "TabPinned", instance.handleEvent);
  145. });
  146. describe("telemetry pref changes from false to true", () => {
  147. beforeEach(() => {
  148. FakePrefs.prototype.prefs = {};
  149. FakePrefs.prototype.prefs[TELEMETRY_PREF] = false;
  150. instance = new TelemetryFeed();
  151. assert.propertyVal(instance, "telemetryEnabled", false);
  152. });
  153. it("should set the enabled property to true", () => {
  154. instance._prefs.set(TELEMETRY_PREF, true);
  155. assert.propertyVal(instance, "telemetryEnabled", true);
  156. });
  157. });
  158. describe("events telemetry pref changes from false to true", () => {
  159. beforeEach(() => {
  160. FakePrefs.prototype.prefs = {};
  161. FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = false;
  162. instance = new TelemetryFeed();
  163. assert.propertyVal(instance, "eventTelemetryEnabled", false);
  164. });
  165. it("should set the enabled property to true", () => {
  166. instance._prefs.set(EVENTS_TELEMETRY_PREF, true);
  167. assert.propertyVal(instance, "eventTelemetryEnabled", true);
  168. });
  169. });
  170. describe("Structured Ingestion telemetry pref changes from false to true", () => {
  171. beforeEach(() => {
  172. FakePrefs.prototype.prefs = {};
  173. FakePrefs.prototype.prefs[STRUCTURED_INGESTION_TELEMETRY_PREF] = false;
  174. instance = new TelemetryFeed();
  175. assert.propertyVal(instance, "structuredIngestionTelemetryEnabled", false);
  176. });
  177. it("should set the enabled property to true", () => {
  178. instance._prefs.set(STRUCTURED_INGESTION_TELEMETRY_PREF, true);
  179. assert.propertyVal(instance, "structuredIngestionTelemetryEnabled", true);
  180. });
  181. });
  182. });
  183. describe("#handleEvent", () => {
  184. it("should dispatch a TAB_PINNED_EVENT", () => {
  185. sandbox.stub(instance, "sendEvent");
  186. globals.set({
  187. Services: {
  188. ...Services,
  189. wm: {getEnumerator: () => [{gBrowser: {tabs: [{pinned: true}]}}]},
  190. },
  191. });
  192. instance.handleEvent({type: "TabPinned", target: {}});
  193. assert.calledOnce(instance.sendEvent);
  194. const [ping] = instance.sendEvent.firstCall.args;
  195. assert.propertyVal(ping, "event", "TABPINNED");
  196. assert.propertyVal(ping, "source", "TAB_CONTEXT_MENU");
  197. assert.propertyVal(ping, "session_id", "n/a");
  198. assert.propertyVal(ping.value, "total_pinned_tabs", 1);
  199. });
  200. it("should skip private windows", () => {
  201. sandbox.stub(instance, "sendEvent");
  202. globals.set({PrivateBrowsingUtils: {isWindowPrivate: () => true}});
  203. instance.handleEvent({type: "TabPinned", target: {}});
  204. assert.notCalled(instance.sendEvent);
  205. });
  206. it("should return the correct value for total_pinned_tabs", () => {
  207. sandbox.stub(instance, "sendEvent");
  208. globals.set({
  209. Services: {
  210. ...Services,
  211. wm: {
  212. getEnumerator: () => [{
  213. gBrowser: {tabs: [{pinned: true}, {pinned: false}]},
  214. }],
  215. },
  216. },
  217. });
  218. instance.handleEvent({type: "TabPinned", target: {}});
  219. assert.calledOnce(instance.sendEvent);
  220. const [ping] = instance.sendEvent.firstCall.args;
  221. assert.propertyVal(ping, "event", "TABPINNED");
  222. assert.propertyVal(ping, "source", "TAB_CONTEXT_MENU");
  223. assert.propertyVal(ping, "session_id", "n/a");
  224. assert.propertyVal(ping.value, "total_pinned_tabs", 1);
  225. });
  226. it("should return the correct value for total_pinned_tabs (when private windows are open)", () => {
  227. sandbox.stub(instance, "sendEvent");
  228. const privateWinStub = sandbox.stub().onCall(0).returns(false)
  229. .onCall(1)
  230. .returns(true);
  231. globals.set({PrivateBrowsingUtils: {isWindowPrivate: privateWinStub}});
  232. globals.set({
  233. Services: {
  234. ...Services,
  235. wm: {
  236. getEnumerator: () => [{
  237. gBrowser: {tabs: [{pinned: true}, {pinned: true}]},
  238. }],
  239. },
  240. },
  241. });
  242. instance.handleEvent({type: "TabPinned", target: {}});
  243. assert.calledOnce(instance.sendEvent);
  244. const [ping] = instance.sendEvent.firstCall.args;
  245. assert.propertyVal(ping.value, "total_pinned_tabs", 0);
  246. });
  247. it("should unregister the event listeners", () => {
  248. const stub = {removeEventListener: sandbox.stub()};
  249. instance.handleEvent({type: "unload", target: stub});
  250. assert.calledTwice(stub.removeEventListener);
  251. assert.calledWithExactly(stub.removeEventListener, "unload", instance.handleEvent);
  252. assert.calledWithExactly(stub.removeEventListener, "TabPinned", instance.handleEvent);
  253. });
  254. });
  255. describe("#addSession", () => {
  256. it("should add a session and return it", () => {
  257. const session = instance.addSession("foo");
  258. assert.equal(instance.sessions.get("foo"), session);
  259. });
  260. it("should set the session_id", () => {
  261. sandbox.spy(global.gUUIDGenerator, "generateUUID");
  262. const session = instance.addSession("foo");
  263. assert.calledOnce(global.gUUIDGenerator.generateUUID);
  264. assert.equal(session.session_id, global.gUUIDGenerator.generateUUID.firstCall.returnValue);
  265. });
  266. it("should set the page if a url parameter is given", () => {
  267. const session = instance.addSession("foo", "about:monkeys");
  268. assert.propertyVal(session, "page", "about:monkeys");
  269. });
  270. it("should set the page prop to 'unknown' if no URL parameter given", () => {
  271. const session = instance.addSession("foo");
  272. assert.propertyVal(session, "page", "unknown");
  273. });
  274. it("should set the perf type when lacking timestamp", () => {
  275. const session = instance.addSession("foo");
  276. assert.propertyVal(session.perf, "load_trigger_type", "unexpected");
  277. });
  278. it("should set load_trigger_type to first_window_opened on the first about:home seen", () => {
  279. const session = instance.addSession("foo", "about:home");
  280. assert.propertyVal(session.perf, "load_trigger_type",
  281. "first_window_opened");
  282. });
  283. it("should not set load_trigger_type to first_window_opened on the second about:home seen", () => {
  284. instance.addSession("foo", "about:home");
  285. const session2 = instance.addSession("foo", "about:home");
  286. assert.notPropertyVal(session2.perf, "load_trigger_type",
  287. "first_window_opened");
  288. });
  289. it("should set load_trigger_ts to the value of perfService.timeOrigin", () => {
  290. const session = instance.addSession("foo", "about:home");
  291. assert.propertyVal(session.perf, "load_trigger_ts",
  292. 123456);
  293. });
  294. it("should create a valid session ping on the first about:home seen", () => {
  295. // Add a session
  296. const portID = "foo";
  297. const session = instance.addSession(portID, "about:home");
  298. // Create a ping referencing the session
  299. const ping = instance.createSessionEndEvent(session);
  300. assert.validate(ping, SessionPing);
  301. });
  302. it("should be a valid ping with the data_late_by_ms perf", () => {
  303. // Add a session
  304. const portID = "foo";
  305. const session = instance.addSession(portID, "about:home");
  306. instance.saveSessionPerfData("foo", {topsites_data_late_by_ms: 10});
  307. instance.saveSessionPerfData("foo", {highlights_data_late_by_ms: 20});
  308. // Create a ping referencing the session
  309. const ping = instance.createSessionEndEvent(session);
  310. assert.validate(ping, SessionPing);
  311. assert.propertyVal(instance.sessions.get("foo").perf,
  312. "highlights_data_late_by_ms", 20);
  313. assert.propertyVal(instance.sessions.get("foo").perf,
  314. "topsites_data_late_by_ms", 10);
  315. });
  316. it("should be a valid ping with the topsites stats perf", () => {
  317. // Add a session
  318. const portID = "foo";
  319. const session = instance.addSession(portID, "about:home");
  320. instance.saveSessionPerfData("foo", {
  321. topsites_icon_stats: {
  322. "custom_screenshot": 0,
  323. "screenshot_with_icon": 2,
  324. "screenshot": 1,
  325. "tippytop": 2,
  326. "rich_icon": 1,
  327. "no_image": 0,
  328. },
  329. topsites_pinned: 3,
  330. topsites_search_shortcuts: 2,
  331. });
  332. // Create a ping referencing the session
  333. const ping = instance.createSessionEndEvent(session);
  334. assert.validate(ping, SessionPing);
  335. assert.propertyVal(instance.sessions.get("foo").perf.topsites_icon_stats,
  336. "screenshot_with_icon", 2);
  337. assert.equal(instance.sessions.get("foo").perf.topsites_pinned, 3);
  338. assert.equal(instance.sessions.get("foo").perf.topsites_search_shortcuts, 2);
  339. });
  340. });
  341. describe("#browserOpenNewtabStart", () => {
  342. it("should call perfService.mark with browser-open-newtab-start", () => {
  343. sandbox.stub(perfService, "mark");
  344. instance.browserOpenNewtabStart();
  345. assert.calledOnce(perfService.mark);
  346. assert.calledWithExactly(perfService.mark, "browser-open-newtab-start");
  347. });
  348. });
  349. describe("#endSession", () => {
  350. it("should not throw if there is no session for the given port ID", () => {
  351. assert.doesNotThrow(() => instance.endSession("doesn't exist"));
  352. });
  353. it("should add a session_duration integer if there is a visibility_event_rcvd_ts", () => {
  354. sandbox.stub(instance, "sendEvent");
  355. const session = instance.addSession("foo");
  356. session.perf.visibility_event_rcvd_ts = 444.4732;
  357. instance.endSession("foo");
  358. assert.isNumber(session.session_duration);
  359. assert.ok(Number.isInteger(session.session_duration),
  360. "session_duration should be an integer");
  361. });
  362. it("shouldn't send session ping if there's no visibility_event_rcvd_ts", () => {
  363. sandbox.stub(instance, "sendEvent");
  364. instance.addSession("foo");
  365. instance.endSession("foo");
  366. assert.notCalled(instance.sendEvent);
  367. assert.isFalse(instance.sessions.has("foo"));
  368. });
  369. it("should remove the session from .sessions", () => {
  370. sandbox.stub(instance, "sendEvent");
  371. instance.addSession("foo");
  372. instance.endSession("foo");
  373. assert.isFalse(instance.sessions.has("foo"));
  374. });
  375. it("should call createSessionSendEvent and sendEvent with the sesssion", () => {
  376. FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
  377. FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = true;
  378. instance = new TelemetryFeed();
  379. sandbox.stub(instance, "sendEvent");
  380. sandbox.stub(instance, "createSessionEndEvent");
  381. sandbox.stub(instance.utEvents, "sendSessionEndEvent");
  382. const session = instance.addSession("foo");
  383. session.perf.visibility_event_rcvd_ts = 444.4732;
  384. instance.endSession("foo");
  385. // Did we call sendEvent with the result of createSessionEndEvent?
  386. assert.calledWith(instance.createSessionEndEvent, session);
  387. let sessionEndEvent = instance.createSessionEndEvent.firstCall.returnValue;
  388. assert.calledWith(instance.sendEvent, sessionEndEvent);
  389. assert.calledWith(instance.utEvents.sendSessionEndEvent, sessionEndEvent);
  390. });
  391. });
  392. describe("ping creators", () => {
  393. beforeEach(() => {
  394. FakePrefs.prototype.prefs = {};
  395. for (const pref of Object.keys(USER_PREFS_ENCODING)) {
  396. FakePrefs.prototype.prefs[pref] = true;
  397. expectedUserPrefs |= USER_PREFS_ENCODING[pref];
  398. }
  399. instance.init();
  400. });
  401. describe("#createPing", () => {
  402. it("should create a valid base ping without a session if no portID is supplied", async () => {
  403. const ping = await instance.createPing();
  404. assert.validate(ping, BasePing);
  405. assert.notProperty(ping, "session_id");
  406. assert.notProperty(ping, "page");
  407. });
  408. it("should create a valid base ping with session info if a portID is supplied", async () => {
  409. // Add a session
  410. const portID = "foo";
  411. instance.addSession(portID, "about:home");
  412. const sessionID = instance.sessions.get(portID).session_id;
  413. // Create a ping referencing the session
  414. const ping = await instance.createPing(portID);
  415. assert.validate(ping, BasePing);
  416. // Make sure we added the right session-related stuff to the ping
  417. assert.propertyVal(ping, "session_id", sessionID);
  418. assert.propertyVal(ping, "page", "about:home");
  419. });
  420. it("should create an unexpected base ping if no session yet portID is supplied", async () => {
  421. const ping = await instance.createPing("foo");
  422. assert.validate(ping, BasePing);
  423. assert.propertyVal(ping, "page", "unknown");
  424. assert.propertyVal(instance.sessions.get("foo").perf, "load_trigger_type", "unexpected");
  425. });
  426. it("should create a base ping with user_prefs", async () => {
  427. const ping = await instance.createPing("foo");
  428. assert.validate(ping, BasePing);
  429. assert.propertyVal(ping, "user_prefs", expectedUserPrefs);
  430. });
  431. });
  432. describe("#createUserEvent", () => {
  433. it("should create a valid event", async () => {
  434. const portID = "foo";
  435. const data = {source: "TOP_SITES", event: "CLICK"};
  436. const action = ac.AlsoToMain(ac.UserEvent(data), portID);
  437. const session = instance.addSession(portID);
  438. const ping = await instance.createUserEvent(action);
  439. // Is it valid?
  440. assert.validate(ping, UserEventPing);
  441. // Does it have the right session_id?
  442. assert.propertyVal(ping, "session_id", session.session_id);
  443. });
  444. });
  445. describe("#createUndesiredEvent", () => {
  446. it("should create a valid event without a session", async () => {
  447. const action = ac.UndesiredEvent({source: "TOP_SITES", event: "MISSING_IMAGE", value: 10});
  448. const ping = await instance.createUndesiredEvent(action);
  449. // Is it valid?
  450. assert.validate(ping, UndesiredPing);
  451. // Does it have the right value?
  452. assert.propertyVal(ping, "value", 10);
  453. });
  454. it("should create a valid event with a session", async () => {
  455. const portID = "foo";
  456. const data = {source: "TOP_SITES", event: "MISSING_IMAGE", value: 10};
  457. const action = ac.AlsoToMain(ac.UndesiredEvent(data), portID);
  458. const session = instance.addSession(portID);
  459. const ping = await instance.createUndesiredEvent(action);
  460. // Is it valid?
  461. assert.validate(ping, UndesiredPing);
  462. // Does it have the right session_id?
  463. assert.propertyVal(ping, "session_id", session.session_id);
  464. // Does it have the right value?
  465. assert.propertyVal(ping, "value", 10);
  466. });
  467. describe("#validate *_data_late_by_ms", () => {
  468. it("should create a valid highlights_data_late_by_ms ping", () => {
  469. const data = {
  470. type: at.TELEMETRY_UNDESIRED_EVENT,
  471. data: {
  472. source: "HIGHLIGHTS",
  473. event: `highlights_data_late_by_ms`,
  474. value: 2,
  475. },
  476. };
  477. const ping = instance.createUndesiredEvent(data);
  478. assert.validate(ping, UndesiredPing);
  479. assert.propertyVal(ping, "value", data.data.value);
  480. assert.propertyVal(ping, "event", data.data.event);
  481. });
  482. });
  483. });
  484. describe("#createPerformanceEvent", () => {
  485. it("should create a valid event without a session", async () => {
  486. const action = ac.PerfEvent({event: "SCREENSHOT_FINISHED", value: 100});
  487. const ping = await instance.createPerformanceEvent(action);
  488. // Is it valid?
  489. assert.validate(ping, PerfPing);
  490. // Does it have the right value?
  491. assert.propertyVal(ping, "value", 100);
  492. });
  493. });
  494. describe("#createSessionEndEvent", () => {
  495. it("should create a valid event", async () => {
  496. const ping = await instance.createSessionEndEvent({
  497. session_id: FAKE_UUID,
  498. page: "about:newtab",
  499. session_duration: 12345,
  500. perf: {
  501. load_trigger_ts: 10,
  502. load_trigger_type: "menu_plus_or_keyboard",
  503. visibility_event_rcvd_ts: 20,
  504. is_preloaded: true,
  505. },
  506. });
  507. // Is it valid?
  508. assert.validate(ping, SessionPing);
  509. assert.propertyVal(ping, "session_id", FAKE_UUID);
  510. assert.propertyVal(ping, "page", "about:newtab");
  511. assert.propertyVal(ping, "session_duration", 12345);
  512. });
  513. it("should create a valid unexpected session event", async () => {
  514. const ping = await instance.createSessionEndEvent({
  515. session_id: FAKE_UUID,
  516. page: "about:newtab",
  517. session_duration: 12345,
  518. perf: {
  519. load_trigger_type: "unexpected",
  520. is_preloaded: true,
  521. },
  522. });
  523. // Is it valid?
  524. assert.validate(ping, SessionPing);
  525. assert.propertyVal(ping, "session_id", FAKE_UUID);
  526. assert.propertyVal(ping, "page", "about:newtab");
  527. assert.propertyVal(ping, "session_duration", 12345);
  528. assert.propertyVal(ping.perf, "load_trigger_type", "unexpected");
  529. });
  530. });
  531. });
  532. describe("#createImpressionStats", () => {
  533. it("should create a valid impression stats ping", async () => {
  534. const tiles = [{id: 10001}, {id: 10002}, {id: 10003}];
  535. const action = ac.ImpressionStats({source: "POCKET", tiles});
  536. const ping = await instance.createImpressionStats(au.getPortIdOfSender(action), action.data);
  537. assert.validate(ping, ImpressionStatsPing);
  538. assert.propertyVal(ping, "source", "POCKET");
  539. assert.propertyVal(ping, "tiles", tiles);
  540. });
  541. it("should create a valid click ping", async () => {
  542. const tiles = [{id: 10001, pos: 2}];
  543. const action = ac.ImpressionStats({source: "POCKET", tiles, click: 0});
  544. const ping = await instance.createImpressionStats(au.getPortIdOfSender(action), action.data);
  545. assert.validate(ping, ImpressionStatsPing);
  546. assert.propertyVal(ping, "click", 0);
  547. assert.propertyVal(ping, "tiles", tiles);
  548. });
  549. it("should create a valid block ping", async () => {
  550. const tiles = [{id: 10001, pos: 2}];
  551. const action = ac.ImpressionStats({source: "POCKET", tiles, block: 0});
  552. const ping = await instance.createImpressionStats(au.getPortIdOfSender(action), action.data);
  553. assert.validate(ping, ImpressionStatsPing);
  554. assert.propertyVal(ping, "block", 0);
  555. assert.propertyVal(ping, "tiles", tiles);
  556. });
  557. it("should create a valid pocket ping", async () => {
  558. const tiles = [{id: 10001, pos: 2}];
  559. const action = ac.ImpressionStats({source: "POCKET", tiles, pocket: 0});
  560. const ping = await instance.createImpressionStats(au.getPortIdOfSender(action), action.data);
  561. assert.validate(ping, ImpressionStatsPing);
  562. assert.propertyVal(ping, "pocket", 0);
  563. assert.propertyVal(ping, "tiles", tiles);
  564. });
  565. });
  566. describe("#createSpocsFillPing", () => {
  567. it("should create a valid SPOCS Fill ping", async () => {
  568. const spocFills = [
  569. {id: 10001, displayed: 0, reason: "frequency_cap", full_recalc: 1},
  570. {id: 10002, displayed: 0, reason: "blocked_by_user", full_recalc: 1},
  571. {id: 10003, displayed: 1, reason: "n/a", full_recalc: 1},
  572. ];
  573. const action = ac.DiscoveryStreamSpocsFill({spoc_fills: spocFills});
  574. const ping = await instance.createSpocsFillPing(action.data);
  575. assert.validate(ping, SpocsFillPing);
  576. assert.propertyVal(ping, "spoc_fills", spocFills);
  577. });
  578. });
  579. describe("#applyCFRPolicy", () => {
  580. it("should use client_id and message_id in prerelease", () => {
  581. globals.set("UpdateUtils", {getUpdateChannel() { return "nightly"; }});
  582. const data = {
  583. action: "cfr_user_event",
  584. source: "CFR",
  585. event: "IMPRESSION",
  586. client_id: "some_client_id",
  587. impression_id: "some_impression_id",
  588. message_id: "cfr_message_01",
  589. bucket_id: "cfr_bucket_01",
  590. };
  591. const ping = instance.applyCFRPolicy(data);
  592. assert.isUndefined(ping.client_id);
  593. assert.propertyVal(ping, "impression_id", "n/a");
  594. assert.propertyVal(ping, "message_id", "cfr_message_01");
  595. assert.isUndefined(ping.bucket_id);
  596. });
  597. it("should use impression_id and bucket_id in release", () => {
  598. globals.set("UpdateUtils", {getUpdateChannel() { return "release"; }});
  599. const data = {
  600. action: "cfr_user_event",
  601. source: "CFR",
  602. event: "IMPRESSION",
  603. client_id: "some_client_id",
  604. impression_id: "some_impression_id",
  605. message_id: "cfr_message_01",
  606. bucket_id: "cfr_bucket_01",
  607. };
  608. const ping = instance.applyCFRPolicy(data);
  609. assert.propertyVal(ping, "impression_id", FAKE_UUID);
  610. assert.propertyVal(ping, "client_id", "n/a");
  611. assert.propertyVal(ping, "message_id", "cfr_bucket_01");
  612. assert.isUndefined(ping.bucket_id);
  613. });
  614. it("should use client_id and message_id in the experiment cohort in release", () => {
  615. globals.set("UpdateUtils", {getUpdateChannel() { return "release"; }});
  616. sandbox.stub(ASRouterPreferences, "providers").get(() => FAKE_ROUTER_MESSAGE_PROVIDER_COHORT);
  617. const data = {
  618. action: "cfr_user_event",
  619. source: "CFR",
  620. event: "IMPRESSION",
  621. client_id: "some_client_id",
  622. impression_id: "some_impression_id",
  623. message_id: "cfr_message_01",
  624. bucket_id: "cfr_bucket_01",
  625. };
  626. const ping = instance.applyCFRPolicy(data);
  627. assert.isUndefined(ping.client_id);
  628. assert.propertyVal(ping, "impression_id", "n/a");
  629. assert.propertyVal(ping, "message_id", "cfr_message_01");
  630. assert.isUndefined(ping.bucket_id);
  631. });
  632. });
  633. describe("#applySnippetsPolicy", () => {
  634. it("should drop client_id and unset impression_id", () => {
  635. const data = {
  636. action: "snippets_user_event",
  637. source: "SNIPPETS",
  638. event: "IMPRESSION",
  639. client_id: "n/a",
  640. impression_id: "some_impression_id",
  641. message_id: "snippets_message_01",
  642. };
  643. const ping = instance.applySnippetsPolicy(data);
  644. assert.isUndefined(ping.client_id);
  645. assert.propertyVal(ping, "impression_id", "n/a");
  646. assert.propertyVal(ping, "message_id", "snippets_message_01");
  647. });
  648. });
  649. describe("#applyOnboardingPolicy", () => {
  650. it("should drop client_id and unset impression_id", () => {
  651. const data = {
  652. action: "onboarding_user_event",
  653. source: "ONBOARDING",
  654. event: "CLICK_BUTTION",
  655. client_id: "n/a",
  656. impression_id: "some_impression_id",
  657. message_id: "onboarding_message_01",
  658. };
  659. const ping = instance.applyOnboardingPolicy(data);
  660. assert.isUndefined(ping.client_id);
  661. assert.propertyVal(ping, "impression_id", "n/a");
  662. assert.propertyVal(ping, "message_id", "onboarding_message_01");
  663. });
  664. });
  665. describe("#createASRouterEvent", () => {
  666. it("should create a valid AS Router event", async () => {
  667. const data = {
  668. action: "snippet_user_event",
  669. source: "SNIPPETS",
  670. event: "CLICK",
  671. message_id: "snippets_message_01",
  672. };
  673. const action = ac.ASRouterUserEvent(data);
  674. const ping = await instance.createASRouterEvent(action);
  675. assert.validate(ping, ASRouterEventPing);
  676. assert.propertyVal(ping, "client_id", "n/a");
  677. assert.propertyVal(ping, "source", "SNIPPETS");
  678. assert.propertyVal(ping, "event", "CLICK");
  679. });
  680. it("should call applyCFRPolicy if action equals to cfr_user_event", async () => {
  681. const data = {
  682. action: "cfr_user_event",
  683. source: "CFR",
  684. event: "IMPRESSION",
  685. message_id: "cfr_message_01",
  686. };
  687. sandbox.stub(instance, "applyCFRPolicy");
  688. const action = ac.ASRouterUserEvent(data);
  689. await instance.createASRouterEvent(action);
  690. assert.calledOnce(instance.applyCFRPolicy);
  691. });
  692. it("should call applySnippetsPolicy if action equals to snippets_user_event", async () => {
  693. const data = {
  694. action: "snippets_user_event",
  695. source: "SNIPPETS",
  696. event: "IMPRESSION",
  697. message_id: "snippets_message_01",
  698. };
  699. sandbox.stub(instance, "applySnippetsPolicy");
  700. const action = ac.ASRouterUserEvent(data);
  701. await instance.createASRouterEvent(action);
  702. assert.calledOnce(instance.applySnippetsPolicy);
  703. });
  704. it("should call applyOnboardingPolicy if action equals to onboarding_user_event", async () => {
  705. const data = {
  706. action: "onboarding_user_event",
  707. source: "ONBOARDING",
  708. event: "CLICK_BUTTON",
  709. message_id: "onboarding_message_01",
  710. };
  711. sandbox.stub(instance, "applyOnboardingPolicy");
  712. const action = ac.ASRouterUserEvent(data);
  713. await instance.createASRouterEvent(action);
  714. assert.calledOnce(instance.applyOnboardingPolicy);
  715. });
  716. });
  717. describe("#sendEvent", () => {
  718. it("should call PingCentre", async () => {
  719. FakePrefs.prototype.prefs.telemetry = true;
  720. const event = {};
  721. instance = new TelemetryFeed();
  722. sandbox.stub(instance.pingCentre, "sendPing");
  723. await instance.sendEvent(event);
  724. assert.calledWith(instance.pingCentre.sendPing, event);
  725. });
  726. });
  727. describe("#sendUTEvent", () => {
  728. it("should call the UT event function passed in", async () => {
  729. FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
  730. FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = true;
  731. const event = {};
  732. instance = new TelemetryFeed();
  733. sandbox.stub(instance.utEvents, "sendUserEvent");
  734. await instance.sendUTEvent(event, instance.utEvents.sendUserEvent);
  735. assert.calledWith(instance.utEvents.sendUserEvent, event);
  736. });
  737. });
  738. describe("#sendStructuredIngestionEvent", () => {
  739. it("should call PingCentre sendStructuredIngestionPing", async () => {
  740. FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
  741. FakePrefs.prototype.prefs[STRUCTURED_INGESTION_TELEMETRY_PREF] = true;
  742. const event = {};
  743. instance = new TelemetryFeed();
  744. sandbox.stub(instance.pingCentre, "sendStructuredIngestionPing");
  745. await instance.sendStructuredIngestionEvent(event, "http://foo.com/base/");
  746. assert.calledWith(instance.pingCentre.sendStructuredIngestionPing, event);
  747. });
  748. });
  749. describe("#sendASRouterEvent", () => {
  750. it("should call PingCentre for AS Router", async () => {
  751. FakePrefs.prototype.prefs.telemetry = true;
  752. const event = {};
  753. instance = new TelemetryFeed();
  754. sandbox.stub(instance.pingCentreForASRouter, "sendPing");
  755. instance.sendASRouterEvent(event);
  756. assert.calledWith(instance.pingCentreForASRouter.sendPing, event);
  757. });
  758. });
  759. describe("#setLoadTriggerInfo", () => {
  760. it("should call saveSessionPerfData w/load_trigger_{ts,type} data", () => {
  761. const stub = sandbox.stub(instance, "saveSessionPerfData");
  762. sandbox.stub(perfService, "getMostRecentAbsMarkStartByName").returns(777);
  763. instance.addSession("port123");
  764. instance.setLoadTriggerInfo("port123");
  765. assert.calledWith(stub, "port123", {
  766. load_trigger_ts: 777,
  767. load_trigger_type: "menu_plus_or_keyboard",
  768. });
  769. });
  770. it("should not call saveSessionPerfData when getting mark throws", () => {
  771. const stub = sandbox.stub(instance, "saveSessionPerfData");
  772. sandbox.stub(perfService, "getMostRecentAbsMarkStartByName").throws();
  773. instance.addSession("port123");
  774. instance.setLoadTriggerInfo("port123");
  775. assert.notCalled(stub);
  776. });
  777. });
  778. describe("#saveSessionPerfData", () => {
  779. it("should update the given session with the given data", () => {
  780. instance.addSession("port123");
  781. assert.notProperty(instance.sessions.get("port123"), "fake_ts");
  782. const data = {fake_ts: 456, other_fake_ts: 789};
  783. instance.saveSessionPerfData("port123", data);
  784. assert.include(instance.sessions.get("port123").perf, data);
  785. });
  786. it("should call setLoadTriggerInfo if data has visibility_event_rcvd_ts", () => {
  787. sandbox.stub(instance, "setLoadTriggerInfo");
  788. instance.addSession("port123");
  789. const data = {visibility_event_rcvd_ts: 444455};
  790. instance.saveSessionPerfData("port123", data);
  791. assert.calledOnce(instance.setLoadTriggerInfo);
  792. assert.calledWithExactly(instance.setLoadTriggerInfo, "port123");
  793. assert.include(instance.sessions.get("port123").perf, data);
  794. });
  795. it("shouldn't call setLoadTriggerInfo if data has no visibility_event_rcvd_ts", () => {
  796. sandbox.stub(instance, "setLoadTriggerInfo");
  797. instance.addSession("port123");
  798. instance.saveSessionPerfData("port123", {monkeys_ts: 444455});
  799. assert.notCalled(instance.setLoadTriggerInfo);
  800. });
  801. it("should not call setLoadTriggerInfo when url is about:home", () => {
  802. sandbox.stub(instance, "setLoadTriggerInfo");
  803. instance.addSession("port123", "about:home");
  804. const data = {visibility_event_rcvd_ts: 444455};
  805. instance.saveSessionPerfData("port123", data);
  806. assert.notCalled(instance.setLoadTriggerInfo);
  807. });
  808. it("should call maybeRecordTopsitesPainted when url is about:home and topsites_first_painted_ts is given", () => {
  809. const topsites_first_painted_ts = 44455;
  810. const data = {topsites_first_painted_ts};
  811. const spy = sandbox.spy();
  812. sandbox.stub(Services.prefs, "getIntPref").returns(1);
  813. globals.set("aboutNewTabService", {
  814. overridden: false,
  815. newTabURL: "",
  816. maybeRecordTopsitesPainted: spy,
  817. });
  818. instance.addSession("port123", "about:home");
  819. instance.saveSessionPerfData("port123", data);
  820. assert.calledOnce(spy);
  821. assert.calledWith(spy, topsites_first_painted_ts);
  822. });
  823. });
  824. describe("#uninit", () => {
  825. it("should call .pingCentre.uninit", () => {
  826. const stub = sandbox.stub(instance.pingCentre, "uninit");
  827. instance.uninit();
  828. assert.calledOnce(stub);
  829. });
  830. it("should call .utEvents.uninit", () => {
  831. const stub = sandbox.stub(instance.utEvents, "uninit");
  832. instance.uninit();
  833. assert.calledOnce(stub);
  834. });
  835. it("should call .pingCentreForASRouter.uninit", () => {
  836. const stub = sandbox.stub(instance.pingCentreForASRouter, "uninit");
  837. instance.uninit();
  838. assert.calledOnce(stub);
  839. });
  840. it("should make this.browserOpenNewtabStart() stop observing browser-open-newtab-start and domwindowopened", async () => {
  841. await instance.init();
  842. sandbox.spy(Services.obs, "removeObserver");
  843. sandbox.stub(instance.pingCentre, "uninit");
  844. await instance.uninit();
  845. assert.calledTwice(Services.obs.removeObserver);
  846. assert.calledWithExactly(Services.obs.removeObserver,
  847. instance.browserOpenNewtabStart, "browser-open-newtab-start");
  848. assert.calledWithExactly(Services.obs.removeObserver,
  849. instance._addWindowListeners, "domwindowopened");
  850. });
  851. });
  852. describe("#onAction", () => {
  853. beforeEach(() => {
  854. FakePrefs.prototype.prefs = {};
  855. });
  856. it("should call .init() on an INIT action", () => {
  857. const init = sandbox.stub(instance, "init");
  858. const sendPageTakeoverData = sandbox.stub(instance, "sendPageTakeoverData");
  859. instance.onAction({type: at.INIT});
  860. assert.calledOnce(init);
  861. assert.calledOnce(sendPageTakeoverData);
  862. });
  863. it("should call .uninit() on an UNINIT action", () => {
  864. const stub = sandbox.stub(instance, "uninit");
  865. instance.onAction({type: at.UNINIT});
  866. assert.calledOnce(stub);
  867. });
  868. it("should call .handleNewTabInit on a NEW_TAB_INIT action", () => {
  869. sandbox.spy(instance, "handleNewTabInit");
  870. instance.onAction(ac.AlsoToMain({
  871. type: at.NEW_TAB_INIT,
  872. data: {url: "about:newtab", browser},
  873. }));
  874. assert.calledOnce(instance.handleNewTabInit);
  875. });
  876. it("should call .addSession() on a NEW_TAB_INIT action", () => {
  877. const stub = sandbox.stub(instance, "addSession").returns({perf: {}});
  878. sandbox.stub(instance, "setLoadTriggerInfo");
  879. instance.onAction(ac.AlsoToMain({
  880. type: at.NEW_TAB_INIT,
  881. data: {url: "about:monkeys", browser},
  882. }, "port123"));
  883. assert.calledOnce(stub);
  884. assert.calledWith(stub, "port123", "about:monkeys");
  885. });
  886. it("should call .endSession() on a NEW_TAB_UNLOAD action", () => {
  887. const stub = sandbox.stub(instance, "endSession");
  888. instance.onAction(ac.AlsoToMain({type: at.NEW_TAB_UNLOAD}, "port123"));
  889. assert.calledWith(stub, "port123");
  890. });
  891. it("should call .saveSessionPerfData on SAVE_SESSION_PERF_DATA", () => {
  892. const stub = sandbox.stub(instance, "saveSessionPerfData");
  893. const data = {some_ts: 10};
  894. const action = {type: at.SAVE_SESSION_PERF_DATA, data};
  895. instance.onAction(ac.AlsoToMain(action, "port123"));
  896. assert.calledWith(stub, "port123", data);
  897. });
  898. it("should send an event on a TELEMETRY_UNDESIRED_EVENT action", () => {
  899. const sendEvent = sandbox.stub(instance, "sendEvent");
  900. const eventCreator = sandbox.stub(instance, "createUndesiredEvent");
  901. const action = {type: at.TELEMETRY_UNDESIRED_EVENT};
  902. instance.onAction(action);
  903. assert.calledWith(eventCreator, action);
  904. assert.calledWith(sendEvent, eventCreator.returnValue);
  905. });
  906. it("should send an event on a TELEMETRY_USER_EVENT action", () => {
  907. FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
  908. FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = true;
  909. instance = new TelemetryFeed();
  910. const sendEvent = sandbox.stub(instance, "sendEvent");
  911. const utSendUserEvent = sandbox.stub(instance.utEvents, "sendUserEvent");
  912. const eventCreator = sandbox.stub(instance, "createUserEvent");
  913. const action = {type: at.TELEMETRY_USER_EVENT};
  914. instance.onAction(action);
  915. assert.calledWith(eventCreator, action);
  916. assert.calledWith(sendEvent, eventCreator.returnValue);
  917. assert.calledWith(utSendUserEvent, eventCreator.returnValue);
  918. });
  919. it("should call handleASRouterUserEvent on TELEMETRY_USER_EVENT action", () => {
  920. FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
  921. FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = true;
  922. instance = new TelemetryFeed();
  923. const eventHandler = sandbox.spy(instance, "handleASRouterUserEvent");
  924. const action = {type: at.AS_ROUTER_TELEMETRY_USER_EVENT, data: {event: "CLICK"}};
  925. instance.onAction(action);
  926. assert.calledWith(eventHandler, action);
  927. });
  928. it("should send an event on a TELEMETRY_PERFORMANCE_EVENT action", () => {
  929. const sendEvent = sandbox.stub(instance, "sendEvent");
  930. const eventCreator = sandbox.stub(instance, "createPerformanceEvent");
  931. const action = {type: at.TELEMETRY_PERFORMANCE_EVENT};
  932. instance.onAction(action);
  933. assert.calledWith(eventCreator, action);
  934. assert.calledWith(sendEvent, eventCreator.returnValue);
  935. });
  936. it("should send an event on a TELEMETRY_IMPRESSION_STATS action", () => {
  937. const sendEvent = sandbox.stub(instance, "sendEvent");
  938. const eventCreator = sandbox.stub(instance, "createImpressionStats");
  939. const tiles = [{id: 10001}, {id: 10002}, {id: 10003}];
  940. const action = ac.ImpressionStats({source: "POCKET", tiles});
  941. instance.onAction(action);
  942. assert.calledWith(eventCreator, au.getPortIdOfSender(action), action.data);
  943. assert.calledWith(sendEvent, eventCreator.returnValue);
  944. });
  945. it("should call .handleDiscoveryStreamImpressionStats on a DISCOVERY_STREAM_IMPRESSION_STATS action", () => {
  946. const session = {};
  947. sandbox.stub(instance.sessions, "get").returns(session);
  948. const data = {source: "foo", tiles: [{id: 1}]};
  949. const action = {type: at.DISCOVERY_STREAM_IMPRESSION_STATS, data};
  950. sandbox.spy(instance, "handleDiscoveryStreamImpressionStats");
  951. instance.onAction(ac.AlsoToMain(action, "port123"));
  952. assert.calledWith(instance.handleDiscoveryStreamImpressionStats, "port123", data);
  953. });
  954. it("should call .handleDiscoveryStreamLoadedContent on a DISCOVERY_STREAM_LOADED_CONTENT action", () => {
  955. const session = {};
  956. sandbox.stub(instance.sessions, "get").returns(session);
  957. const data = {source: "foo", tiles: [{id: 1}]};
  958. const action = {type: at.DISCOVERY_STREAM_LOADED_CONTENT, data};
  959. sandbox.spy(instance, "handleDiscoveryStreamLoadedContent");
  960. instance.onAction(ac.AlsoToMain(action, "port123"));
  961. assert.calledWith(instance.handleDiscoveryStreamLoadedContent, "port123", data);
  962. });
  963. it("should send an event on a DISCOVERY_STREAM_SPOCS_FILL action", () => {
  964. const sendEvent = sandbox.stub(instance, "sendStructuredIngestionEvent");
  965. const eventCreator = sandbox.stub(instance, "createSpocsFillPing");
  966. const spocFills = [
  967. {id: 10001, displayed: 0, reason: "frequency_cap", full_recalc: 1},
  968. {id: 10002, displayed: 0, reason: "blocked_by_user", full_recalc: 1},
  969. {id: 10003, displayed: 1, reason: "n/a", full_recalc: 1},
  970. ];
  971. const action = ac.DiscoveryStreamSpocsFill({spoc_fills: spocFills});
  972. instance.onAction(action);
  973. assert.calledWith(eventCreator, action.data);
  974. assert.calledWith(sendEvent, eventCreator.returnValue);
  975. });
  976. it("should call .handleTrailheadEnrollEvent on a TRAILHEAD_ENROLL_EVENT action", () => {
  977. const data = {experiment: "foo", type: "bar", branch: "baz"};
  978. const action = {type: at.TRAILHEAD_ENROLL_EVENT, data};
  979. sandbox.spy(instance, "handleTrailheadEnrollEvent");
  980. instance.onAction(action);
  981. assert.calledWith(instance.handleTrailheadEnrollEvent, action);
  982. });
  983. });
  984. describe("#handleNewTabInit", () => {
  985. it("should set the session as preloaded if the browser is preloaded", () => {
  986. const session = {perf: {}};
  987. let preloadedBrowser = {getAttribute() { return "preloaded"; }};
  988. sandbox.stub(instance, "addSession").returns(session);
  989. instance.onAction(ac.AlsoToMain({
  990. type: at.NEW_TAB_INIT,
  991. data: {url: "about:newtab", browser: preloadedBrowser},
  992. }));
  993. assert.ok(session.perf.is_preloaded);
  994. });
  995. it("should set the session as non-preloaded if the browser is non-preloaded", () => {
  996. const session = {perf: {}};
  997. let nonPreloadedBrowser = {getAttribute() { return ""; }};
  998. sandbox.stub(instance, "addSession").returns(session);
  999. instance.onAction(ac.AlsoToMain({
  1000. type: at.NEW_TAB_INIT,
  1001. data: {url: "about:newtab", browser: nonPreloadedBrowser},
  1002. }));
  1003. assert.ok(!session.perf.is_preloaded);
  1004. });
  1005. });
  1006. describe("#sendPageTakeoverData", () => {
  1007. let fakePrefs = {"browser.newtabpage.enabled": true};
  1008. beforeEach(() => {
  1009. globals.set("Services", Object.assign({}, Services, {prefs: {getBoolPref: key => fakePrefs[key]}}));
  1010. // Services.prefs = {getBoolPref: key => fakePrefs[key]};
  1011. });
  1012. it("should send correct event data for about:home set to custom URL", async () => {
  1013. fakeHomePageUrl = "https://searchprovider.com";
  1014. instance._prefs.set(TELEMETRY_PREF, true);
  1015. instance._classifySite = () => Promise.resolve("other");
  1016. const sendEvent = sandbox.stub(instance, "sendEvent");
  1017. await instance.sendPageTakeoverData();
  1018. assert.calledOnce(sendEvent);
  1019. assert.equal(sendEvent.firstCall.args[0].event, "PAGE_TAKEOVER_DATA");
  1020. assert.deepEqual(sendEvent.firstCall.args[0].value, {
  1021. home_url_category: "other",
  1022. });
  1023. assert.validate(sendEvent.firstCall.args[0], UserEventPing);
  1024. });
  1025. it("should send correct event data for about:newtab set to custom URL", async () => {
  1026. globals.set("aboutNewTabService", {
  1027. overridden: true,
  1028. newTabURL: "https://searchprovider.com",
  1029. });
  1030. instance._prefs.set(TELEMETRY_PREF, true);
  1031. instance._classifySite = () => Promise.resolve("other");
  1032. const sendEvent = sandbox.stub(instance, "sendEvent");
  1033. await instance.sendPageTakeoverData();
  1034. assert.calledOnce(sendEvent);
  1035. assert.equal(sendEvent.firstCall.args[0].event, "PAGE_TAKEOVER_DATA");
  1036. assert.deepEqual(sendEvent.firstCall.args[0].value, {
  1037. newtab_url_category: "other",
  1038. });
  1039. assert.validate(sendEvent.firstCall.args[0], UserEventPing);
  1040. });
  1041. it("should not send an event if neither about:{home,newtab} are set to custom URL", async () => {
  1042. instance._prefs.set(TELEMETRY_PREF, true);
  1043. const sendEvent = sandbox.stub(instance, "sendEvent");
  1044. await instance.sendPageTakeoverData();
  1045. assert.notCalled(sendEvent);
  1046. });
  1047. it("should send home_extension_id and newtab_extension_id when appropriate", async () => {
  1048. const ID = "{abc-foo-bar}";
  1049. fakeExtensionSettingsStore.getSetting = () => ({id: ID});
  1050. instance._prefs.set(TELEMETRY_PREF, true);
  1051. instance._classifySite = () => Promise.resolve("other");
  1052. const sendEvent = sandbox.stub(instance, "sendEvent");
  1053. await instance.sendPageTakeoverData();
  1054. assert.calledOnce(sendEvent);
  1055. assert.equal(sendEvent.firstCall.args[0].event, "PAGE_TAKEOVER_DATA");
  1056. assert.deepEqual(sendEvent.firstCall.args[0].value, {
  1057. home_extension_id: ID,
  1058. newtab_extension_id: ID,
  1059. });
  1060. assert.validate(sendEvent.firstCall.args[0], UserEventPing);
  1061. });
  1062. });
  1063. describe("#sendDiscoveryStreamImpressions", () => {
  1064. it("should not send impression pings if there is no impression data", () => {
  1065. const spy = sandbox.spy(instance, "sendEvent");
  1066. const session = {};
  1067. instance.sendDiscoveryStreamImpressions("foo", session);
  1068. assert.notCalled(spy);
  1069. });
  1070. it("should send impression pings if there is impression data", () => {
  1071. const spy = sandbox.spy(instance, "sendEvent");
  1072. const session = {
  1073. impressionSets: {
  1074. source_foo: [{id: 1, pos: 0}, {id: 2, pos: 1}],
  1075. source_bar: [{id: 3, pos: 0}, {id: 4, pos: 1}],
  1076. },
  1077. };
  1078. instance.sendDiscoveryStreamImpressions("foo", session);
  1079. assert.calledTwice(spy);
  1080. });
  1081. });
  1082. describe("#sendDiscoveryStreamLoadedContent", () => {
  1083. it("should not send loaded content pings if there is no loaded content data", () => {
  1084. const spy = sandbox.spy(instance, "sendEvent");
  1085. const session = {};
  1086. instance.sendDiscoveryStreamLoadedContent("foo", session);
  1087. assert.notCalled(spy);
  1088. });
  1089. it("should send loaded content pings if there is loaded content data", () => {
  1090. const spy = sandbox.spy(instance, "sendEvent");
  1091. const session = {
  1092. loadedContentSets: {
  1093. source_foo: [{id: 1, pos: 0}, {id: 2, pos: 1}],
  1094. source_bar: [{id: 3, pos: 0}, {id: 4, pos: 1}],
  1095. },
  1096. };
  1097. instance.sendDiscoveryStreamLoadedContent("foo", session);
  1098. assert.calledTwice(spy);
  1099. let [payload] = spy.firstCall.args;
  1100. let sources = new Set([]);
  1101. sources.add(payload.source);
  1102. assert.equal(payload.loaded, 2);
  1103. assert.deepEqual(payload.tiles, session.loadedContentSets[payload.source]);
  1104. [payload] = spy.secondCall.args;
  1105. sources.add(payload.source);
  1106. assert.equal(payload.loaded, 2);
  1107. assert.deepEqual(payload.tiles, session.loadedContentSets[payload.source]);
  1108. assert.deepEqual(sources, new Set(["source_foo", "source_bar"]));
  1109. });
  1110. });
  1111. describe("#handleDiscoveryStreamImpressionStats", () => {
  1112. it("should throw for a missing session", () => {
  1113. assert.throws(() => {
  1114. instance.handleDiscoveryStreamImpressionStats("a_missing_port", {});
  1115. }, "Session does not exist.");
  1116. });
  1117. it("should store impression to impressionSets", () => {
  1118. const session = instance.addSession("new_session", "about:newtab");
  1119. instance.handleDiscoveryStreamImpressionStats("new_session",
  1120. {source: "foo", tiles: [{id: 1, pos: 0}]});
  1121. assert.equal(Object.keys(session.impressionSets).length, 1);
  1122. assert.deepEqual(session.impressionSets.foo, [{id: 1, pos: 0}]);
  1123. // Add another ping with the same source
  1124. instance.handleDiscoveryStreamImpressionStats("new_session",
  1125. {source: "foo", tiles: [{id: 2, pos: 1}]});
  1126. assert.deepEqual(session.impressionSets.foo,
  1127. [{id: 1, pos: 0}, {id: 2, pos: 1}]);
  1128. // Add another ping with a different source
  1129. instance.handleDiscoveryStreamImpressionStats("new_session",
  1130. {source: "bar", tiles: [{id: 3, pos: 2}]});
  1131. assert.equal(Object.keys(session.impressionSets).length, 2);
  1132. assert.deepEqual(session.impressionSets.foo, [{id: 1, pos: 0}, {id: 2, pos: 1}]);
  1133. assert.deepEqual(session.impressionSets.bar, [{id: 3, pos: 2}]);
  1134. });
  1135. });
  1136. describe("#handleDiscoveryStreamLoadedContent", () => {
  1137. it("should throw for a missing session", () => {
  1138. assert.throws(() => {
  1139. instance.handleDiscoveryStreamLoadedContent("a_missing_port", {});
  1140. }, "Session does not exist.");
  1141. });
  1142. it("should store loaded content to loadedContentSets", () => {
  1143. const session = instance.addSession("new_session", "about:newtab");
  1144. instance.handleDiscoveryStreamLoadedContent("new_session",
  1145. {source: "foo", tiles: [{id: 1, pos: 0}]});
  1146. assert.equal(Object.keys(session.loadedContentSets).length, 1);
  1147. assert.deepEqual(session.loadedContentSets.foo, [{id: 1, pos: 0}]);
  1148. // Add another ping with the same source
  1149. instance.handleDiscoveryStreamLoadedContent("new_session",
  1150. {source: "foo", tiles: [{id: 2, pos: 1}]});
  1151. assert.deepEqual(session.loadedContentSets.foo,
  1152. [{id: 1, pos: 0}, {id: 2, pos: 1}]);
  1153. // Add another ping with a different source
  1154. instance.handleDiscoveryStreamLoadedContent("new_session",
  1155. {source: "bar", tiles: [{id: 3, pos: 2}]});
  1156. assert.equal(Object.keys(session.loadedContentSets).length, 2);
  1157. assert.deepEqual(session.loadedContentSets.foo, [{id: 1, pos: 0}, {id: 2, pos: 1}]);
  1158. assert.deepEqual(session.loadedContentSets.bar, [{id: 3, pos: 2}]);
  1159. });
  1160. });
  1161. describe("#_generateStructuredIngestionEndpoint", () => {
  1162. it("should generate a valid endpoint", () => {
  1163. const fakeEndpoint = "http://fakeendpoint.com/base/";
  1164. const fakeUUID = "{34f24486-f01a-9749-9c5b-21476af1fa77}";
  1165. const fakeUUIDWithoutBraces = fakeUUID.substring(1, fakeUUID.length - 1);
  1166. FakePrefs.prototype.prefs = {};
  1167. FakePrefs.prototype.prefs[STRUCTURED_INGESTION_ENDPOINT_PREF] = fakeEndpoint;
  1168. sandbox.stub(global.gUUIDGenerator, "generateUUID").returns(fakeUUID);
  1169. const feed = new TelemetryFeed();
  1170. const url = feed._generateStructuredIngestionEndpoint("testPingType", "1");
  1171. assert.equal(url, `${fakeEndpoint}/testPingType/1/${fakeUUIDWithoutBraces}`);
  1172. });
  1173. });
  1174. describe("#handleTrailheadEnrollEvent", () => {
  1175. it("should send a TRAILHEAD_ENROLL_EVENT if the telemetry is enabled", () => {
  1176. FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
  1177. const data = {experiment: "foo", type: "bar", branch: "baz"};
  1178. instance = new TelemetryFeed();
  1179. sandbox.stub(instance.utEvents, "sendTrailheadEnrollEvent");
  1180. instance.handleTrailheadEnrollEvent({data});
  1181. assert.calledWith(instance.utEvents.sendTrailheadEnrollEvent, data);
  1182. });
  1183. it("should not send TRAILHEAD_ENROLL_EVENT if the telemetry is disabled", () => {
  1184. FakePrefs.prototype.prefs[TELEMETRY_PREF] = false;
  1185. const data = {experiment: "foo", type: "bar", branch: "baz"};
  1186. instance = new TelemetryFeed();
  1187. sandbox.stub(instance.utEvents, "sendTrailheadEnrollEvent");
  1188. instance.handleTrailheadEnrollEvent({data});
  1189. assert.notCalled(instance.utEvents.sendTrailheadEnrollEvent);
  1190. });
  1191. });
  1192. });