browser_UsageTelemetry_interaction.js (32963B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ 3 */ 4 5 "use strict"; 6 7 gReduceMotionOverride = true; 8 9 const AREAS = [ 10 "keyboard", 11 "menu_bar", 12 "tabs_bar", 13 "nav_bar", 14 "bookmarks_bar", 15 "app_menu", 16 "tabs_context", 17 "content_context", 18 "overflow_menu", 19 "pinned_overflow_menu", 20 "pageaction_urlbar", 21 "pageaction_panel", 22 23 "preferences_paneHome", 24 "preferences_paneGeneral", 25 "preferences_panePrivacy", 26 "preferences_paneSearch", 27 "preferences_paneSearchResults", 28 "preferences_paneSync", 29 "preferences_paneContainers", 30 ]; 31 32 function resetGleanEvents() { 33 Services.fog.testResetFOG(); 34 GleanPings.prototypeNoCodeEvents.setEnabled(true); 35 } 36 37 // Checks that the correct number of clicks are registered against the correct 38 // keys in the scalars. Also runs keyed scalar checks against non-area types 39 // passed in through expectedOther. 40 function assertInteractionScalars(expectedAreas, expectedOther = {}) { 41 // Every time this checks Scalars, it clears them. So clear FOG too. 42 resetGleanEvents(); 43 let processScalars = 44 Services.telemetry.getSnapshotForKeyedScalars("main", true)?.parent ?? {}; 45 46 let compareSourceWithExpectations = (source, expected = {}) => { 47 let scalars = processScalars?.[`browser.ui.interaction.${source}`] ?? {}; 48 49 let expectedKeys = new Set( 50 Object.keys(scalars).concat(Object.keys(expected)) 51 ); 52 53 for (let key of expectedKeys) { 54 Assert.equal( 55 scalars[key], 56 expected[key], 57 `Expected to see the correct value for ${key} in ${source}.` 58 ); 59 } 60 }; 61 62 for (let source of AREAS) { 63 compareSourceWithExpectations(source, expectedAreas[source]); 64 } 65 66 for (let source in expectedOther) { 67 compareSourceWithExpectations(source, expectedOther[source]); 68 } 69 } 70 71 const elem = id => document.getElementById(id); 72 const click = el => { 73 if (typeof el == "string") { 74 el = elem(el); 75 } 76 77 EventUtils.synthesizeMouseAtCenter(el, {}, window); 78 }; 79 80 add_task(async function toolbarButtons() { 81 info("Adding a bookmark to the bookmarks toolbar."); 82 let addedBookmark = await PlacesUtils.bookmarks.insert({ 83 parentGuid: PlacesUtils.bookmarks.toolbarGuid, 84 title: "Test", 85 url: "https://example.com", 86 }); 87 88 registerCleanupFunction(async () => { 89 await PlacesUtils.bookmarks.remove(addedBookmark); 90 }); 91 92 await BrowserTestUtils.withNewTab("https://example.com", async () => { 93 let customButton = await new Promise(resolve => { 94 CustomizableUI.createWidget({ 95 // In CSS identifiers cannot start with a number but CustomizableUI accepts that. 96 id: "12foo", 97 label: "12foo", 98 onCreated: resolve, 99 defaultArea: "nav-bar", 100 }); 101 }); 102 103 Services.telemetry.getSnapshotForKeyedScalars("main", true); 104 resetGleanEvents(); 105 // We want to record events into this ping, so it has to be enabled. 106 GleanPings.prototypeNoCodeEvents.setEnabled(true); 107 108 let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser); 109 let tabClose = BrowserTestUtils.waitForTabClosing(newTab); 110 111 let tabs = elem("tabbrowser-tabs"); 112 if (!tabs.overflowing) { 113 tabs.setAttribute("overflow", "true"); 114 registerCleanupFunction(() => { 115 tabs.removeAttribute("overflow"); 116 }); 117 } 118 119 // We intentionally turn off a11y_checks for these click events, because the 120 // test is checking the telemetry functionality and the following 3 clicks 121 // are targeting disabled controls to test the changes in scalars (for more 122 // refer to the bug 1864576 comment 2 and bug 1854999 comment 4): 123 AccessibilityUtils.setEnv({ 124 mustBeEnabled: false, 125 }); 126 click("stop-reload-button"); 127 click("back-button"); 128 click("back-button"); 129 AccessibilityUtils.resetEnv(); 130 131 // Make sure the all tabs panel is in the document. 132 gTabsPanel.initElements(); 133 let view = elem("allTabsMenu-allTabsView"); 134 let shown = BrowserTestUtils.waitForEvent(view, "ViewShown"); 135 click("alltabs-button"); 136 await shown; 137 138 let hidden = BrowserTestUtils.waitForEvent(view, "ViewHiding"); 139 gTabsPanel.hideAllTabsPanel(); 140 await hidden; 141 142 click(newTab.querySelector(".tab-close-button")); 143 await tabClose; 144 145 let bookmarksToolbar = gNavToolbox.querySelector("#PersonalToolbar"); 146 147 let bookmarksToolbarReady = BrowserTestUtils.waitForMutationCondition( 148 bookmarksToolbar, 149 { attributes: true }, 150 () => { 151 return ( 152 bookmarksToolbar.getAttribute("collapsed") != "true" && 153 bookmarksToolbar.hasAttribute("initialized") 154 ); 155 } 156 ); 157 158 window.setToolbarVisibility( 159 bookmarksToolbar, 160 true /* isVisible */, 161 false /* persist */, 162 false /* animated */ 163 ); 164 registerCleanupFunction(() => { 165 window.setToolbarVisibility( 166 bookmarksToolbar, 167 false /* isVisible */, 168 false /* persist */, 169 false /* animated */ 170 ); 171 }); 172 await bookmarksToolbarReady; 173 174 // The Bookmarks Toolbar does some optimizations to try not to jank the 175 // browser when populating itself, and does so asynchronously. We wait 176 // until a bookmark item is available in the DOM before continuing. 177 let placesToolbarItems = document.getElementById("PlacesToolbarItems"); 178 await BrowserTestUtils.waitForMutationCondition( 179 placesToolbarItems, 180 { childList: true }, 181 () => placesToolbarItems.querySelector(".bookmark-item") != null 182 ); 183 184 click(placesToolbarItems.querySelector(".bookmark-item")); 185 186 click(customButton); 187 188 let events = Glean.browserUsage.interaction 189 .testGetValue() 190 .map(e => [e.extra.source, e.extra.widget_id]); 191 Assert.deepEqual( 192 [ 193 ["nav-bar", "stop-reload-button"], 194 ["nav-bar", "back-button"], 195 ["nav-bar", "back-button"], 196 ["all-tabs-panel-entrypoint", "alltabs-button"], 197 ["tabs-bar", "alltabs-button"], 198 ["tabs-bar", "tab-close-button"], 199 ["bookmarks-bar", "bookmark-item"], 200 ["nav-bar", "12foo"], 201 ], 202 events 203 ); 204 assertInteractionScalars( 205 { 206 nav_bar: { 207 "stop-reload-button": 1, 208 "back-button": 2, 209 "12foo": 1, 210 }, 211 tabs_bar: { 212 "alltabs-button": 1, 213 "tab-close-button": 1, 214 }, 215 bookmarks_bar: { 216 "bookmark-item": 1, 217 }, 218 }, 219 { 220 all_tabs_panel_entrypoint: { 221 "alltabs-button": 1, 222 }, 223 } 224 ); 225 CustomizableUI.destroyWidget("12foo"); 226 }); 227 }); 228 229 add_task(async function contextMenu() { 230 await BrowserTestUtils.withNewTab("https://example.com", async browser => { 231 Services.telemetry.getSnapshotForKeyedScalars("main", true); 232 resetGleanEvents(); 233 234 let tab = gBrowser.getTabForBrowser(browser); 235 let context = elem("tabContextMenu"); 236 let shown = BrowserTestUtils.waitForEvent(context, "popupshown"); 237 EventUtils.synthesizeMouseAtCenter( 238 tab, 239 { type: "contextmenu", button: 2 }, 240 window 241 ); 242 await shown; 243 244 let hidden = BrowserTestUtils.waitForEvent(context, "popuphidden"); 245 context.activateItem(document.getElementById("context_toggleMuteTab")); 246 await hidden; 247 248 let events = Glean.browserUsage.interaction 249 .testGetValue() 250 .map(e => [e.extra.source, e.extra.widget_id]); 251 252 Assert.deepEqual( 253 [ 254 ["tabs-context", "context-toggleMuteTab"], 255 ["tabs-context-entrypoint", "context-toggleMuteTab"], 256 ], 257 events 258 ); 259 assertInteractionScalars({ 260 tabs_context: { 261 "context-toggleMuteTab": 1, 262 }, 263 }); 264 265 // Check that tab-related items in the toolbar menu also register telemetry: 266 context = elem("toolbar-context-menu"); 267 shown = BrowserTestUtils.waitForEvent(context, "popupshown"); 268 let scrollbox = elem("tabbrowser-arrowscrollbox"); 269 EventUtils.synthesizeMouse( 270 scrollbox, 271 // offset within the scrollbox - somewhere near the end: 272 scrollbox.getBoundingClientRect().width - 20, 273 5, 274 { type: "contextmenu", button: 2 }, 275 window 276 ); 277 await shown; 278 279 hidden = BrowserTestUtils.waitForEvent(context, "popuphidden"); 280 context.activateItem( 281 document.getElementById("toolbar-context-selectAllTabs") 282 ); 283 await hidden; 284 285 events = Glean.browserUsage.interaction 286 .testGetValue() 287 .map(e => [e.extra.source, e.extra.widget_id]); 288 Assert.deepEqual( 289 [ 290 ["tabs-context", "toolbar-context-selectAllTabs"], 291 ["tabs-context-entrypoint", "toolbar-context-selectAllTabs"], 292 ], 293 events 294 ); 295 assertInteractionScalars({ 296 tabs_context: { 297 "toolbar-context-selectAllTabs": 1, 298 }, 299 }); 300 // tidy up: 301 gBrowser.clearMultiSelectedTabs(); 302 }); 303 }); 304 305 add_task(async function contextMenu_entrypoints() { 306 /** 307 * A utility function for this test task that opens the tab context 308 * menu for a particular trigger node, chooses the "Reload Tab" item, 309 * and then waits for the context menu to close. 310 * 311 * @param {Element} triggerNode 312 * The node that the tab context menu should be triggered with. 313 * @returns {Promise<undefined>} 314 * Resolves after the context menu has fired the popuphidden event. 315 */ 316 let openAndCloseTabContextMenu = async triggerNode => { 317 let contextMenu = document.getElementById("tabContextMenu"); 318 let popupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); 319 EventUtils.synthesizeMouseAtCenter(triggerNode, { 320 type: "contextmenu", 321 button: 2, 322 }); 323 await popupShown; 324 325 let popupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); 326 let menuitem = document.getElementById("context_reloadTab"); 327 contextMenu.activateItem(menuitem); 328 await popupHidden; 329 }; 330 331 const TAB_CONTEXTMENU_ENTRYPOINT_SCALAR = 332 "browser.ui.interaction.tabs_context_entrypoint"; 333 Services.telemetry.clearScalars(); 334 335 let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); 336 TelemetryTestUtils.assertScalarUnset( 337 scalars, 338 TAB_CONTEXTMENU_ENTRYPOINT_SCALAR 339 ); 340 341 await openAndCloseTabContextMenu(gBrowser.selectedTab); 342 scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); 343 TelemetryTestUtils.assertKeyedScalar( 344 scalars, 345 TAB_CONTEXTMENU_ENTRYPOINT_SCALAR, 346 "tabs-bar", 347 1 348 ); 349 350 gTabsPanel.initElements(); 351 let allTabsView = document.getElementById("allTabsMenu-allTabsView"); 352 let allTabsPopupShownPromise = BrowserTestUtils.waitForEvent( 353 allTabsView, 354 "ViewShown" 355 ); 356 gTabsPanel.showAllTabsPanel(null); 357 await allTabsPopupShownPromise; 358 359 let firstTabItem = gTabsPanel.allTabsViewTabs.children[0]; 360 await openAndCloseTabContextMenu(firstTabItem); 361 scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); 362 TelemetryTestUtils.assertKeyedScalar( 363 scalars, 364 TAB_CONTEXTMENU_ENTRYPOINT_SCALAR, 365 "alltabs-menu", 366 1 367 ); 368 369 let allTabsPopupHiddenPromise = BrowserTestUtils.waitForEvent( 370 allTabsView.panelMultiView, 371 "PanelMultiViewHidden" 372 ); 373 gTabsPanel.hideAllTabsPanel(); 374 await allTabsPopupHiddenPromise; 375 }); 376 377 add_task(async function appMenu() { 378 await BrowserTestUtils.withNewTab("https://example.com", async () => { 379 Services.telemetry.getSnapshotForKeyedScalars("main", true); 380 resetGleanEvents(); 381 382 let shown = BrowserTestUtils.waitForEvent( 383 elem("appMenu-popup"), 384 "popupshown" 385 ); 386 click("PanelUI-menu-button"); 387 await shown; 388 389 let hidden = BrowserTestUtils.waitForEvent( 390 elem("appMenu-popup"), 391 "popuphidden" 392 ); 393 394 let findButtonID = "appMenu-find-button2"; 395 click(findButtonID); 396 await hidden; 397 398 let expectedScalars = { 399 nav_bar: { 400 "PanelUI-menu-button": 1, 401 }, 402 app_menu: { 403 [findButtonID]: 1, 404 }, 405 }; 406 407 let events = Glean.browserUsage.interaction 408 .testGetValue() 409 .map(e => [e.extra.source, e.extra.widget_id]); 410 Assert.deepEqual( 411 [ 412 ["nav-bar", "PanelUI-menu-button"], 413 ["app-menu", findButtonID], 414 ], 415 events 416 ); 417 418 assertInteractionScalars(expectedScalars); 419 }); 420 }); 421 422 add_task(async function devtools() { 423 await BrowserTestUtils.withNewTab("https://example.com", async () => { 424 Services.telemetry.getSnapshotForKeyedScalars("main", true); 425 resetGleanEvents(); 426 427 let shown = BrowserTestUtils.waitForEvent( 428 elem("appMenu-popup"), 429 "popupshown" 430 ); 431 click("PanelUI-menu-button"); 432 await shown; 433 434 click("appMenu-more-button2"); 435 shown = BrowserTestUtils.waitForEvent( 436 elem("appmenu-moreTools"), 437 "ViewShown" 438 ); 439 await shown; 440 441 let tabOpen = BrowserTestUtils.waitForNewTab(gBrowser); 442 let hidden = BrowserTestUtils.waitForEvent( 443 elem("appMenu-popup"), 444 "popuphidden" 445 ); 446 click( 447 document.querySelector( 448 "#appmenu-moreTools toolbarbutton[key='key_viewSource']" 449 ) 450 ); 451 await hidden; 452 453 let tab = await tabOpen; 454 BrowserTestUtils.removeTab(tab); 455 456 // Note that item ID's have '_' converted to '-'. 457 let events = Glean.browserUsage.interaction 458 .testGetValue() 459 .map(e => [e.extra.source, e.extra.widget_id]); 460 Assert.deepEqual( 461 [ 462 ["nav-bar", "PanelUI-menu-button"], 463 ["app-menu", "appMenu-more-button2"], 464 ["app-menu", "key-viewSource"], 465 ], 466 events 467 ); 468 assertInteractionScalars({ 469 nav_bar: { 470 "PanelUI-menu-button": 1, 471 }, 472 app_menu: { 473 "appMenu-more-button2": 1, 474 "key-viewSource": 1, 475 }, 476 }); 477 }); 478 }); 479 480 add_task(async function webextension() { 481 BrowserUsageTelemetry._resetAddonIds(); 482 483 await BrowserTestUtils.withNewTab("https://example.com", async browser => { 484 Services.telemetry.getSnapshotForKeyedScalars("main", true); 485 resetGleanEvents(); 486 487 function background() { 488 browser.commands.onCommand.addListener(() => { 489 browser.test.sendMessage("oncommand"); 490 }); 491 492 browser.runtime.onMessage.addListener(msg => { 493 if (msg == "from-sidebar-action") { 494 browser.test.sendMessage("sidebar-opened"); 495 } 496 }); 497 498 browser.test.sendMessage("ready"); 499 } 500 501 const extension = ExtensionTestUtils.loadExtension({ 502 manifest: { 503 version: "1", 504 browser_specific_settings: { 505 gecko: { id: "random_addon@example.com" }, 506 }, 507 browser_action: { 508 default_icon: "default.png", 509 default_title: "Hello", 510 default_area: "navbar", 511 }, 512 page_action: { 513 default_icon: "default.png", 514 default_title: "Hello", 515 show_matches: ["https://example.com/*"], 516 }, 517 commands: { 518 test_command: { 519 suggested_key: { 520 default: "Alt+Shift+J", 521 }, 522 }, 523 _execute_sidebar_action: { 524 suggested_key: { 525 default: "Alt+Shift+Q", 526 }, 527 }, 528 }, 529 sidebar_action: { 530 default_panel: "sidebar.html", 531 open_at_install: false, 532 }, 533 }, 534 files: { 535 "sidebar.html": ` 536 <!DOCTYPE html> 537 <html> 538 <head> 539 <meta charset="utf-8"> 540 <script src="sidebar.js"></script> 541 </head> 542 </html> 543 `, 544 545 "sidebar.js": function () { 546 browser.runtime.sendMessage("from-sidebar-action"); 547 }, 548 }, 549 background, 550 }); 551 552 await extension.startup(); 553 await extension.awaitMessage("ready"); 554 555 // As the first add-on interacted with this should show up as `addon0`. 556 557 click("random_addon_example_com-browser-action"); 558 let events = Glean.browserUsage.interaction.testGetValue(); 559 Assert.deepEqual( 560 [["nav-bar", "addon0"]], 561 events.map(e => [e.extra.source, e.extra.widget_id]) 562 ); 563 assertInteractionScalars({ 564 nav_bar: { 565 addon0: 1, 566 }, 567 }); 568 569 // Wait for the element to show up. 570 await TestUtils.waitForCondition(() => 571 elem("pageAction-urlbar-random_addon_example_com") 572 ); 573 574 click("pageAction-urlbar-random_addon_example_com"); 575 events = Glean.browserUsage.interaction.testGetValue(); 576 Assert.deepEqual( 577 [["pageaction-urlbar", "addon0"]], 578 events.map(e => [e.extra.source, e.extra.widget_id]) 579 ); 580 assertInteractionScalars({ 581 pageaction_urlbar: { 582 addon0: 1, 583 }, 584 }); 585 586 EventUtils.synthesizeKey("j", { altKey: true, shiftKey: true }); 587 await extension.awaitMessage("oncommand"); 588 events = Glean.browserUsage.interaction.testGetValue(); 589 Assert.deepEqual( 590 [["keyboard", "addon0"]], 591 events.map(e => [e.extra.source, e.extra.widget_id]) 592 ); 593 assertInteractionScalars({ 594 keyboard: { 595 addon0: 1, 596 }, 597 }); 598 599 EventUtils.synthesizeKey("q", { altKey: true, shiftKey: true }); 600 await extension.awaitMessage("sidebar-opened"); 601 events = Glean.browserUsage.interaction.testGetValue(); 602 Assert.deepEqual( 603 [["keyboard", "addon0"]], 604 events.map(e => [e.extra.source, e.extra.widget_id]) 605 ); 606 assertInteractionScalars({ 607 keyboard: { 608 addon0: 1, 609 }, 610 }); 611 612 const extension2 = ExtensionTestUtils.loadExtension({ 613 manifest: { 614 version: "1", 615 browser_specific_settings: { 616 gecko: { id: "random_addon2@example.com" }, 617 }, 618 browser_action: { 619 default_icon: "default.png", 620 default_title: "Hello", 621 default_area: "navbar", 622 }, 623 page_action: { 624 default_icon: "default.png", 625 default_title: "Hello", 626 show_matches: ["https://example.com/*"], 627 }, 628 commands: { 629 test_command: { 630 suggested_key: { 631 default: "Alt+Shift+9", 632 }, 633 }, 634 }, 635 }, 636 background, 637 }); 638 639 await extension2.startup(); 640 await extension2.awaitMessage("ready"); 641 642 // A second extension should be `addon1`. 643 644 click("random_addon2_example_com-browser-action"); 645 events = Glean.browserUsage.interaction.testGetValue(); 646 Assert.deepEqual( 647 [["nav-bar", "addon1"]], 648 events.map(e => [e.extra.source, e.extra.widget_id]) 649 ); 650 assertInteractionScalars({ 651 nav_bar: { 652 addon1: 1, 653 }, 654 }); 655 656 // Wait for the element to show up. 657 await TestUtils.waitForCondition(() => 658 elem("pageAction-urlbar-random_addon2_example_com") 659 ); 660 661 click("pageAction-urlbar-random_addon2_example_com"); 662 events = Glean.browserUsage.interaction.testGetValue(); 663 Assert.deepEqual( 664 [["pageaction-urlbar", "addon1"]], 665 events.map(e => [e.extra.source, e.extra.widget_id]) 666 ); 667 assertInteractionScalars({ 668 pageaction_urlbar: { 669 addon1: 1, 670 }, 671 }); 672 673 EventUtils.synthesizeKey("9", { altKey: true, shiftKey: true }); 674 await extension2.awaitMessage("oncommand"); 675 events = Glean.browserUsage.interaction.testGetValue(); 676 Assert.deepEqual( 677 [["keyboard", "addon1"]], 678 events.map(e => [e.extra.source, e.extra.widget_id]) 679 ); 680 assertInteractionScalars({ 681 keyboard: { 682 addon1: 1, 683 }, 684 }); 685 686 // The first should have retained its ID. 687 click("random_addon_example_com-browser-action"); 688 events = Glean.browserUsage.interaction.testGetValue(); 689 Assert.deepEqual( 690 [["nav-bar", "addon0"]], 691 events.map(e => [e.extra.source, e.extra.widget_id]) 692 ); 693 assertInteractionScalars({ 694 nav_bar: { 695 addon0: 1, 696 }, 697 }); 698 699 EventUtils.synthesizeKey("j", { altKey: true, shiftKey: true }); 700 await extension.awaitMessage("oncommand"); 701 events = Glean.browserUsage.interaction.testGetValue(); 702 Assert.deepEqual( 703 [["keyboard", "addon0"]], 704 events.map(e => [e.extra.source, e.extra.widget_id]) 705 ); 706 assertInteractionScalars({ 707 keyboard: { 708 addon0: 1, 709 }, 710 }); 711 712 click("pageAction-urlbar-random_addon_example_com"); 713 events = Glean.browserUsage.interaction.testGetValue(); 714 Assert.deepEqual( 715 [["pageaction-urlbar", "addon0"]], 716 events.map(e => [e.extra.source, e.extra.widget_id]) 717 ); 718 assertInteractionScalars({ 719 pageaction_urlbar: { 720 addon0: 1, 721 }, 722 }); 723 724 await extension.unload(); 725 726 // Clear the last opened ID so if this test runs again the sidebar won't 727 // automatically open when the extension is installed. 728 window.SidebarController.lastOpenedId = null; 729 730 // The second should retain its ID. 731 click("random_addon2_example_com-browser-action"); 732 click("random_addon2_example_com-browser-action"); 733 events = Glean.browserUsage.interaction.testGetValue(); 734 Assert.deepEqual( 735 [ 736 ["nav-bar", "addon1"], 737 ["nav-bar", "addon1"], 738 ], 739 events.map(e => [e.extra.source, e.extra.widget_id]) 740 ); 741 assertInteractionScalars({ 742 nav_bar: { 743 addon1: 2, 744 }, 745 }); 746 747 click("pageAction-urlbar-random_addon2_example_com"); 748 events = Glean.browserUsage.interaction.testGetValue(); 749 Assert.deepEqual( 750 [["pageaction-urlbar", "addon1"]], 751 events.map(e => [e.extra.source, e.extra.widget_id]) 752 ); 753 assertInteractionScalars({ 754 pageaction_urlbar: { 755 addon1: 1, 756 }, 757 }); 758 759 EventUtils.synthesizeKey("9", { altKey: true, shiftKey: true }); 760 await extension2.awaitMessage("oncommand"); 761 events = Glean.browserUsage.interaction.testGetValue(); 762 Assert.deepEqual( 763 [["keyboard", "addon1"]], 764 events.map(e => [e.extra.source, e.extra.widget_id]) 765 ); 766 assertInteractionScalars({ 767 keyboard: { 768 addon1: 1, 769 }, 770 }); 771 772 await extension2.unload(); 773 774 // Now test that browser action items in the add-ons panel also get 775 // telemetry recorded for them. 776 const extension3 = ExtensionTestUtils.loadExtension({ 777 manifest: { 778 version: "1", 779 browser_specific_settings: { 780 gecko: { id: "random_addon3@example.com" }, 781 }, 782 browser_action: { 783 default_icon: "default.png", 784 default_title: "Hello", 785 }, 786 }, 787 }); 788 789 await extension3.startup(); 790 791 const shown = BrowserTestUtils.waitForPopupEvent( 792 gUnifiedExtensions.panel, 793 "shown" 794 ); 795 await gUnifiedExtensions.togglePanel(); 796 await shown; 797 798 click("random_addon3_example_com-browser-action"); 799 events = Glean.browserUsage.interaction.testGetValue(); 800 Assert.deepEqual( 801 [["unified-extensions-area", "addon2"]], 802 events.map(e => [e.extra.source, e.extra.widget_id]) 803 ); 804 assertInteractionScalars({ 805 unified_extensions_area: { 806 addon2: 1, 807 }, 808 }); 809 const hidden = BrowserTestUtils.waitForPopupEvent( 810 gUnifiedExtensions.panel, 811 "hidden" 812 ); 813 await gUnifiedExtensions.panel.hidePopup(); 814 await hidden; 815 816 await extension3.unload(); 817 }); 818 }); 819 820 add_task(async function mainMenu() { 821 // macOS does not use the menu bar. 822 if (AppConstants.platform == "macosx") { 823 return; 824 } 825 826 BrowserUsageTelemetry._resetAddonIds(); 827 828 await BrowserTestUtils.withNewTab("https://example.com", async () => { 829 Services.telemetry.getSnapshotForKeyedScalars("main", true); 830 resetGleanEvents(); 831 832 CustomizableUI.setToolbarVisibility("toolbar-menubar", true); 833 834 let shown = BrowserTestUtils.waitForEvent( 835 elem("menu_EditPopup"), 836 "popupshown" 837 ); 838 click("edit-menu"); 839 await shown; 840 841 let hidden = BrowserTestUtils.waitForEvent( 842 elem("menu_EditPopup"), 843 "popuphidden" 844 ); 845 click("menu_selectAll"); 846 await hidden; 847 848 let events = Glean.browserUsage.interaction.testGetValue(); 849 Assert.deepEqual( 850 [["menu-bar", "menu-selectAll"]], 851 events.map(e => [e.extra.source, e.extra.widget_id]) 852 ); 853 assertInteractionScalars({ 854 menu_bar: { 855 // Note that the _ is replaced with - for telemetry identifiers. 856 "menu-selectAll": 1, 857 }, 858 }); 859 860 CustomizableUI.setToolbarVisibility("toolbar-menubar", false); 861 }); 862 }); 863 864 add_task(async function preferences() { 865 let finalPaneEvent = Services.prefs.getBoolPref("identity.fxaccounts.enabled") 866 ? "sync-pane-loaded" 867 : "privacy-pane-loaded"; 868 let finalPrefPaneLoaded = TestUtils.topicObserved(finalPaneEvent, () => true); 869 await BrowserTestUtils.withNewTab("about:preferences", async () => { 870 await finalPrefPaneLoaded; 871 872 Services.telemetry.getSnapshotForKeyedScalars("main", true); 873 resetGleanEvents(); 874 875 await BrowserTestUtils.synthesizeMouseAtCenter( 876 "#browserRestoreSession", 877 {}, 878 gBrowser.selectedBrowser.browsingContext 879 ); 880 881 await BrowserTestUtils.synthesizeMouseAtCenter( 882 "#category-search", 883 {}, 884 gBrowser.selectedBrowser.browsingContext 885 ); 886 887 await BrowserTestUtils.synthesizeMouseAtCenter( 888 "#category-privacy", 889 {}, 890 gBrowser.selectedBrowser.browsingContext 891 ); 892 await BrowserTestUtils.waitForCondition(() => 893 gBrowser.selectedBrowser.contentDocument.getElementById( 894 "contentBlockingLearnMore" 895 ) 896 ); 897 898 const onLearnMoreOpened = BrowserTestUtils.waitForNewTab(gBrowser); 899 gBrowser.selectedBrowser.contentDocument 900 .getElementById("contentBlockingLearnMore") 901 .scrollIntoView(); 902 await BrowserTestUtils.synthesizeMouseAtCenter( 903 "#contentBlockingLearnMore", 904 {}, 905 gBrowser.selectedBrowser.browsingContext 906 ); 907 await onLearnMoreOpened; 908 gBrowser.removeCurrentTab(); 909 910 let events = Glean.browserUsage.interaction 911 .testGetValue() 912 .map(e => [e.extra.source, e.extra.widget_id]); 913 Assert.deepEqual( 914 [ 915 ["preferences_paneGeneral", "browserRestoreSession"], 916 ["preferences_panePrivacy", "contentBlockingLearnMore"], 917 ], 918 events 919 ); 920 assertInteractionScalars({ 921 preferences_paneGeneral: { 922 browserRestoreSession: 1, 923 }, 924 preferences_panePrivacy: { 925 contentBlockingLearnMore: 1, 926 }, 927 }); 928 }); 929 }); 930 931 /** 932 * Context click on a history or bookmark link and open it in a new window. 933 * 934 * @param {Element} link - The link to open. 935 */ 936 async function openLinkUsingContextMenu(link) { 937 const placesContext = document.getElementById("placesContext"); 938 const promisePopup = BrowserTestUtils.waitForEvent( 939 placesContext, 940 "popupshown" 941 ); 942 EventUtils.synthesizeMouseAtCenter(link, { 943 button: 2, 944 type: "contextmenu", 945 }); 946 await promisePopup; 947 const promiseNewWindow = BrowserTestUtils.waitForNewWindow(); 948 placesContext.activateItem( 949 document.getElementById("placesContext_open:newwindow") 950 ); 951 const win = await promiseNewWindow; 952 await BrowserTestUtils.closeWindow(win); 953 } 954 955 async function history_appMenu(useContextClick) { 956 await BrowserTestUtils.withNewTab("https://example.com", async () => { 957 let shown = BrowserTestUtils.waitForEvent( 958 elem("appMenu-popup"), 959 "popupshown" 960 ); 961 click("PanelUI-menu-button"); 962 await shown; 963 964 click("appMenu-history-button"); 965 shown = BrowserTestUtils.waitForEvent(elem("PanelUI-history"), "ViewShown"); 966 await shown; 967 968 let list = document.getElementById("appMenu_historyMenu"); 969 let listItem = list.querySelector("toolbarbutton"); 970 971 if (useContextClick) { 972 await openLinkUsingContextMenu(listItem); 973 } else { 974 EventUtils.synthesizeMouseAtCenter(listItem, {}); 975 } 976 977 let expectedScalars = { 978 nav_bar: { 979 "PanelUI-menu-button": 1, 980 }, 981 982 app_menu: { "history-item": 1, "appMenu-history-button": 1 }, 983 }; 984 let events = Glean.browserUsage.interaction 985 .testGetValue() 986 .map(e => [e.extra.source, e.extra.widget_id]); 987 Assert.deepEqual( 988 [ 989 ["nav-bar", "PanelUI-menu-button"], 990 ["app-menu", "appMenu-history-button"], 991 ["app-menu", "history-item"], 992 ], 993 events 994 ); 995 assertInteractionScalars(expectedScalars); 996 }); 997 } 998 999 add_task(async function history_appMenu_click() { 1000 await history_appMenu(false); 1001 }); 1002 1003 add_task(async function history_appMenu_context_click() { 1004 await history_appMenu(true); 1005 }); 1006 1007 async function bookmarks_appMenu(useContextClick) { 1008 await BrowserTestUtils.withNewTab("https://example.com", async () => { 1009 let shown = BrowserTestUtils.waitForEvent( 1010 elem("appMenu-popup"), 1011 "popupshown" 1012 ); 1013 1014 shown = BrowserTestUtils.waitForEvent(elem("appMenu-popup"), "popupshown"); 1015 click("PanelUI-menu-button"); 1016 await shown; 1017 1018 click("appMenu-bookmarks-button"); 1019 shown = BrowserTestUtils.waitForEvent( 1020 elem("PanelUI-bookmarks"), 1021 "ViewShown" 1022 ); 1023 await shown; 1024 1025 let list = document.getElementById("panelMenu_bookmarksMenu"); 1026 let listItem = list.querySelector("toolbarbutton"); 1027 1028 if (useContextClick) { 1029 await openLinkUsingContextMenu(listItem); 1030 } else { 1031 EventUtils.synthesizeMouseAtCenter(listItem, {}); 1032 } 1033 1034 let expectedScalars = { 1035 nav_bar: { 1036 "PanelUI-menu-button": 1, 1037 }, 1038 1039 app_menu: { "bookmark-item": 1, "appMenu-bookmarks-button": 1 }, 1040 }; 1041 let events = Glean.browserUsage.interaction 1042 .testGetValue() 1043 .map(e => [e.extra.source, e.extra.widget_id]); 1044 Assert.deepEqual( 1045 [ 1046 ["nav-bar", "PanelUI-menu-button"], 1047 ["app-menu", "appMenu-bookmarks-button"], 1048 ["app-menu", "bookmark-item"], 1049 ], 1050 events 1051 ); 1052 assertInteractionScalars(expectedScalars); 1053 }); 1054 } 1055 1056 add_task(async function bookmarks_appMenu_click() { 1057 await bookmarks_appMenu(false); 1058 }); 1059 1060 add_task(async function bookmarks_appMenu_context_click() { 1061 await bookmarks_appMenu(true); 1062 }); 1063 1064 async function bookmarks_library_navbar(useContextClick) { 1065 await BrowserTestUtils.withNewTab("https://example.com", async () => { 1066 CustomizableUI.addWidgetToArea("library-button", "nav-bar"); 1067 let button = document.getElementById("library-button"); 1068 button.click(); 1069 await BrowserTestUtils.waitForEvent( 1070 elem("appMenu-libraryView"), 1071 "ViewShown" 1072 ); 1073 1074 click("appMenu-library-bookmarks-button"); 1075 await BrowserTestUtils.waitForEvent(elem("PanelUI-bookmarks"), "ViewShown"); 1076 1077 let list = document.getElementById("panelMenu_bookmarksMenu"); 1078 let listItem = list.querySelector("toolbarbutton"); 1079 1080 if (useContextClick) { 1081 await openLinkUsingContextMenu(listItem); 1082 } else { 1083 EventUtils.synthesizeMouseAtCenter(listItem, {}); 1084 } 1085 1086 let expectedScalars = { 1087 nav_bar: { 1088 "library-button": 1, 1089 "bookmark-item": 1, 1090 "appMenu-library-bookmarks-button": 1, 1091 }, 1092 }; 1093 let events = Glean.browserUsage.interaction 1094 .testGetValue() 1095 .map(e => [e.extra.source, e.extra.widget_id]); 1096 Assert.deepEqual( 1097 [ 1098 ["nav-bar", "library-button"], 1099 ["nav-bar", "appMenu-library-bookmarks-button"], 1100 ["nav-bar", "bookmark-item"], 1101 ], 1102 events 1103 ); 1104 assertInteractionScalars(expectedScalars); 1105 }); 1106 1107 CustomizableUI.removeWidgetFromArea("library-button"); 1108 } 1109 1110 add_task(async function bookmarks_library_navbar_click() { 1111 await bookmarks_library_navbar(false); 1112 }); 1113 1114 add_task(async function bookmarks_library_navbar_context_click() { 1115 await bookmarks_library_navbar(true); 1116 }); 1117 1118 async function history_library_navbar(useContextClick) { 1119 await BrowserTestUtils.withNewTab("https://example.com", async () => { 1120 CustomizableUI.addWidgetToArea("library-button", "nav-bar"); 1121 let button = document.getElementById("library-button"); 1122 button.click(); 1123 await BrowserTestUtils.waitForEvent( 1124 elem("appMenu-libraryView"), 1125 "ViewShown" 1126 ); 1127 1128 click("appMenu-library-history-button"); 1129 let shown = BrowserTestUtils.waitForEvent( 1130 elem("PanelUI-history"), 1131 "ViewShown" 1132 ); 1133 await shown; 1134 1135 let list = document.getElementById("appMenu_historyMenu"); 1136 let listItem = list.querySelector("toolbarbutton"); 1137 1138 if (useContextClick) { 1139 await openLinkUsingContextMenu(listItem); 1140 } else { 1141 EventUtils.synthesizeMouseAtCenter(listItem, {}); 1142 } 1143 1144 let expectedScalars = { 1145 nav_bar: { 1146 "library-button": 1, 1147 "history-item": 1, 1148 "appMenu-library-history-button": 1, 1149 }, 1150 }; 1151 let events = Glean.browserUsage.interaction 1152 .testGetValue() 1153 .map(e => [e.extra.source, e.extra.widget_id]); 1154 Assert.deepEqual( 1155 [ 1156 ["nav-bar", "library-button"], 1157 ["nav-bar", "appMenu-library-history-button"], 1158 ["nav-bar", "history-item"], 1159 ], 1160 events 1161 ); 1162 assertInteractionScalars(expectedScalars); 1163 }); 1164 1165 CustomizableUI.removeWidgetFromArea("library-button"); 1166 } 1167 1168 add_task(async function history_library_navbar_click() { 1169 await history_library_navbar(false); 1170 }); 1171 1172 add_task(async function history_library_navbar_context_click() { 1173 await history_library_navbar(true); 1174 });