tor-browser

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

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 });