Bug 1620358: Gather telemetry on toolbar state. r=Gijs,zombie, a=jcristau
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 16 Jun 2020 21:06:15 +0000
changeset 600866 02ebf58165068f69bfd9fafdb9b8b22092e0da7b
parent 600865 86e9f849c672b77c1f5ef160f5705dd644c772cb
child 600867 f1c8364c72de60e268571d01c247fb95abf4ec70
push id2361
push userjcristau@mozilla.com
push dateMon, 22 Jun 2020 18:14:30 +0000
treeherdermozilla-release@1e8edd35e323 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, zombie, jcristau
bugs1620358
milestone78.0
Bug 1620358: Gather telemetry on toolbar state. r=Gijs,zombie, a=jcristau Differential Revision: https://phabricator.services.mozilla.com/D78393
browser/base/content/browser-menubar.inc
browser/base/content/browser-pageActions.js
browser/base/content/browser-places.js
browser/base/content/browser.js
browser/base/content/browser.xhtml
browser/components/customizableui/CustomizableUI.jsm
browser/components/customizableui/CustomizeMode.jsm
browser/components/customizableui/SearchWidgetTracker.jsm
browser/components/customizableui/content/panelUI.inc.xhtml
browser/components/extensions/ext-browser.json
browser/components/extensions/parent/ext-browserAction.js
browser/components/extensions/parent/ext-pageAction.js
browser/components/uitour/UITour.jsm
browser/components/urlbar/UrlbarInput.jsm
browser/docs/BrowserUsageTelemetry.rst
browser/modules/BrowserUsageTelemetry.jsm
browser/modules/test/browser/browser.ini
browser/modules/test/browser/browser_UsageTelemetry_interaction.js
browser/modules/test/browser/browser_UsageTelemetry_toolbars.js
testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
toolkit/components/telemetry/Scalars.yaml
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -139,17 +139,17 @@
 #endif
               </menupopup>
             </menu>
 
             <menu id="view-menu" data-l10n-id="menu-view">
               <menupopup id="menu_viewPopup"
                          onpopupshowing="updateCharacterEncodingMenuState();">
                 <menu id="viewToolbarsMenu" data-l10n-id="menu-view-toolbars-menu">
-                  <menupopup onpopupshowing="onViewToolbarsPopupShowing(event);">
+                  <menupopup id="view-menu-popup" onpopupshowing="onViewToolbarsPopupShowing(event);">
                     <menuseparator/>
                     <menuitem id="menu_customizeToolbars"
                               command="cmd_CustomizeToolbars" data-l10n-id="menu-view-customize-toolbar"/>
                   </menupopup>
                 </menu>
                 <menu id="viewSidebarMenuMenu" data-l10n-id="menu-view-sidebar">
                   <menupopup id="viewSidebarMenu">
                     <menuitem id="menu_bookmarksSidebar"
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -951,16 +951,21 @@ var BrowserPageActions = {
   togglePinningForContextAction() {
     if (!this._contextAction) {
       return;
     }
     let action = this._contextAction;
     this._contextAction = null;
 
     action.pinnedToUrlbar = !action.pinnedToUrlbar;
+    BrowserUsageTelemetry.recordWidgetChange(
+      action.id,
+      action.pinnedToUrlbar ? "page-action-buttons" : null,
+      "pageaction-context"
+    );
   },
 
   /**
    * Call this from the menu item in the context menu that opens about:addons.
    */
   openAboutAddonsForContextAction() {
     if (!this._contextAction) {
       return;
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1426,21 +1426,26 @@ var BookmarkingUI = {
   selectLabel(elementId, visible) {
     let element = document.getElementById(elementId);
     element.setAttribute(
       "label",
       element.getAttribute(visible ? "label-hide" : "label-show")
     );
   },
 
-  toggleBookmarksToolbar() {
+  toggleBookmarksToolbar(reason) {
     CustomizableUI.setToolbarVisibility(
       "PersonalToolbar",
       document.getElementById("PersonalToolbar").collapsed
     );
+    BrowserUsageTelemetry.recordToolbarVisibility(
+      "PersonalToolbar",
+      document.getElementById("PersonalToolbar").collapsed,
+      reason
+    );
   },
 
   attachPlacesView(event, node) {
     // If the view is already there, bail out early.
     if (node.parentNode._placesView) {
       return;
     }
 
@@ -1919,19 +1924,29 @@ var BookmarkingUI = {
           // Last alternative is to use the navbar as the placement marker.
           pos =
             CustomizableUI.getPlacementOfWidget("urlbar-container").position +
             1;
         }
       }
 
       CustomizableUI.addWidgetToArea(this.BOOKMARK_BUTTON_ID, area, pos);
+      BrowserUsageTelemetry.recordWidgetChange(
+        this.BOOKMARK_BUTTON_ID,
+        area,
+        "bookmark-tools"
+      );
     } else {
       // Move it back to the palette.
       CustomizableUI.removeWidgetFromArea(this.BOOKMARK_BUTTON_ID);
+      BrowserUsageTelemetry.recordWidgetChange(
+        this.BOOKMARK_BUTTON_ID,
+        null,
+        "bookmark-tools"
+      );
     }
     triggerNode.setAttribute("checked", !placement);
     updateToggleControlLabel(triggerNode);
   },
 
   handlePlacesEvents(aEvents) {
     for (let ev of aEvents) {
       switch (ev.type) {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6421,19 +6421,21 @@ function onViewToolbarsPopupShowing(aEve
   } else {
     moveToPanel.setAttribute("disabled", true);
     removeFromToolbar.setAttribute("disabled", true);
   }
 }
 
 function onViewToolbarCommand(aEvent) {
   let node = aEvent.originalTarget;
+  let menuId = node.parentNode.id;
   let toolbarId = node.getAttribute("toolbarId");
   let isVisible = node.getAttribute("checked") == "true";
   CustomizableUI.setToolbarVisibility(toolbarId, isVisible);
+  BrowserUsageTelemetry.recordToolbarVisibility(toolbarId, isVisible, menuId);
   updateToggleControlLabel(node);
 }
 
 function setToolbarVisibility(toolbar, isVisible, persist = true) {
   let hidingAttribute;
   if (toolbar.getAttribute("type") == "menubar") {
     hidingAttribute = "autohide";
     if (AppConstants.platform == "linux") {
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -410,26 +410,26 @@
                 data-lazy-l10n-id="toolbar-context-menu-remove-extension"
                 contexttype="toolbaritem"
                 class="customize-context-removeExtension"/>
       <menuitem oncommand="ToolbarContextMenu.reportExtensionForContextAction(this.parentElement, 'toolbar_context_menu')"
                 data-lazy-l10n-id="toolbar-context-menu-report-extension"
                 contexttype="toolbaritem"
                 class="customize-context-reportExtension"/>
       <menuseparator/>
-      <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
+      <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode, 'toolbar-context-menu')"
                 data-lazy-l10n-id="toolbar-context-menu-pin-to-overflow-menu"
                 contexttype="toolbaritem"
                 class="customize-context-moveToPanel"/>
       <menuitem id="toolbar-context-autohide-downloads-button"
                 oncommand="ToolbarContextMenu.onDownloadsAutoHideChange(event);"
                 type="checkbox"
                 data-lazy-l10n-id="toolbar-context-menu-auto-hide-downloads-button"
                 contexttype="toolbaritem"/>
-      <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
+      <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode, 'toolbar-context-menu')"
                 data-lazy-l10n-id="toolbar-context-menu-remove-from-toolbar"
                 contexttype="toolbaritem"
                 class="customize-context-removeFromToolbar"/>
       <menuitem id="toolbar-context-reloadSelectedTab"
                 contexttype="tabbar"
                 oncommand="gBrowser.reloadMultiSelectedTabs();"
                 data-lazy-l10n-id="toolbar-context-menu-reload-selected-tab"/>
       <menuitem id="toolbar-context-reloadSelectedTabs"
@@ -1332,17 +1332,17 @@
                        context="placesContext"
                        onpopupshowing="if (!this.parentNode._placesView)
                                          new PlacesMenu(event, `place:parent=${PlacesUtils.bookmarks.toolbarGuid}`,
                                                         PlacesUIUtils.getViewForNode(this.parentNode.parentNode).options);">
               <menuitem id="BMB_viewBookmarksToolbar"
                         class="menuitem-iconic subviewbutton"
                         label-show="&viewBookmarksToolbar.label;"
                         label-hide="&hideBookmarksToolbar.label;"
-                        oncommand="BookmarkingUI.toggleBookmarksToolbar();"/>
+                        oncommand="BookmarkingUI.toggleBookmarksToolbar('bookmarks-widget');"/>
               <menuseparator/>
               <!-- Bookmarks toolbar items -->
             </menupopup>
           </menu>
           <menu id="BMB_unsortedBookmarks"
                 class="menu-iconic bookmark-item subviewbutton"
                 label="&bookmarksMenuButton.other.label;"
                 container="true">
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -16,16 +16,17 @@ const { AppConstants } = ChromeUtils.imp
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   AddonManagerPrivate: "resource://gre/modules/AddonManager.jsm",
   SearchWidgetTracker: "resource:///modules/SearchWidgetTracker.jsm",
   CustomizableWidgets: "resource:///modules/CustomizableWidgets.jsm",
   PanelMultiView: "resource:///modules/PanelMultiView.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   ShortcutUtils: "resource://gre/modules/ShortcutUtils.jsm",
+  BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
   const kUrl =
     "chrome://browser/locale/customizableui/customizableWidgets.properties";
   return Services.strings.createBundle(kUrl);
 });
 
@@ -4383,16 +4384,19 @@ var CustomizableUI = {
       } else if (menuChild.localName == "menuitem") {
         subviewItem = doc.createXULElement("toolbarbutton");
         CustomizableUI.addShortcut(menuChild, subviewItem);
 
         let item = menuChild;
         if (!item.hasAttribute("onclick")) {
           subviewItem.addEventListener("click", event => {
             let newEvent = new doc.defaultView.MouseEvent(event.type, event);
+
+            // Telemetry should only pay attention to the original event.
+            BrowserUsageTelemetry.ignoreEvent(newEvent);
             item.dispatchEvent(newEvent);
           });
         }
 
         if (!item.hasAttribute("oncommand")) {
           subviewItem.addEventListener("command", event => {
             let newEvent = doc.createEvent("XULCommandEvent");
             newEvent.initCommandEvent(
@@ -4403,16 +4407,19 @@ var CustomizableUI = {
               event.detail,
               event.ctrlKey,
               event.altKey,
               event.shiftKey,
               event.metaKey,
               event.sourceEvent,
               0
             );
+
+            // Telemetry should only pay attention to the original event.
+            BrowserUsageTelemetry.ignoreEvent(newEvent);
             item.dispatchEvent(newEvent);
           });
         }
       } else {
         continue;
       }
       for (let attr of attrs) {
         let attrVal = menuChild.getAttribute(attr);
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -51,16 +51,21 @@ ChromeUtils.defineModuleGetter(
 );
 ChromeUtils.defineModuleGetter(
   this,
   "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
+  "BrowserUsageTelemetry",
+  "resource:///modules/BrowserUsageTelemetry.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
   "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm"
 );
 XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
   const kUrl =
     "chrome://browser/locale/customizableui/customizableWidgets.properties";
   return Services.strings.createBundle(kUrl);
 });
@@ -663,17 +668,17 @@ CustomizeMode.prototype = {
             "animationend",
             cleanupWidgetAnimationEnd
           );
         });
       });
     });
   },
 
-  async addToToolbar(aNode) {
+  async addToToolbar(aNode, aReason) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
       aNode = aNode.firstElementChild;
     }
     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
     let animationNode;
     if (widgetAnimationPromise) {
       animationNode = await widgetAnimationPromise;
@@ -685,16 +690,20 @@ CustomizeMode.prototype = {
       aNode.closest("#customization-palette")
     ) {
       widgetToAdd = widgetToAdd.match(
         /^customizableui-special-(spring|spacer|separator)/
       )[1];
     }
 
     CustomizableUI.addWidgetToArea(widgetToAdd, CustomizableUI.AREA_NAVBAR);
+    BrowserUsageTelemetry.recordWidgetChange(
+      widgetToAdd,
+      CustomizableUI.AREA_NAVBAR
+    );
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly moves this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
@@ -702,29 +711,30 @@ CustomizeMode.prototype = {
       }
     }
 
     if (animationNode) {
       animationNode.classList.remove("animate-out");
     }
   },
 
-  async addToPanel(aNode) {
+  async addToPanel(aNode, aReason) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
       aNode = aNode.firstElementChild;
     }
     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
     let animationNode;
     if (widgetAnimationPromise) {
       animationNode = await widgetAnimationPromise;
     }
 
     let panel = CustomizableUI.AREA_FIXED_OVERFLOW_PANEL;
     CustomizableUI.addWidgetToArea(aNode.id, panel);
+    BrowserUsageTelemetry.recordWidgetChange(aNode.id, panel, aReason);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly moves this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
@@ -749,28 +759,29 @@ CustomizeMode.prototype = {
             this.removeAttribute("animate");
             this.removeAttribute("fade");
           }
         });
       });
     }
   },
 
-  async removeFromArea(aNode) {
+  async removeFromArea(aNode, aReason) {
     aNode = this._getCustomizableChildForNode(aNode);
     if (aNode.localName == "toolbarpaletteitem" && aNode.firstElementChild) {
       aNode = aNode.firstElementChild;
     }
     let widgetAnimationPromise = this._promiseWidgetAnimationOut(aNode);
     let animationNode;
     if (widgetAnimationPromise) {
       animationNode = await widgetAnimationPromise;
     }
 
     CustomizableUI.removeWidgetFromArea(aNode.id);
+    BrowserUsageTelemetry.recordWidgetChange(aNode.id, null, aReason);
     if (!this._customizing) {
       CustomizableUI.dispatchToolboxEvent("customizationchange");
     }
 
     // If the user explicitly removes this item, turn off autohide.
     if (aNode.id == "downloads-button") {
       Services.prefs.setBoolPref(kDownloadAutoHidePref, false);
       if (this._customizing) {
@@ -2084,17 +2095,18 @@ CustomizeMode.prototype = {
     // Is the target area the customization palette?
     if (aTargetArea.id == kPaletteId) {
       // Did we drag from outside the palette?
       if (aOriginArea.id !== kPaletteId) {
         if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) {
           return;
         }
 
-        CustomizableUI.removeWidgetFromArea(aDraggedItemId);
+        CustomizableUI.removeWidgetFromArea(aDraggedItemId, "drag");
+        BrowserUsageTelemetry.recordWidgetChange(aDraggedItemId, null, "drag");
         // Special widgets are removed outright, we can return here:
         if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
           return;
         }
       }
       draggedItem = draggedItem.parentNode;
 
       // If the target node is the palette itself, just append
@@ -2143,16 +2155,21 @@ CustomizeMode.prototype = {
         /^customizableui-special-(spring|spacer|separator)/
       )[1];
     }
 
     // Is the target the customization area itself? If so, we just add the
     // widget to the end of the area.
     if (aTargetNode == areaCustomizationTarget) {
       CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id);
+      BrowserUsageTelemetry.recordWidgetChange(
+        aDraggedItemId,
+        aTargetArea.id,
+        "drag"
+      );
       this._onDragEnd(aEvent);
       return;
     }
 
     // We need to determine the place that the widget is being dropped in
     // the target.
     let placement;
     let itemForPlacement = aTargetNode;
@@ -2191,18 +2208,28 @@ CustomizeMode.prototype = {
     }
     let position = placement ? placement.position : null;
 
     // Is the target area the same as the origin? Since we've already handled
     // the possibility that the target is the customization palette, we know
     // that the widget is moving within a customizable area.
     if (aTargetArea == aOriginArea) {
       CustomizableUI.moveWidgetWithinArea(aDraggedItemId, position);
+      BrowserUsageTelemetry.recordWidgetChange(
+        aDraggedItemId,
+        aTargetArea.id,
+        "drag"
+      );
     } else {
       CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position);
+      BrowserUsageTelemetry.recordWidgetChange(
+        aDraggedItemId,
+        aTargetArea.id,
+        "drag"
+      );
     }
 
     this._onDragEnd(aEvent);
 
     // If we dropped onto a skipintoolbarset item, manually correct the drop location:
     if (aTargetNode != itemForPlacement) {
       let draggedWrapper = draggedItem.parentNode;
       let container = draggedWrapper.parentNode;
@@ -2634,16 +2661,21 @@ CustomizeMode.prototype = {
           break;
         }
       }
       CustomizableUI.addWidgetToArea(
         "downloads-button",
         "nav-bar",
         insertionPoint
       );
+      BrowserUsageTelemetry.recordWidgetChange(
+        "downloads-button",
+        "nav-bar",
+        "move-downloads"
+      );
     }
   },
 
   async _showDownloadsAutoHidePanel() {
     let doc = this.document;
     let panel = doc.getElementById(kDownloadAutohidePanelId);
     panel.hidePopup();
     let button = doc.getElementById("downloads-button");
--- a/browser/components/customizableui/SearchWidgetTracker.jsm
+++ b/browser/components/customizableui/SearchWidgetTracker.jsm
@@ -16,16 +16,21 @@ const { AppConstants } = ChromeUtils.imp
   "resource://gre/modules/AppConstants.jsm"
 );
 
 ChromeUtils.defineModuleGetter(
   this,
   "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "BrowserUsageTelemetry",
+  "resource:///modules/BrowserUsageTelemetry.jsm"
+);
 
 const WIDGET_ID = "search-container";
 const PREF_NAME = "browser.search.widget.inNavBar";
 
 const SearchWidgetTracker = {
   init() {
     this.onWidgetReset = this.onWidgetUndoMove = node => {
       if (node.id == WIDGET_ID) {
@@ -80,18 +85,24 @@ const SearchWidgetTracker = {
     if (newValue) {
       // The URL bar widget is always present in the navigation toolbar, so we
       // can simply read its position to place the search bar right after it.
       CustomizableUI.addWidgetToArea(
         WIDGET_ID,
         CustomizableUI.AREA_NAVBAR,
         CustomizableUI.getPlacementOfWidget("urlbar-container").position + 1
       );
+      BrowserUsageTelemetry.recordWidgetChange(
+        WIDGET_ID,
+        CustomizableUI.AREA_NAVBAR,
+        "searchpref"
+      );
     } else {
       CustomizableUI.removeWidgetFromArea(WIDGET_ID);
+      BrowserUsageTelemetry.recordWidgetChange(WIDGET_ID, null, "searchpref");
     }
   },
 
   removePersistedWidths() {
     Services.xulStore.removeValue(
       AppConstants.BROWSER_CHROME_URL,
       "urlbar-container",
       "width"
--- a/browser/components/customizableui/content/panelUI.inc.xhtml
+++ b/browser/components/customizableui/content/panelUI.inc.xhtml
@@ -38,28 +38,28 @@
               data-lazy-l10n-id="toolbar-context-menu-remove-extension"
               contexttype="toolbaritem"
               class="customize-context-removeExtension"/>
     <menuitem oncommand="ToolbarContextMenu.reportExtensionForContextAction(this.parentElement, 'toolbar_context_menu')"
               data-lazy-l10n-id="toolbar-context-menu-report-extension"
               contexttype="toolbaritem"
               class="customize-context-reportExtension"/>
     <menuseparator/>
-    <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
+    <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode, 'panelitem-context')"
               id="customizationPanelItemContextMenuPin"
               data-lazy-l10n-id="toolbar-context-menu-pin-to-overflow-menu"
               closemenu="single"
               class="customize-context-moveToPanel"/>
-    <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
+    <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode, 'panelitem-context')"
               id="customizationPanelItemContextMenuUnpin"
               closemenu="single"
               class="customize-context-moveToToolbar"
               accesskey="&customizeMenu.unpinFromOverflowMenu.accesskey;"
               label="&customizeMenu.unpinFromOverflowMenu.label;"/>
-    <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode)"
+    <menuitem oncommand="gCustomizeMode.removeFromArea(document.popupNode, 'panelitem-context')"
               closemenu="single"
               class="customize-context-removeFromPanel"
               data-lazy-l10n-id="toolbar-context-menu-remove-from-toolbar"/>
     <menuseparator/>
     <menuitem command="cmd_CustomizeToolbars"
               class="viewCustomizeToolbar"
               data-lazy-l10n-id="toolbar-context-menu-view-customize-toolbar"/>
   </menupopup>
@@ -174,21 +174,21 @@
       </popupnotificationcontent>
     </popupnotification>
   </panel>
 </html:template>
 
 <html:template id="customModeWrapper">
   <menupopup id="customizationPaletteItemContextMenu"
              onpopupshowing="gCustomizeMode.onPaletteContextMenuShowing(event)">
-    <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode)"
+    <menuitem oncommand="gCustomizeMode.addToToolbar(document.popupNode, 'palette-context')"
               class="customize-context-addToToolbar"
               accesskey="&customizeMenu.addToToolbar.accesskey;"
               label="&customizeMenu.addToToolbar.label;"/>
-    <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
+    <menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode, 'palette-context')"
               class="customize-context-addToPanel"
               accesskey="&customizeMenu.addToOverflowMenu.accesskey;"
               label="&customizeMenu.addToOverflowMenu.label;"/>
   </menupopup>
 
   <menupopup id="customizationPanelContextMenu">
     <menuitem command="cmd_CustomizeToolbars"
               accesskey="&customizeMenu.addMoreItems.accesskey;"
@@ -217,17 +217,17 @@
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView"
                   viewCacheId="appMenu-viewCache">
     <panelview id="appMenu-mainView" class="PanelUI-subView"
                descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <vbox id="appMenu-addon-banners"/>
-        <toolbarbutton class="panel-banner-item"
+        <toolbarbutton id="appMenu-update-banner" class="panel-banner-item"
                        label-update-available="&updateAvailable.panelUI.label;"
                        label-update-manual="&updateManual.panelUI.label;"
                        label-update-unsupported="&updateUnsupported.panelUI.label;"
                        label-update-restart="&updateRestart.panelUI.label2;"
                        oncommand="PanelUI._onBannerItemSelected(event)"
                        wrap="true"
                        hidden="true"/>
         <toolbaritem id="appMenu-fxa-status"
@@ -525,16 +525,17 @@
             <hbox id="PanelUI-remotetabs-tabsdisabledpane" pack="center" flex="1">
               <vbox class="PanelUI-remotetabs-instruction-box" align="center">
                 <hbox pack="center">
                   <image class="fxaSyncIllustrationIssue"/>
                 </hbox>
                 <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
                 <hbox pack="center">
                   <toolbarbutton class="PanelUI-remotetabs-button"
+                                 id="PanelUI-remotetabs-tabsdisabledpane-button"
                                  label="&appMenuRemoteTabs.opensyncprefs.label;"
                                  oncommand="gSync.openPrefs('synced-tabs');"/>
                 </hbox>
               </vbox>
             </hbox>
             <!-- Sync is ready to Sync but we are still fetching the tabs to show -->
             <vbox id="PanelUI-remotetabs-fetching">
               <!-- Show intentionally blank panel, see bug 1239845 -->
@@ -560,52 +561,56 @@
           <vbox id="PanelUI-remotetabs-setupsync"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 hidden="true">
             <image class="fxaSyncIllustration"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.welcome.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
+                           id="PanelUI-remotetabs-setupsync-button"
                            label="&appMenuRemoteTabs.signintosync.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
           <!-- When Sync is not enabled -->
           <vbox id="PanelUI-remotetabs-syncdisabled"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 hidden="true">
             <image class="fxaSyncIllustration"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.welcome.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
+                           id="PanelUI-remotetabs-syncdisabled-button"
                            label="&appMenuRemoteTabs.turnonsync.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
           <!-- When Sync needs re-authentication -->
           <vbox id="PanelUI-remotetabs-reauthsync"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 hidden="true">
             <image class="fxaSyncIllustrationIssue"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.welcome.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
+                           id="PanelUI-remotetabs-reauthsync-button"
                            label="&appMenuRemoteTabs.signintosync.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
           <!-- When Sync needs verification -->
           <vbox id="PanelUI-remotetabs-unverified"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 hidden="true">
             <image class="fxaSyncIllustrationIssue"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.unverified.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-button"
+                           id="PanelUI-remotetabs-unverified-button"
                            label="&appMenuRemoteTabs.opensyncprefs.label;"
                            oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
         </hbox>
       </vbox>
     </panelview>
 
     <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
@@ -694,17 +699,17 @@
                    value="Settings" />
             <menulist id="PanelUI-profiler-presets"
                       flex="1"
                       value="custom">
               <menupopup id="PanelUI-profiler-presets-menupopup" presetsbuilt="false">
                 <!-- The rest of the values get dynamically inserted. The "presetsbuilt"
                     attribute will get updated to "true" once the presets have been
                     built. -->
-                <menuitem label="Custom" value="custom"/>
+                <menuitem id="PanelUI-profiler-presets-custom" label="Custom" value="custom"/>
               </menupopup>
             </menulist>
             <!-- The following description gets inserted dynamically. -->
             <description id="PanelUI-profiler-content-description" />
             <hbox id="PanelUI-profiler-content-custom">
               <button id="PanelUI-profiler-content-custom-button">
                 Edit Settingsā€¦
               </button>
@@ -964,17 +969,17 @@
                        key="viewBookmarksSidebarKb"
                        oncommand="SidebarUI.toggle('viewBookmarksSidebar', this);">
                        <observes element="sidebar-box" attribute="positionend"/>
         </toolbarbutton>
         <toolbarbutton id="panelMenu_viewBookmarksToolbar"
                        class="subviewbutton subviewbutton-iconic"
                        label-show="&viewBookmarksToolbar.label;"
                        label-hide="&hideBookmarksToolbar.label;"
-                       oncommand="BookmarkingUI.toggleBookmarksToolbar();"/>
+                       oncommand="BookmarkingUI.toggleBookmarksToolbar('bookmark-tools');"/>
       </vbox>
     </panelview>
 
     <panelview id="PanelUI-whatsNew" class="PanelUI-subView">
       <vbox class="panel-subview-body">
         <toolbaritem id="PanelUI-whatsNew-content"
                      orient="vertical"
                      smoothscroll="false">
--- a/browser/components/extensions/ext-browser.json
+++ b/browser/components/extensions/ext-browser.json
@@ -6,16 +6,17 @@
     "paths": [
       ["bookmarks"]
     ]
   },
   "browserAction": {
     "url": "chrome://browser/content/parent/ext-browserAction.js",
     "schema": "chrome://extensions/content/schemas/browser_action.json",
     "scopes": ["addon_parent"],
+    "events": ["update", "uninstall", "disable"],
     "manifest": ["browser_action"],
     "paths": [
       ["browserAction"]
     ]
   },
   "browsingData": {
     "url": "chrome://browser/content/parent/ext-browsingData.js",
     "schema": "chrome://browser/content/schemas/browsing_data.json",
@@ -138,16 +139,17 @@
     "paths": [
       ["omnibox"]
     ]
   },
   "pageAction": {
     "url": "chrome://browser/content/parent/ext-pageAction.js",
     "schema": "chrome://extensions/content/schemas/page_action.json",
     "scopes": ["addon_parent"],
+    "events": ["update", "uninstall", "disable"],
     "manifest": ["page_action"],
     "paths": [
       ["pageAction"]
     ]
   },
   "pkcs11": {
     "url": "chrome://browser/content/parent/ext-pkcs11.js",
     "schema": "chrome://browser/content/schemas/pkcs11.json",
--- a/browser/components/extensions/parent/ext-browserAction.js
+++ b/browser/components/extensions/parent/ext-browserAction.js
@@ -26,16 +26,21 @@ ChromeUtils.defineModuleGetter(
   "setTimeout",
   "resource://gre/modules/Timer.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
   "ViewPopup",
   "resource:///modules/ExtensionPopups.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "BrowserUsageTelemetry",
+  "resource:///modules/BrowserUsageTelemetry.jsm"
+);
 
 var { DefaultWeakMap } = ExtensionUtils;
 
 var { ExtensionParent } = ChromeUtils.import(
   "resource://gre/modules/ExtensionParent.jsm"
 );
 var { BrowserActionBase } = ChromeUtils.import(
   "resource://gre/modules/ExtensionActions.jsm"
@@ -52,16 +57,20 @@ XPCOMUtils.defineLazyGetter(this, "brows
   return {
     navbar: CustomizableUI.AREA_NAVBAR,
     menupanel: CustomizableUI.AREA_FIXED_OVERFLOW_PANEL,
     tabstrip: CustomizableUI.AREA_TABSTRIP,
     personaltoolbar: CustomizableUI.AREA_BOOKMARKS,
   };
 });
 
+function actionWidgetId(widgetId) {
+  return `${widgetId}-browser-action`;
+}
+
 class BrowserAction extends BrowserActionBase {
   constructor(extension, buttonDelegate) {
     let tabContext = new TabContext(target => {
       let window = target.ownerGlobal;
       if (target === window) {
         return this.getContextData(null);
       }
       return tabContext.get(window);
@@ -117,32 +126,63 @@ this.browserAction = class extends Exten
       await StartupCache.get(
         extension,
         ["browserAction", "default_icon_data"],
         () => this.getIconData(this.action.getIcon())
       )
     );
 
     let widgetId = makeWidgetId(extension.id);
-    this.id = `${widgetId}-browser-action`;
+    this.id = actionWidgetId(widgetId);
     this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
     this.widget = null;
 
     this.pendingPopup = null;
     this.pendingPopupTimeout = null;
     this.eventQueue = [];
 
     this.tabManager = extension.tabManager;
     this.browserStyle = options.browser_style;
 
     browserActionMap.set(extension, this);
 
     this.build();
   }
 
+  static onUpdate(id, manifest) {
+    if (!("browser_action" in manifest)) {
+      // If the new version has no browser action then mark this widget as
+      // hidden in the telemetry. If it is already marked hidden then this will
+      // do nothing.
+      BrowserUsageTelemetry.recordWidgetChange(
+        actionWidgetId(makeWidgetId(id)),
+        null,
+        "addon"
+      );
+    }
+  }
+
+  static onDisable(id) {
+    BrowserUsageTelemetry.recordWidgetChange(
+      actionWidgetId(makeWidgetId(id)),
+      null,
+      "addon"
+    );
+  }
+
+  static onUninstall(id) {
+    // If the telemetry already has this widget as hidden then this will not
+    // record anything.
+    BrowserUsageTelemetry.recordWidgetChange(
+      actionWidgetId(makeWidgetId(id)),
+      null,
+      "addon"
+    );
+  }
+
   onShutdown() {
     browserActionMap.delete(this.extension);
     this.action.onShutdown();
 
     CustomizableUI.destroyWidget(this.id);
 
     this.clearPopup();
   }
@@ -263,16 +303,29 @@ this.browserAction = class extends Exten
           event.preventDefault();
           this.emit("click", tabbrowser.selectedBrowser);
           // Ensure we close any popups this node was in:
           CustomizableUI.hidePanelForNode(event.target);
         }
       },
     });
 
+    if (this.extension.startupReason != "APP_STARTUP") {
+      // Make sure the browser telemetry has the correct state for this widget.
+      // Defer loading BrowserUsageTelemetry until after startup is complete.
+      ExtensionParent.browserStartupPromise.then(() => {
+        let placement = CustomizableUI.getPlacementOfWidget(widget.id);
+        BrowserUsageTelemetry.recordWidgetChange(
+          widget.id,
+          placement?.area || null,
+          "addon"
+        );
+      });
+    }
+
     this.widget = widget;
   }
 
   /**
    * Triggers this browser action for the given window, with the same effects as
    * if it were clicked by a user.
    *
    * This has no effect if the browser action is disabled for, or not
--- a/browser/components/extensions/parent/ext-pageAction.js
+++ b/browser/components/extensions/parent/ext-pageAction.js
@@ -16,19 +16,27 @@ ChromeUtils.defineModuleGetter(
   "PageActions",
   "resource:///modules/PageActions.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
   "PanelPopup",
   "resource:///modules/ExtensionPopups.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "BrowserUsageTelemetry",
+  "resource:///modules/BrowserUsageTelemetry.jsm"
+);
 
 var { DefaultWeakMap } = ExtensionUtils;
 
+var { ExtensionParent } = ChromeUtils.import(
+  "resource://gre/modules/ExtensionParent.jsm"
+);
 var { PageActionBase } = ChromeUtils.import(
   "resource://gre/modules/ExtensionActions.jsm"
 );
 
 // WeakMap[Extension -> PageAction]
 let pageActionMap = new WeakMap();
 
 class PageAction extends PageActionBase {
@@ -50,16 +58,35 @@ class PageAction extends PageActionBase 
   }
 }
 
 this.pageAction = class extends ExtensionAPI {
   static for(extension) {
     return pageActionMap.get(extension);
   }
 
+  static onUpdate(id, manifest) {
+    if (!("page_action" in manifest)) {
+      // If the new version has no page action then mark this widget as hidden
+      // in the telemetry. If it is already marked hidden then this will do
+      // nothing.
+      BrowserUsageTelemetry.recordWidgetChange(makeWidgetId(id), null, "addon");
+    }
+  }
+
+  static onDisable(id) {
+    BrowserUsageTelemetry.recordWidgetChange(makeWidgetId(id), null, "addon");
+  }
+
+  static onUninstall(id) {
+    // If the telemetry already has this widget as hidden then this will not
+    // record anything.
+    BrowserUsageTelemetry.recordWidgetChange(makeWidgetId(id), null, "addon");
+  }
+
   async onManifestEntry(entryName) {
     let { extension } = this;
     let options = extension.manifest.page_action;
 
     this.action = new PageAction(extension, this);
     await this.action.loadIconData();
 
     let widgetId = makeWidgetId(extension.id);
@@ -122,16 +149,30 @@ this.pageAction = class extends Extensio
           onPlacedInPanel: buttonNode => onPlacedHandler(buttonNode, true),
           onPlacedInUrlbar: buttonNode => onPlacedHandler(buttonNode, false),
           onRemovedFromWindow: browserWindow => {
             browserWindow.document.removeEventListener("popupshowing", this);
           },
         })
       );
 
+      if (this.extension.startupReason != "APP_STARTUP") {
+        // Make sure the browser telemetry has the correct state for this widget.
+        // Defer loading BrowserUsageTelemetry until after startup is complete.
+        ExtensionParent.browserStartupPromise.then(() => {
+          BrowserUsageTelemetry.recordWidgetChange(
+            widgetId,
+            this.browserPageAction.pinnedToUrlbar
+              ? "page-action-buttons"
+              : null,
+            "addon"
+          );
+        });
+      }
+
       // If the page action is only enabled in some URLs, do pattern matching in
       // the active tabs and update the button if necessary.
       if (this.action.getProperty(null, "enabled") === undefined) {
         for (let window of windowTracker.browserWindows()) {
           let tab = window.gBrowser.selectedTab;
           if (this.action.isShownForTab(tab)) {
             this.updateButton(window);
           }
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -54,16 +54,21 @@ ChromeUtils.defineModuleGetter(
   "ResetProfile",
   "resource://gre/modules/ResetProfile.jsm"
 );
 ChromeUtils.defineModuleGetter(
   this,
   "UpdateUtils",
   "resource://gre/modules/UpdateUtils.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "BrowserUsageTelemetry",
+  "resource:///modules/BrowserUsageTelemetry.jsm"
+);
 
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL = "browser.uitour.loglevel";
 
 const BACKGROUND_PAGE_ACTIONS_ALLOWED = new Set([
   "forceShowReaderIcon",
   "getConfiguration",
   "getTreatmentTag",
@@ -1982,16 +1987,21 @@ var UITour = {
       );
       return;
     }
 
     CustomizableUI.addWidgetToArea(
       aTarget.widgetName,
       CustomizableUI.AREA_NAVBAR
     );
+    BrowserUsageTelemetry.recordWidgetChange(
+      aTarget.widgetName,
+      CustomizableUI.AREA_NAVBAR,
+      "uitour"
+    );
     this.sendPageCallback(aBrowser, aCallbackID);
   },
 
   _addAnnotationPanelMutationObserver(aPanelEl) {
     if (AppConstants.platform == "linux") {
       let observer = this._annotationPanelMutationObservers.get(aPanelEl);
       if (observer) {
         return;
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -1884,16 +1884,17 @@ class UrlbarInput {
     ) {
       insertLocation = insertLocation.nextElementSibling;
     }
     if (!insertLocation) {
       return;
     }
 
     let pasteAndGo = this.document.createXULElement("menuitem");
+    pasteAndGo.id = "paste-and-go";
     let label = Services.strings
       .createBundle("chrome://browser/locale/browser.properties")
       .GetStringFromName("pasteAndGo.label");
     pasteAndGo.setAttribute("label", label);
     pasteAndGo.setAttribute("anonid", "paste-and-go");
     pasteAndGo.addEventListener("command", () => {
       this._suppressStartQuery = true;
 
--- a/browser/docs/BrowserUsageTelemetry.rst
+++ b/browser/docs/BrowserUsageTelemetry.rst
@@ -11,18 +11,111 @@ The measurements recording begins right 
 Search telemetry
 ================
 This module exposes the ``recordSearch`` method, which serves as the main entry point for recording search related Telemetry. It records only the search *counts* per engine and the origin of the search, but nothing pertaining the search contents themselves.
 
 As the transition to the ``BrowserUsageTelemetry`` happens, the ``recordSearch`` calls are dispatched through `BrowserSearch.recordSearchInTelemetry <https://dxr.mozilla.org/mozilla-central/rev/3e73fd638e687a4d7f46613586e5156b8e2af846/browser/base/content/browser.js#3752>`_, that is called by all the search related UI components (urlbar, searchbar, context menu and about\:\* pages).
 
 A list of the components recording search Telemetry can be found using the following `DXR search <https://dxr.mozilla.org/mozilla-central/search?q=recordSearchInTelemetry>`_.
 
-Measured interactions
-=====================
-The usage telemetry module currently measures these interactions with the browser:
+Tab and window interactions
+===========================
+The usage telemetry module currently measures these interactions with the browser's tabs and windows:
 
 - *tab and window engagement*: counts the number of non-private tabs and windows opened in a subsession, after the session is restored (see e.g. ``browser.engagement.max_concurrent_tab_count``);
 - *URI loads*: counts the number of page loads (doesn't track and send the addresses, just the counts) directly triggered by the users (see ``browser.engagement.total_uri_count``);
 - *navigation events*: at this time, this only counts the number of time a page load is triggered by a particular UI interaction (e.g. by searching through the URL bar, see ``browser.engagement.navigation.urlbar``).
 
 
 Please see `Scalars.yaml <https://dxr.mozilla.org/mozilla-central/source/toolkit/components/telemetry/Scalars.yaml>`_ for the full list of tracked interactions.
+
+Customizable UI
+===============
+
+This telemetry records information about the positions of toolbar items and when
+the user interacts with them. It is submitted as scalar values along with the
+normal telemetry ping. There are a number of different parts to this telemetry:
+
+UI Areas
+--------
+
+For the purposes of this telemetry a set of areas are defined:
+
+* In the main browser UI:
+
+  * ``menu-bar`` - The main menu.
+  * ``menu-toolbar`` - The normally hidden toolbar that holds the main menu.
+  * ``drag-space`` - The optional drag space.
+  * ``titlebar`` - The optional title bar.
+  * ``tabs-bar`` - The area where tabs are displayed.
+  * ``bookmarks-bar`` - The bookmarks toolbar.
+  * ``app-menu`` - The main application (hamburger) menu.
+  * ``tabs-context`` - The context menu shown from right-clicking a tab.
+  * ``content-context`` - The context menu shown from right-clicking the web page.
+  * ``widget-overflow-list`` - Items that have overflowed the available space.
+  * ``pinned-overflow-menu`` - Items that the user has pinned to the toolbar overflow menu.
+  * ``pageaction-urlbar`` - Page actions buttons in the address bar.
+  * ``pageaction-panel`` - The page action (meatball) menu.
+  * ``nav-bar-start`` - The area of the navigation toolbar before the address bar.
+  * ``nav-bar-end`` - The area of the navigastion toolbar after the address bar.
+
+* In ``about:preferences`` the different cagtegories are used:
+
+  * ``preferences-paneGeneral``
+  * ``preferences-paneHome``
+  * ``preferences-panePrivacy``
+  * ``preferences-paneSearch``
+  * ``preferences-paneSearchResults``
+  * ``preferences-paneSync``
+  * ``preferences-paneContainers``
+
+Widget Identifiers
+------------------
+
+In order to uniquely identify a visual element a set of heuristics are used:
+
+#. If the element is one of the customizable toolbar items then that item's ID
+   is used.
+#. If the DOM element has an ID set then that is used.
+#. If the DOM element's class contains one of ``bookmark-item``,
+   ``tab-icon-sound`` or ``tab-close-button`` then that is used.
+#. If the DOM element has a ``preference`` ``key``, ``command``, ``observes`` or
+   ``data-l10n-id`` attribute then that is used.
+#. If there is still no identifier then this is repeated for the DOM element's
+   parent element.
+
+Widget Locations
+----------------
+
+The keyed scalar ``browser.ui.toolbar_widgets`` records the position of widgets in
+the UI. At startup the positions of widgets are collected and recorded by
+setting the scalar key ``<widget id>_pinned_<area>`` to true. The widget ID are
+the IDs of the elements in the DOM. The area is one of the areas listed above
+from the browser UI that can be customised.
+
+For the areas that can be controlled the scalar keys ``<area>_<off/on>`` are set.
+
+Widget Customization
+--------------------
+
+The scalar ``browser.ui.customized_widgets`` records whenever the user moves a
+widget around the toolbars or shows or hides some of the areas. When a change
+occurs the scalar with the key ``<widget id>_<action>_<old area>_<new area>_<reason>``
+is incremented. The action can be one of ``move``, ``add`` or ``remove``. Old
+area and new area are the previous and now locations of the widget. In the case
+of ``add`` or ``remove`` actions one of the areas will be ``na``. For areas that
+can be shown or hidden the areas will be ``off`` or ``on``. The reason is a simple
+string that indicates what caused the move to happen (drag, context menu, etc.).
+
+UI Interactions
+---------------
+
+The scalars ``browser.ui.interaction.<area>`` record how often the use
+interacts with the browser. The area is one of those above with the addition of
+``keyboard`` for keyboard shortcuts.
+
+When an interaction occurs the widget's identifier is used as the key and the
+scalar is incremented. If the widget is provided by an add-on then the add-on
+identifier is dropped and an identifier of the form ``addonX`` is used where X
+is a number. The number used is stable for a single session. Everytime the user
+moves or interacts with an add-on the same number is used but then the numbers
+for each add-on may change after Firefox has been restarted.
+
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ b/browser/modules/BrowserUsageTelemetry.jsm
@@ -14,16 +14,19 @@ var EXPORTED_SYMBOLS = [
   "MINIMUM_TAB_COUNT_INTERVAL_MS",
 ];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
+  CustomizableUI: "resource:///modules/CustomizableUI.jsm",
+  PageActions: "resource:///modules/PageActions.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   SearchTelemetry: "resource:///modules/SearchTelemetry.jsm",
   Services: "resource://gre/modules/Services.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
   clearTimeout: "resource://gre/modules/Timer.jsm",
 });
 
 // This pref is in seconds!
@@ -113,16 +116,90 @@ const URLBAR_SELECTED_RESULT_METHODS = {
   click: 2,
   arrowEnterSelection: 3,
   tabEnterSelection: 4,
   rightClickEnter: 5,
 };
 
 const MINIMUM_TAB_COUNT_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes, in ms
 
+// The elements we consider to be interactive.
+const UI_TARGET_ELEMENTS = [
+  "menuitem",
+  "toolbarbutton",
+  "key",
+  "command",
+  "checkbox",
+  "input",
+  "button",
+  "image",
+  "radio",
+  "richlistitem",
+];
+
+// The containers of interactive elements that we care about and their pretty
+// names. These should be listed in order of most-specific to least-specific,
+// when iterating JavaScript will guarantee that ordering and so we will find
+// the most specific area first.
+const BROWSER_UI_CONTAINER_IDS = {
+  "toolbar-menubar": "menu-bar",
+  TabsToolbar: "tabs-bar",
+  PersonalToolbar: "bookmarks-bar",
+  "appMenu-popup": "app-menu",
+  tabContextMenu: "tabs-context",
+  contentAreaContextMenu: "content-context",
+  "widget-overflow-list": "overflow-menu",
+  "widget-overflow-fixed-list": "pinned-overflow-menu",
+  "page-action-buttons": "pageaction-urlbar",
+  pageActionPanel: "pageaction-panel",
+
+  // This should appear last as some of the above are inside the nav bar.
+  "nav-bar": "nav-bar",
+};
+
+const IGNORABLE_EVENTS = new WeakMap();
+
+const KNOWN_ADDONS = [];
+
+function telemetryId(widgetId, obscureAddons = true) {
+  // Add-on IDs need to be obscured.
+  function addonId(id) {
+    if (!obscureAddons) {
+      return id;
+    }
+
+    let pos = KNOWN_ADDONS.indexOf(id);
+    if (pos < 0) {
+      pos = KNOWN_ADDONS.length;
+      KNOWN_ADDONS.push(id);
+    }
+    return `addon${pos}`;
+  }
+
+  if (widgetId.endsWith("-browser-action")) {
+    widgetId = addonId(
+      widgetId.substring(0, widgetId.length - "-browser-action".length)
+    );
+  } else if (widgetId.startsWith("pageAction-")) {
+    let actionId;
+    if (widgetId.startsWith("pageAction-urlbar-")) {
+      actionId = widgetId.substring("pageAction-urlbar-".length);
+    } else if (widgetId.startsWith("pageAction-panel-")) {
+      actionId = widgetId.substring("pageAction-panel-".length);
+    }
+
+    if (actionId) {
+      let action = PageActions.actionForID(actionId);
+      widgetId = action?._isMozillaAction ? actionId : addonId(actionId);
+    }
+  }
+
+  return widgetId.replace(/_/g, "-");
+}
+
 function getOpenTabsAndWinsCounts() {
   let loadedTabCount = 0;
   let tabCount = 0;
   let winCount = 0;
 
   for (let win of Services.wm.getEnumerator("navigator:browser")) {
     winCount++;
     tabCount += win.gBrowser.tabs.length;
@@ -332,16 +409,28 @@ let URICountListener = {
 let BrowserUsageTelemetry = {
   _inited: false,
 
   init() {
     this._lastRecordTabCount = 0;
     this._lastRecordLoadedTabCount = 0;
     this._setupAfterRestore();
     this._inited = true;
+
+    Services.prefs.addObserver("browser.tabs.extraDragSpace", this);
+    Services.prefs.addObserver("browser.tabs.drawInTitlebar", this);
+
+    this._recordUITelemetry();
+  },
+
+  /**
+   * Resets the masked add-on identifiers. Only for use in tests.
+   */
+  _resetAddonIds() {
+    KNOWN_ADDONS.length = 0;
   },
 
   /**
    * Handle subsession splits in the parent process.
    */
   afterSubsessionSplit() {
     // Scalars just got cleared due to a subsession split. We need to set the maximum
     // concurrent tab and window counts so that they reflect the correct value for the
@@ -376,16 +465,38 @@ let BrowserUsageTelemetry = {
   observe(subject, topic, data) {
     switch (topic) {
       case DOMWINDOW_OPENED_TOPIC:
         this._onWindowOpen(subject);
         break;
       case TELEMETRY_SUBSESSIONSPLIT_TOPIC:
         this.afterSubsessionSplit();
         break;
+      case "nsPref:changed":
+        switch (data) {
+          case "browser.tabs.extraDragSpace":
+            this._recordWidgetChange(
+              "drag-space",
+              Services.prefs.getBoolPref("browser.tabs.extraDragSpace")
+                ? "on"
+                : "off",
+              "pref"
+            );
+            break;
+          case "browser.tabs.drawInTitlebar":
+            this._recordWidgetChange(
+              "titlebar",
+              Services.prefs.getBoolPref("browser.tabs.drawInTitlebar")
+                ? "off"
+                : "on",
+              "pref"
+            );
+            break;
+        }
+        break;
     }
   },
 
   handleEvent(event) {
     switch (event.type) {
       case "TabOpen":
         this._onTabOpen(getOpenTabsAndWinsCounts());
         break;
@@ -664,20 +775,377 @@ let BrowserUsageTelemetry = {
       counts.tabCount
     );
     Services.telemetry.scalarSetMaximum(
       MAX_WINDOW_COUNT_SCALAR_NAME,
       counts.winCount
     );
   },
 
+  _buildWidgetPositions() {
+    let widgetMap = new Map();
+
+    const toolbarState = nodeId => {
+      let value = Services.xulStore.getValue(
+        AppConstants.BROWSER_CHROME_URL,
+        nodeId,
+        "collapsed"
+      );
+      if (value) {
+        return value == "true" ? "off" : "on";
+      }
+      return "off";
+    };
+
+    widgetMap.set(
+      BROWSER_UI_CONTAINER_IDS.PersonalToolbar,
+      toolbarState("PersonalToolbar")
+    );
+
+    let menuBarHidden =
+      Services.xulStore.getValue(
+        AppConstants.BROWSER_CHROME_URL,
+        "toolbar-menubar",
+        "autohide"
+      ) != "false";
+
+    widgetMap.set("menu-toolbar", menuBarHidden ? "off" : "on");
+
+    widgetMap.set(
+      "drag-space",
+      Services.prefs.getBoolPref("browser.tabs.extraDragSpace") ? "on" : "off"
+    );
+
+    // Drawing in the titlebar means not showing the titlebar, hence the negation.
+    widgetMap.set(
+      "titlebar",
+      Services.prefs.getBoolPref("browser.tabs.drawInTitlebar", true)
+        ? "off"
+        : "on"
+    );
+
+    for (let area of CustomizableUI.areas) {
+      if (!(area in BROWSER_UI_CONTAINER_IDS)) {
+        continue;
+      }
+
+      let position = BROWSER_UI_CONTAINER_IDS[area];
+      if (area == "nav-bar") {
+        position = `${BROWSER_UI_CONTAINER_IDS[area]}-start`;
+      }
+
+      let widgets = CustomizableUI.getWidgetsInArea(area);
+
+      for (let widget of widgets) {
+        if (!widget) {
+          continue;
+        }
+
+        if (widget.id.startsWith("customizableui-special-")) {
+          continue;
+        }
+
+        if (area == "nav-bar" && widget.id == "urlbar-container") {
+          position = `${BROWSER_UI_CONTAINER_IDS[area]}-end`;
+          continue;
+        }
+
+        widgetMap.set(widget.id, position);
+      }
+    }
+
+    let actions = PageActions.actions;
+    for (let action of actions) {
+      if (action.pinnedToUrlbar) {
+        widgetMap.set(action.id, "pageaction-urlbar");
+      }
+    }
+
+    return widgetMap;
+  },
+
+  _getWidgetID(node) {
+    // We want to find a sensible ID for this element.
+    if (!node) {
+      return null;
+    }
+
+    // See if this is a customizable widget.
+    if (node.ownerDocument.URL == AppConstants.BROWSER_CHROME_URL) {
+      // First find if it is inside one of the customizable areas.
+      for (let area of CustomizableUI.areas) {
+        if (node.closest(`#${area}`)) {
+          for (let widget of CustomizableUI.getWidgetIdsInArea(area)) {
+            if (
+              // We care about the buttons on the tabs themselves.
+              widget == "tabbrowser-tabs" ||
+              // We care about the page action and other buttons in here.
+              widget == "urlbar-container" ||
+              // We care about individual bookmarks here.
+              widget == "personal-bookmarks"
+            ) {
+              continue;
+            }
+
+            if (node.closest(`#${widget}`)) {
+              return widget;
+            }
+          }
+          break;
+        }
+      }
+    }
+
+    if (node.id) {
+      return node.id;
+    }
+
+    // A couple of special cases in the tabs.
+    for (let cls of ["bookmark-item", "tab-icon-sound", "tab-close-button"]) {
+      if (node.classList.contains(cls)) {
+        return cls;
+      }
+    }
+
+    // One of these will at least let us know what the widget is for.
+    for (let idAttribute of [
+      "preference",
+      "key",
+      "command",
+      "observes",
+      "data-l10n-id",
+    ]) {
+      if (node.hasAttribute(idAttribute)) {
+        return node.getAttribute(idAttribute);
+      }
+    }
+
+    return this._getWidgetID(node.parentElement);
+  },
+
+  _getWidgetContainer(node) {
+    if (node.localName == "key") {
+      return "keyboard";
+    }
+
+    if (node.ownerDocument.URL == AppConstants.BROWSER_CHROME_URL) {
+      // Find the container holding this element.
+      for (let containerId of Object.keys(BROWSER_UI_CONTAINER_IDS)) {
+        let container = node.ownerDocument.getElementById(containerId);
+        if (container && container.contains(node)) {
+          return BROWSER_UI_CONTAINER_IDS[containerId];
+        }
+      }
+    } else if (node.ownerDocument.URL.startsWith("about:preferences")) {
+      // Find the element's category.
+      let container = node.closest("[data-category]");
+      if (!container) {
+        return null;
+      }
+
+      return `preferences_${container.getAttribute("data-category")}`;
+    }
+
+    return null;
+  },
+
+  lastClickTarget: null,
+
+  ignoreEvent(event) {
+    IGNORABLE_EVENTS.set(event, true);
+  },
+
+  _recordCommand(event) {
+    if (IGNORABLE_EVENTS.get(event)) {
+      return;
+    }
+
+    let types = [event.type];
+    let sourceEvent = event;
+    while (sourceEvent.sourceEvent) {
+      sourceEvent = sourceEvent.sourceEvent;
+      types.push(sourceEvent.type);
+    }
+
+    let lastTarget = this.lastClickTarget?.get();
+    if (
+      lastTarget &&
+      sourceEvent.type == "command" &&
+      sourceEvent.target.contains(lastTarget)
+    ) {
+      // Ignore a command event triggered by a click.
+      this.lastClickTarget = null;
+      return;
+    }
+
+    this.lastClickTarget = null;
+
+    if (sourceEvent.type == "click") {
+      // Only care about main button clicks.
+      if (sourceEvent.button != 0) {
+        return;
+      }
+
+      // This click may trigger a command event so retain the target to be able
+      // to dedupe that event.
+      this.lastClickTarget = Cu.getWeakReference(sourceEvent.target);
+    }
+
+    // We should never see events from web content as they are fired in a
+    // content process, but let's be safe.
+    let url = sourceEvent.target.ownerDocument.documentURIObject;
+    if (!url.schemeIs("chrome") && !url.schemeIs("about")) {
+      return;
+    }
+
+    // This is what events targetted  at content will actually look like.
+    if (sourceEvent.target.localName == "browser") {
+      return;
+    }
+
+    // Find the actual element we're interested in.
+    let node = sourceEvent.target;
+    while (!UI_TARGET_ELEMENTS.includes(node.localName)) {
+      node = node.parentNode;
+      if (!node) {
+        // A click on a space or label or something we're not interested in.
+        return;
+      }
+    }
+
+    let item = this._getWidgetID(node);
+    let source = this._getWidgetContainer(node);
+
+    if (item && source) {
+      let scalar = `browser.ui.interaction.${source.replace("-", "_")}`;
+      Services.telemetry.keyedScalarAdd(scalar, telemetryId(item), 1);
+    }
+  },
+
+  /**
+   * Listens for UI interactions in the window.
+   */
+  _addUsageListeners(win) {
+    // Listen for command events from the UI.
+    win.addEventListener("command", event => this._recordCommand(event), true);
+    win.addEventListener("click", event => this._recordCommand(event), true);
+  },
+
+  /**
+   * A public version of the private method to take care of the `nav-bar-start`,
+   * `nav-bar-end` thing that callers shouldn't have to care about. It also
+   * accepts the DOM ids for the areas rather than the cleaner ones we report
+   * to telemetry.
+   */
+  recordWidgetChange(widgetId, newPos, reason) {
+    try {
+      if (newPos) {
+        newPos = BROWSER_UI_CONTAINER_IDS[newPos];
+      }
+
+      if (newPos == "nav-bar") {
+        let { position } = CustomizableUI.getPlacementOfWidget(widgetId);
+        let { position: urlPosition } = CustomizableUI.getPlacementOfWidget(
+          "urlbar-container"
+        );
+        newPos = newPos + (urlPosition > position ? "-start" : "-end");
+      }
+
+      this._recordWidgetChange(widgetId, newPos, reason);
+    } catch (e) {
+      console.error(e);
+    }
+  },
+
+  recordToolbarVisibility(toolbarId, newState, reason) {
+    this._recordWidgetChange(
+      BROWSER_UI_CONTAINER_IDS[toolbarId],
+      newState ? "on" : "off",
+      reason
+    );
+  },
+
+  _recordWidgetChange(widgetId, newPos, reason) {
+    // In some cases (like when add-ons are detected during startup) this gets
+    // called before we've reported the initial positions. Ignore such cases.
+    if (!this.widgetMap) {
+      return;
+    }
+
+    if (widgetId == "urlbar-container") {
+      // We don't report the position of the url bar, it is after nav-bar-start
+      // and before nav-bar-end. But moving it means the widgets around it have
+      // effectively moved so update those.
+      let position = "nav-bar-start";
+      let widgets = CustomizableUI.getWidgetsInArea("nav-bar");
+
+      for (let widget of widgets) {
+        if (!widget) {
+          continue;
+        }
+
+        if (widget.id.startsWith("customizableui-special-")) {
+          continue;
+        }
+
+        if (widget.id == "urlbar-container") {
+          position = "nav-bar-end";
+          continue;
+        }
+
+        // This will do nothing if the position hasn't changed.
+        this._recordWidgetChange(widget.id, position, reason);
+      }
+
+      return;
+    }
+
+    let oldPos = this.widgetMap.get(widgetId);
+    if (oldPos == newPos) {
+      return;
+    }
+
+    let action = "move";
+
+    if (!oldPos) {
+      action = "add";
+    } else if (!newPos) {
+      action = "remove";
+    }
+
+    let key = `${telemetryId(widgetId, false)}_${action}_${oldPos ??
+      "na"}_${newPos ?? "na"}_${reason}`;
+    Services.telemetry.keyedScalarAdd("browser.ui.customized_widgets", key, 1);
+
+    if (newPos) {
+      this.widgetMap.set(widgetId, newPos);
+    } else {
+      this.widgetMap.delete(widgetId);
+    }
+  },
+
+  _recordUITelemetry() {
+    this.widgetMap = this._buildWidgetPositions();
+
+    for (let [widgetId, position] of this.widgetMap.entries()) {
+      let key = `${telemetryId(widgetId, false)}_pinned_${position}`;
+      Services.telemetry.keyedScalarSet(
+        "browser.ui.toolbar_widgets",
+        key,
+        true
+      );
+    }
+  },
+
   /**
    * Adds listeners to a single chrome window.
    */
   _registerWindow(win) {
+    this._addUsageListeners(win);
+
     win.addEventListener("unload", this);
     win.addEventListener("TabOpen", this, true);
     win.addEventListener("TabPinned", this, true);
 
     win.gBrowser.tabContainer.addEventListener(TAB_RESTORING_TOPIC, this);
     win.gBrowser.addTabsProgressListener(URICountListener);
   },
 
--- a/browser/modules/test/browser/browser.ini
+++ b/browser/modules/test/browser/browser.ini
@@ -36,18 +36,20 @@ support-files =
 skip-if = os != "win" || (os == "win" && bits == 64) # bug 1456807
 [browser_UnsubmittedCrashHandler.js]
 run-if = crashreporter
 skip-if = fission && !webrender # Two startup notifications, this confuses the test. Also, this is an unsupported combo.
 [browser_urlBar_zoom.js]
 skip-if = (os == "mac") || (!debug && os == "linux" && bits == 64 && os_version == "18.04") # Bug 1528429
 [browser_UsageTelemetry.js]
 [browser_UsageTelemetry_domains.js]
+[browser_UsageTelemetry_interaction.js]
 [browser_UsageTelemetry_private_and_restore.js]
 skip-if = verify && debug
+[browser_UsageTelemetry_toolbars.js]
 [browser_UsageTelemetry_uniqueOriginsVisitedInPast24Hours.js]
 [browser_UsageTelemetry_urlbar_extension.js]
 [browser_UsageTelemetry_urlbar_places.js]
 [browser_UsageTelemetry_urlbar_remotetab.js]
 [browser_UsageTelemetry_urlbar_tip.js]
 [browser_UsageTelemetry_urlbar_topsite.js]
 [browser_UsageTelemetry_urlbar.js]
 support-files =
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser/browser_UsageTelemetry_interaction.js
@@ -0,0 +1,399 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+gReduceMotionOverride = true;
+
+const AREAS = [
+  "keyboard",
+  "menu_bar",
+  "tabs_bar",
+  "nav_bar",
+  "bookmarks_bar",
+  "app_menu",
+  "tabs_context",
+  "content_context",
+  "overflow_menu",
+  "pinned_overflow_menu",
+  "pageaction_urlbar",
+  "pageaction_panel",
+
+  "preferences_paneHome",
+  "preferences_paneGeneral",
+  "preferences_panePrivacy",
+  "preferences_paneSearch",
+  "preferences_paneSearchResults",
+  "preferences_paneSync",
+  "preferences_paneContainers",
+];
+
+// Checks that the correct number of clicks are registered against the correct
+// keys in the scalars.
+function assertInteractionScalars(expectedAreas) {
+  let processScalars =
+    Services.telemetry.getSnapshotForKeyedScalars("main", true)?.parent ?? {};
+
+  for (let source of AREAS) {
+    let scalars = processScalars?.[`browser.ui.interaction.${source}`] ?? {};
+
+    let expected = expectedAreas[source] ?? {};
+
+    let expectedKeys = new Set(
+      Object.keys(scalars).concat(Object.keys(expected))
+    );
+    for (let key of expectedKeys) {
+      Assert.equal(
+        scalars[key],
+        expected[key],
+        `Expected to see the correct value for ${key} in ${source}.`
+      );
+    }
+  }
+}
+
+const elem = id => document.getElementById(id);
+const click = el => {
+  if (typeof el == "string") {
+    el = elem(el);
+  }
+
+  EventUtils.synthesizeMouseAtCenter(el, {}, window);
+};
+
+add_task(async function toolbarButtons() {
+  await BrowserTestUtils.withNewTab("http://example.com", async () => {
+    Services.telemetry.getSnapshotForKeyedScalars("main", true);
+
+    let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+    let tabClose = BrowserTestUtils.waitForTabClosing(newTab);
+
+    let transition = BrowserTestUtils.waitForTransition(
+      elem("PersonalToolbar")
+    );
+    CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
+    registerCleanupFunction(() => {
+      CustomizableUI.setToolbarVisibility("PersonalToolbar", false);
+    });
+    await transition;
+
+    let tabs = elem("tabbrowser-tabs");
+    if (!tabs.hasAttribute("overflow")) {
+      tabs.setAttribute("overflow", "true");
+      registerCleanupFunction(() => {
+        tabs.removeAttribute("overflow");
+      });
+    }
+
+    click("stop-reload-button");
+    click("back-button");
+    click("back-button");
+
+    // Make sure the all tabs panel is in the document.
+    gTabsPanel.initElements();
+    let view = elem("allTabsMenu-allTabsView");
+    let shown = BrowserTestUtils.waitForEvent(view, "ViewShown");
+    click("alltabs-button");
+    await shown;
+
+    let hidden = BrowserTestUtils.waitForEvent(view, "ViewHiding");
+    gTabsPanel.hideAllTabsPanel();
+    await hidden;
+
+    click(newTab.querySelector(".tab-close-button"));
+    await tabClose;
+    click(document.querySelector("#PlacesToolbarItems .bookmark-item"));
+
+    let pagePanel = elem("pageActionPanel");
+    shown = BrowserTestUtils.waitForEvent(pagePanel, "popupshown");
+    click("pageActionButton");
+    await shown;
+
+    hidden = BrowserTestUtils.waitForEvent(pagePanel, "popuphidden");
+    click("pageAction-panel-copyURL");
+    await hidden;
+
+    assertInteractionScalars({
+      nav_bar: {
+        "stop-reload-button": 1,
+        "back-button": 2,
+      },
+      tabs_bar: {
+        "alltabs-button": 1,
+        "tab-close-button": 1,
+      },
+      bookmarks_bar: {
+        "bookmark-item": 1,
+      },
+      pageaction_urlbar: {
+        pageActionButton: 1,
+      },
+      pageaction_panel: {
+        copyURL: 1,
+      },
+    });
+  });
+});
+
+add_task(async function contextMenu() {
+  await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+    Services.telemetry.getSnapshotForKeyedScalars("main", true);
+
+    let tab = gBrowser.getTabForBrowser(browser);
+    let context = elem("tabContextMenu");
+    let shown = BrowserTestUtils.waitForEvent(context, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(
+      tab,
+      { type: "contextmenu", button: 2 },
+      window
+    );
+    await shown;
+
+    let hidden = BrowserTestUtils.waitForEvent(context, "popuphidden");
+    click("context_toggleMuteTab");
+    await hidden;
+
+    assertInteractionScalars({
+      tabs_context: {
+        "context-toggleMuteTab": 1,
+      },
+    });
+  });
+});
+
+add_task(async function appMenu() {
+  await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+    Services.telemetry.getSnapshotForKeyedScalars("main", true);
+
+    let shown = BrowserTestUtils.waitForEvent(
+      elem("appMenu-popup"),
+      "popupshown"
+    );
+    click("PanelUI-menu-button");
+    await shown;
+
+    let hidden = BrowserTestUtils.waitForEvent(
+      elem("appMenu-popup"),
+      "popuphidden"
+    );
+    click("appMenu-find-button");
+    await hidden;
+
+    assertInteractionScalars({
+      nav_bar: {
+        "PanelUI-menu-button": 1,
+      },
+      app_menu: {
+        "appMenu-find-button": 1,
+      },
+    });
+  });
+});
+
+add_task(async function devtools() {
+  await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+    Services.telemetry.getSnapshotForKeyedScalars("main", true);
+
+    let shown = BrowserTestUtils.waitForEvent(
+      elem("appMenu-popup"),
+      "popupshown"
+    );
+    click("PanelUI-menu-button");
+    await shown;
+
+    shown = BrowserTestUtils.waitForEvent(
+      elem("PanelUI-developer"),
+      "ViewShown"
+    );
+    click("appMenu-developer-button");
+    await shown;
+
+    let tabOpen = BrowserTestUtils.waitForNewTab(gBrowser);
+    let hidden = BrowserTestUtils.waitForEvent(
+      elem("appMenu-popup"),
+      "popuphidden"
+    );
+    click(
+      document.querySelector(
+        "#PanelUI-developer toolbarbutton[key='key_viewSource']"
+      )
+    );
+    await hidden;
+
+    let tab = await tabOpen;
+    BrowserTestUtils.removeTab(tab);
+
+    // Note that item ID's have '_' converted to '-'.
+    assertInteractionScalars({
+      nav_bar: {
+        "PanelUI-menu-button": 1,
+      },
+      app_menu: {
+        "appMenu-developer-button": 1,
+        "key-viewSource": 1,
+      },
+    });
+  });
+});
+
+add_task(async function webextension() {
+  BrowserUsageTelemetry._resetAddonIds();
+
+  await BrowserTestUtils.withNewTab("http://example.com", async browser => {
+    Services.telemetry.getSnapshotForKeyedScalars("main", true);
+
+    const extension = ExtensionTestUtils.loadExtension({
+      manifest: {
+        version: "1",
+        applications: {
+          gecko: { id: "random_addon@example.com" },
+        },
+        browser_action: {
+          default_icon: "default.png",
+          default_title: "Hello",
+        },
+        page_action: {
+          default_icon: "default.png",
+          default_title: "Hello",
+          show_matches: ["http://example.com/*"],
+        },
+      },
+    });
+
+    await extension.startup();
+
+    // As the first add-on interacted with this should show up as `addon0`.
+
+    click("random_addon_example_com-browser-action");
+    assertInteractionScalars({
+      nav_bar: {
+        addon0: 1,
+      },
+    });
+
+    // Wait for the element to show up.
+    await TestUtils.waitForCondition(() =>
+      elem("pageAction-urlbar-random_addon_example_com")
+    );
+
+    click("pageAction-urlbar-random_addon_example_com");
+    assertInteractionScalars({
+      pageaction_urlbar: {
+        addon0: 1,
+      },
+    });
+
+    const extension2 = ExtensionTestUtils.loadExtension({
+      manifest: {
+        version: "1",
+        applications: {
+          gecko: { id: "random_addon2@example.com" },
+        },
+        browser_action: {
+          default_icon: "default.png",
+          default_title: "Hello",
+        },
+        page_action: {
+          default_icon: "default.png",
+          default_title: "Hello",
+          show_matches: ["http://example.com/*"],
+        },
+      },
+    });
+
+    await extension2.startup();
+
+    // A second extension should be `addon1`.
+
+    click("random_addon2_example_com-browser-action");
+    assertInteractionScalars({
+      nav_bar: {
+        addon1: 1,
+      },
+    });
+
+    // Wait for the element to show up.
+    await TestUtils.waitForCondition(() =>
+      elem("pageAction-urlbar-random_addon2_example_com")
+    );
+
+    click("pageAction-urlbar-random_addon2_example_com");
+    assertInteractionScalars({
+      pageaction_urlbar: {
+        addon1: 1,
+      },
+    });
+
+    // The first should have retained its ID.
+    click("random_addon_example_com-browser-action");
+    assertInteractionScalars({
+      nav_bar: {
+        addon0: 1,
+      },
+    });
+
+    click("pageAction-urlbar-random_addon_example_com");
+    assertInteractionScalars({
+      pageaction_urlbar: {
+        addon0: 1,
+      },
+    });
+
+    await extension.unload();
+
+    // The second should retain its ID.
+    click("random_addon2_example_com-browser-action");
+    click("random_addon2_example_com-browser-action");
+    assertInteractionScalars({
+      nav_bar: {
+        addon1: 2,
+      },
+    });
+
+    click("pageAction-urlbar-random_addon2_example_com");
+    assertInteractionScalars({
+      pageaction_urlbar: {
+        addon1: 1,
+      },
+    });
+
+    await extension2.unload();
+  });
+});
+
+add_task(async function preferences() {
+  let initialized = BrowserTestUtils.waitForEvent(gBrowser, "Initialized");
+  await BrowserTestUtils.withNewTab("about:preferences", async browser => {
+    await initialized;
+
+    Services.telemetry.getSnapshotForKeyedScalars("main", true);
+
+    await BrowserTestUtils.synthesizeMouseAtCenter(
+      "#browserRestoreSession",
+      {},
+      gBrowser.selectedBrowser.browsingContext
+    );
+
+    await BrowserTestUtils.synthesizeMouseAtCenter(
+      "#category-search",
+      {},
+      gBrowser.selectedBrowser.browsingContext
+    );
+
+    await BrowserTestUtils.synthesizeMouseAtCenter(
+      "#searchBarShownRadio",
+      {},
+      gBrowser.selectedBrowser.browsingContext
+    );
+
+    assertInteractionScalars({
+      preferences_paneGeneral: {
+        browserRestoreSession: 1,
+      },
+      preferences_paneSearch: {
+        searchBarShownRadio: 1,
+      },
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser/browser_UsageTelemetry_toolbars.js
@@ -0,0 +1,642 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+gReduceMotionOverride = true;
+
+function enterCustomizationMode(win = window) {
+  let customizationReadyPromise = BrowserTestUtils.waitForEvent(
+    win.gNavToolbox,
+    "customizationready"
+  );
+  win.gCustomizeMode.enter();
+  return customizationReadyPromise;
+}
+
+function leaveCustomizationMode(win = window) {
+  let customizationDonePromise = BrowserTestUtils.waitForEvent(
+    win.gNavToolbox,
+    "aftercustomization"
+  );
+  win.gCustomizeMode.exit();
+  return customizationDonePromise;
+}
+
+Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true);
+registerCleanupFunction(() =>
+  Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck")
+);
+
+// Stolen from browser/components/customizableui/tests/browser/head.js
+function simulateItemDrag(aToDrag, aTarget, aEvent = {}, aOffset = 2) {
+  let ev = aEvent;
+  if (ev == "end" || ev == "start") {
+    let win = aTarget.ownerGlobal;
+    const dwu = win.windowUtils;
+    let bounds = dwu.getBoundsWithoutFlushing(aTarget);
+    if (ev == "end") {
+      ev = {
+        clientX: bounds.right - aOffset,
+        clientY: bounds.bottom - aOffset,
+      };
+    } else {
+      ev = { clientX: bounds.left + aOffset, clientY: bounds.top + aOffset };
+    }
+  }
+  ev._domDispatchOnly = true;
+  EventUtils.synthesizeDrop(
+    aToDrag.parentNode,
+    aTarget,
+    null,
+    null,
+    aToDrag.ownerGlobal,
+    aTarget.ownerGlobal,
+    ev
+  );
+  // Ensure dnd suppression is cleared.
+  EventUtils.synthesizeMouseAtCenter(
+    aTarget,
+    { type: "mouseup" },
+    aTarget.ownerGlobal
+  );
+}
+
+function organizeToolbars(state = {}) {
+  // Set up the defaults for the state.
+  let targetState = Object.assign(
+    {
+      // Areas where widgets can be placed, set to an array of widget IDs.
+      "toolbar-menubar": undefined,
+      PersonalToolbar: undefined,
+      TabsToolbar: ["tabbrowser-tabs", "alltabs-button"],
+      "widget-overflow-fixed-list": undefined,
+      "nav-bar": ["back-button", "forward-button", "urlbar-container"],
+
+      // The page action's that should be in the URL bar.
+      pageActionsInUrlBar: [],
+
+      // Areas to show or hide.
+      dragSpaceVisible: false,
+      titlebarVisible: false,
+      menubarVisible: false,
+      personalToolbarVisible: false,
+    },
+    state
+  );
+
+  for (let area of CustomizableUI.areas) {
+    // Clear out anything there already.
+    for (let widgetId of CustomizableUI.getWidgetIdsInArea(area)) {
+      CustomizableUI.removeWidgetFromArea(widgetId);
+    }
+
+    if (targetState[area]) {
+      // We specify the position explicitly to support the toolbars that have
+      // fixed widgets.
+      let position = 0;
+      for (let widgetId of targetState[area]) {
+        CustomizableUI.addWidgetToArea(widgetId, area, position++);
+      }
+    }
+  }
+
+  CustomizableUI.setToolbarVisibility(
+    "toolbar-menubar",
+    targetState.menubarVisible
+  );
+  CustomizableUI.setToolbarVisibility(
+    "PersonalToolbar",
+    targetState.personalToolbarVisible
+  );
+
+  Services.prefs.setBoolPref(
+    "browser.tabs.extraDragSpace",
+    !targetState.titlebarVisible && targetState.dragSpaceVisible
+  );
+  Services.prefs.setBoolPref(
+    "browser.tabs.drawInTitlebar",
+    !targetState.titlebarVisible
+  );
+
+  for (let action of PageActions.actions) {
+    action.pinnedToUrlbar = targetState.pageActionsInUrlBar.includes(action.id);
+  }
+
+  // Clear out the existing telemetry.
+  Services.telemetry.getSnapshotForKeyedScalars("main", true);
+}
+
+function assertVisibilityScalars(expected) {
+  let scalars =
+    Services.telemetry.getSnapshotForKeyedScalars("main", true)?.parent?.[
+      "browser.ui.toolbar_widgets"
+    ] ?? {};
+
+  // Only some platforms have the menubar items.
+  if (AppConstants.MENUBAR_CAN_AUTOHIDE) {
+    expected.push("menubar-items_pinned_menu-bar");
+  }
+
+  let keys = new Set(expected.concat(Object.keys(scalars)));
+  for (let key of keys) {
+    Assert.ok(expected.includes(key), `Scalar key ${key} was unexpected.`);
+    Assert.ok(scalars[key], `Expected to see see scalar key ${key} be true.`);
+  }
+}
+
+function assertCustomizeScalars(expected) {
+  let scalars =
+    Services.telemetry.getSnapshotForKeyedScalars("main", true)?.parent?.[
+      "browser.ui.customized_widgets"
+    ] ?? {};
+
+  let keys = new Set(Object.keys(expected).concat(Object.keys(scalars)));
+  for (let key of keys) {
+    Assert.equal(
+      scalars[key],
+      expected[key],
+      `Expected to see the correct value for scalar ${key}.`
+    );
+  }
+}
+
+add_task(async function widgetPositions() {
+  organizeToolbars();
+
+  BrowserUsageTelemetry._recordUITelemetry();
+
+  assertVisibilityScalars([
+    "drag-space_pinned_off",
+    "menu-toolbar_pinned_off",
+    "titlebar_pinned_off",
+    "bookmarks-bar_pinned_off",
+
+    "tabbrowser-tabs_pinned_tabs-bar",
+    "alltabs-button_pinned_tabs-bar",
+
+    "forward-button_pinned_nav-bar-start",
+    "back-button_pinned_nav-bar-start",
+  ]);
+
+  organizeToolbars({
+    PersonalToolbar: [
+      "fxa-toolbar-menu-button",
+      "new-tab-button",
+      "developer-button",
+    ],
+
+    TabsToolbar: [
+      "stop-reload-button",
+      "tabbrowser-tabs",
+      "personal-bookmarks",
+    ],
+
+    "nav-bar": [
+      "home-button",
+      "forward-button",
+      "downloads-button",
+      "urlbar-container",
+      "back-button",
+      "library-button",
+    ],
+
+    dragSpaceVisible: true,
+    personalToolbarVisible: true,
+  });
+
+  BrowserUsageTelemetry._recordUITelemetry();
+
+  assertVisibilityScalars([
+    "drag-space_pinned_on",
+    "menu-toolbar_pinned_off",
+    "titlebar_pinned_off",
+    "bookmarks-bar_pinned_on",
+
+    "tabbrowser-tabs_pinned_tabs-bar",
+    "stop-reload-button_pinned_tabs-bar",
+    "personal-bookmarks_pinned_tabs-bar",
+    "alltabs-button_pinned_tabs-bar",
+
+    "home-button_pinned_nav-bar-start",
+    "forward-button_pinned_nav-bar-start",
+    "downloads-button_pinned_nav-bar-start",
+    "back-button_pinned_nav-bar-end",
+    "library-button_pinned_nav-bar-end",
+
+    "fxa-toolbar-menu-button_pinned_bookmarks-bar",
+    "new-tab-button_pinned_bookmarks-bar",
+    "developer-button_pinned_bookmarks-bar",
+  ]);
+
+  CustomizableUI.reset();
+});
+
+add_task(async function customizeMode() {
+  // Create a default state.
+  organizeToolbars({
+    PersonalToolbar: ["personal-bookmarks"],
+
+    TabsToolbar: ["tabbrowser-tabs", "new-tab-button"],
+
+    "nav-bar": [
+      "back-button",
+      "forward-button",
+      "stop-reload-button",
+      "urlbar-container",
+      "home-button",
+      "library-button",
+    ],
+  });
+
+  BrowserUsageTelemetry._recordUITelemetry();
+
+  assertVisibilityScalars([
+    "drag-space_pinned_off",
+    "menu-toolbar_pinned_off",
+    "titlebar_pinned_off",
+    "bookmarks-bar_pinned_off",
+
+    "tabbrowser-tabs_pinned_tabs-bar",
+    "new-tab-button_pinned_tabs-bar",
+    "alltabs-button_pinned_tabs-bar",
+
+    "back-button_pinned_nav-bar-start",
+    "forward-button_pinned_nav-bar-start",
+    "stop-reload-button_pinned_nav-bar-start",
+    "home-button_pinned_nav-bar-end",
+    "library-button_pinned_nav-bar-end",
+
+    "personal-bookmarks_pinned_bookmarks-bar",
+  ]);
+
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+
+  await enterCustomizationMode(win);
+
+  let toolbarButton = win.document.getElementById(
+    "customization-toolbar-visibility-button"
+  );
+  let toolbarPopup = win.document.getElementById("customization-toolbar-menu");
+  let popupShown = BrowserTestUtils.waitForEvent(toolbarPopup, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(toolbarButton, {}, win);
+  await popupShown;
+
+  let popupHidden = BrowserTestUtils.waitForEvent(toolbarPopup, "popuphidden");
+  let barButton = win.document.getElementById("toggle_PersonalToolbar");
+  EventUtils.synthesizeMouseAtCenter(barButton, {}, win);
+  await popupHidden;
+
+  let navbar = CustomizableUI.getCustomizationTarget(
+    win.document.getElementById("nav-bar")
+  );
+  let bookmarksBar = CustomizableUI.getCustomizationTarget(
+    win.document.getElementById("PersonalToolbar")
+  );
+  let tabBar = CustomizableUI.getCustomizationTarget(
+    win.document.getElementById("TabsToolbar")
+  );
+
+  simulateItemDrag(win.document.getElementById("home-button"), navbar, "start");
+  simulateItemDrag(win.document.getElementById("library-button"), bookmarksBar);
+  simulateItemDrag(win.document.getElementById("stop-reload-button"), tabBar);
+  simulateItemDrag(
+    win.document.getElementById("stop-reload-button"),
+    navbar,
+    "start"
+  );
+  simulateItemDrag(win.document.getElementById("stop-reload-button"), tabBar);
+
+  await leaveCustomizationMode(win);
+
+  await BrowserTestUtils.closeWindow(win);
+
+  assertCustomizeScalars({
+    "home-button_move_nav-bar-end_nav-bar-start_drag": 1,
+    "library-button_move_nav-bar-end_bookmarks-bar_drag": 1,
+    "stop-reload-button_move_nav-bar-start_tabs-bar_drag": 2,
+    "stop-reload-button_move_tabs-bar_nav-bar-start_drag": 1,
+    "bookmarks-bar_move_off_on_customization-toolbar-menu": 1,
+  });
+
+  CustomizableUI.reset();
+});
+
+add_task(async function contextMenus() {
+  // Create a default state.
+  organizeToolbars({
+    PersonalToolbar: ["personal-bookmarks"],
+
+    TabsToolbar: ["tabbrowser-tabs", "new-tab-button"],
+
+    "nav-bar": [
+      "back-button",
+      "forward-button",
+      "stop-reload-button",
+      "urlbar-container",
+      "home-button",
+      "library-button",
+    ],
+  });
+
+  BrowserUsageTelemetry._recordUITelemetry();
+
+  assertVisibilityScalars([
+    "drag-space_pinned_off",
+    "menu-toolbar_pinned_off",
+    "titlebar_pinned_off",
+    "bookmarks-bar_pinned_off",
+
+    "tabbrowser-tabs_pinned_tabs-bar",
+    "new-tab-button_pinned_tabs-bar",
+    "alltabs-button_pinned_tabs-bar",
+
+    "back-button_pinned_nav-bar-start",
+    "forward-button_pinned_nav-bar-start",
+    "stop-reload-button_pinned_nav-bar-start",
+    "home-button_pinned_nav-bar-end",
+    "library-button_pinned_nav-bar-end",
+
+    "personal-bookmarks_pinned_bookmarks-bar",
+  ]);
+
+  let menu = document.getElementById("toolbar-context-menu");
+  let popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+  let button = document.getElementById("stop-reload-button");
+  EventUtils.synthesizeMouseAtCenter(
+    button,
+    { type: "contextmenu", button: 2 },
+    window
+  );
+  await popupShown;
+
+  let popupHidden = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+  let barButton = document.getElementById("toggle_PersonalToolbar");
+  EventUtils.synthesizeMouseAtCenter(barButton, {}, window);
+  await popupHidden;
+
+  popupShown = BrowserTestUtils.waitForEvent(menu, "popupshown");
+  EventUtils.synthesizeMouseAtCenter(
+    button,
+    { type: "contextmenu", button: 2 },
+    window
+  );
+  await popupShown;
+
+  popupHidden = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+  let removeButton = document.querySelector(
+    "#toolbar-context-menu .customize-context-removeFromToolbar"
+  );
+  EventUtils.synthesizeMouseAtCenter(removeButton, {}, window);
+  await popupHidden;
+
+  assertCustomizeScalars({
+    "bookmarks-bar_move_off_on_toolbar-context-menu": 1,
+    "stop-reload-button_remove_nav-bar-start_na_toolbar-context-menu": 1,
+  });
+
+  CustomizableUI.reset();
+});
+
+add_task(async function pageActions() {
+  // The page action button is only visible when a page is loaded.
+  await BrowserTestUtils.withNewTab("http://example.com", async () => {
+    // Create a default state.
+    organizeToolbars({
+      PersonalToolbar: ["personal-bookmarks"],
+
+      TabsToolbar: ["tabbrowser-tabs", "new-tab-button"],
+
+      "nav-bar": [
+        "back-button",
+        "forward-button",
+        "stop-reload-button",
+        "urlbar-container",
+        "home-button",
+        "library-button",
+      ],
+
+      pageActionsInUrlBar: ["emailLink", "pinTab"],
+    });
+
+    BrowserUsageTelemetry._recordUITelemetry();
+
+    assertVisibilityScalars([
+      "drag-space_pinned_off",
+      "menu-toolbar_pinned_off",
+      "titlebar_pinned_off",
+      "bookmarks-bar_pinned_off",
+
+      "tabbrowser-tabs_pinned_tabs-bar",
+      "new-tab-button_pinned_tabs-bar",
+      "alltabs-button_pinned_tabs-bar",
+
+      "back-button_pinned_nav-bar-start",
+      "forward-button_pinned_nav-bar-start",
+      "stop-reload-button_pinned_nav-bar-start",
+      "home-button_pinned_nav-bar-end",
+      "library-button_pinned_nav-bar-end",
+
+      "emailLink_pinned_pageaction-urlbar",
+      "pinTab_pinned_pageaction-urlbar",
+
+      "personal-bookmarks_pinned_bookmarks-bar",
+    ]);
+
+    let panel = document.getElementById("pageActionPanel");
+    let button = document.getElementById("pageActionButton");
+    let context = document.getElementById("pageActionContextMenu");
+
+    let popupShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(button, {}, window);
+    await popupShown;
+
+    popupShown = BrowserTestUtils.waitForEvent(context, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(
+      document.getElementById("pageAction-panel-copyURL"),
+      { type: "contextmenu", button: 2 },
+      window
+    );
+    await popupShown;
+
+    let popupHidden = BrowserTestUtils.waitForEvent(context, "popuphidden");
+    EventUtils.synthesizeMouseAtCenter(
+      document.querySelector(".pageActionContextMenuItem.builtInUnpinned"),
+      {},
+      window
+    );
+    await popupHidden;
+
+    popupHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+    EventUtils.synthesizeMouseAtCenter(button, {}, window);
+    await popupHidden;
+
+    popupShown = BrowserTestUtils.waitForEvent(context, "popupshown");
+    EventUtils.synthesizeMouseAtCenter(
+      document.getElementById("pageAction-urlbar-emailLink"),
+      { type: "contextmenu", button: 2 },
+      window
+    );
+    await popupShown;
+
+    popupHidden = BrowserTestUtils.waitForEvent(context, "popuphidden");
+    EventUtils.synthesizeMouseAtCenter(
+      document.querySelector(".pageActionContextMenuItem.builtInPinned"),
+      {},
+      window
+    );
+    await popupHidden;
+
+    assertCustomizeScalars({
+      "copyURL_add_na_pageaction-urlbar_pageaction-context": 1,
+      "emailLink_remove_pageaction-urlbar_na_pageaction-context": 1,
+    });
+
+    CustomizableUI.reset();
+  });
+});
+
+add_task(async function extensions() {
+  // The page action button is only visible when a page is loaded.
+  await BrowserTestUtils.withNewTab("http://example.com", async () => {
+    organizeToolbars();
+
+    const extension = ExtensionTestUtils.loadExtension({
+      useAddonManager: "temporary",
+      manifest: {
+        version: "1",
+        applications: {
+          gecko: { id: "random_addon@example.com" },
+        },
+        browser_action: {
+          default_icon: "default.png",
+          default_title: "Hello",
+        },
+        page_action: {
+          default_icon: "default.png",
+          default_title: "Hello",
+        },
+      },
+    });
+
+    await extension.startup();
+
+    assertCustomizeScalars({
+      "random-addon-example-com_add_na_nav-bar-end_addon": 1,
+      "random-addon-example-com_add_na_pageaction-urlbar_addon": 1,
+    });
+
+    BrowserUsageTelemetry._recordUITelemetry();
+
+    assertVisibilityScalars([
+      "drag-space_pinned_off",
+      "menu-toolbar_pinned_off",
+      "titlebar_pinned_off",
+      "bookmarks-bar_pinned_off",
+
+      "tabbrowser-tabs_pinned_tabs-bar",
+      "alltabs-button_pinned_tabs-bar",
+
+      "forward-button_pinned_nav-bar-start",
+      "back-button_pinned_nav-bar-start",
+
+      "random-addon-example-com_pinned_nav-bar-end",
+
+      "random-addon-example-com_pinned_pageaction-urlbar",
+    ]);
+
+    let addon = await AddonManager.getAddonByID(extension.id);
+    await addon.disable();
+
+    assertCustomizeScalars({
+      "random-addon-example-com_remove_nav-bar-end_na_addon": 1,
+      "random-addon-example-com_remove_pageaction-urlbar_na_addon": 1,
+    });
+
+    BrowserUsageTelemetry._recordUITelemetry();
+
+    assertVisibilityScalars([
+      "drag-space_pinned_off",
+      "menu-toolbar_pinned_off",
+      "titlebar_pinned_off",
+      "bookmarks-bar_pinned_off",
+
+      "tabbrowser-tabs_pinned_tabs-bar",
+      "alltabs-button_pinned_tabs-bar",
+
+      "forward-button_pinned_nav-bar-start",
+      "back-button_pinned_nav-bar-start",
+    ]);
+
+    await addon.enable();
+
+    assertCustomizeScalars({
+      "random-addon-example-com_add_na_nav-bar-end_addon": 1,
+      "random-addon-example-com_add_na_pageaction-urlbar_addon": 1,
+    });
+
+    BrowserUsageTelemetry._recordUITelemetry();
+
+    assertVisibilityScalars([
+      "drag-space_pinned_off",
+      "menu-toolbar_pinned_off",
+      "titlebar_pinned_off",
+      "bookmarks-bar_pinned_off",
+
+      "tabbrowser-tabs_pinned_tabs-bar",
+      "alltabs-button_pinned_tabs-bar",
+
+      "forward-button_pinned_nav-bar-start",
+      "back-button_pinned_nav-bar-start",
+
+      "random-addon-example-com_pinned_nav-bar-end",
+
+      "random-addon-example-com_pinned_pageaction-urlbar",
+    ]);
+
+    await addon.reload();
+
+    assertCustomizeScalars({});
+
+    await enterCustomizationMode();
+
+    let navbar = CustomizableUI.getCustomizationTarget(
+      document.getElementById("nav-bar")
+    );
+
+    simulateItemDrag(
+      document.getElementById("random_addon_example_com-browser-action"),
+      navbar,
+      "start"
+    );
+
+    await leaveCustomizationMode();
+
+    assertCustomizeScalars({
+      "random-addon-example-com_move_nav-bar-end_nav-bar-start_drag": 1,
+    });
+
+    await extension.unload();
+
+    assertCustomizeScalars({
+      "random-addon-example-com_remove_nav-bar-start_na_addon": 1,
+      "random-addon-example-com_remove_pageaction-urlbar_na_addon": 1,
+    });
+
+    BrowserUsageTelemetry._recordUITelemetry();
+
+    assertVisibilityScalars([
+      "drag-space_pinned_off",
+      "menu-toolbar_pinned_off",
+      "titlebar_pinned_off",
+      "bookmarks-bar_pinned_off",
+
+      "tabbrowser-tabs_pinned_tabs-bar",
+      "alltabs-button_pinned_tabs-bar",
+
+      "forward-button_pinned_nav-bar-start",
+      "back-button_pinned_nav-bar-start",
+    ]);
+  });
+});
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -2067,16 +2067,61 @@ var BrowserTestUtils = {
       ).then(event => {
         // The originalTarget of the AlertActive on a notificationbox
         // will be the notification itself.
         resolve(event.originalTarget);
       });
     });
   },
 
+  /**
+   * Waits for CSS transitions to complete for an element. Tracks any
+   * transitions that start after this function is called and resolves once all
+   * started transitions complete.
+   *
+   * @param element (Element)
+   *        The element that will transition.
+   * @param timeout (number)
+   *        The maximum time to wait in milliseconds. Defaults to 5 seconds.
+   * @return Promise
+   *        Resolves when transitions complete or rejects if the timeout is hit.
+   */
+  waitForTransition(element, timeout = 5000) {
+    return new Promise((resolve, reject) => {
+      let cleanup = () => {
+        element.removeEventListener("transitionrun", listener);
+        element.removeEventListener("transitionend", listener);
+      };
+
+      let timer = element.ownerGlobal.setTimeout(() => {
+        cleanup();
+        reject();
+      }, timeout);
+
+      let transitionCount = 0;
+
+      let listener = event => {
+        if (event.type == "transitionrun") {
+          transitionCount++;
+        } else {
+          transitionCount--;
+          if (transitionCount == 0) {
+            cleanup();
+            element.ownerGlobal.clearTimeout(timer);
+            resolve();
+          }
+        }
+      };
+
+      element.addEventListener("transitionrun", listener);
+      element.addEventListener("transitionend", listener);
+      element.addEventListener("transitioncancel", listener);
+    });
+  },
+
   _knownAboutPages: new Set(),
   _loadedAboutContentScript: false,
   /**
    * Registers an about: page with particular flags in both the parent
    * and any content processes. Returns a promise that resolves when
    * registration is complete.
    *
    * @param registerCleanupFunction (Function)
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -579,16 +579,399 @@ browser.input:
       - ktaeleman@mozilla.com
     release_channel_collection: opt-out
     products:
       - 'firefox'
     record_in_processes:
       - 'main'
       - 'content'
 
+browser.ui:
+  toolbar_widgets:
+    bug_numbers:
+      - 1620358
+    description: >
+      The widgets in the toolbars.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: boolean
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  customized_widgets:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records when widgets are added, removed or moved in the UI.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+browser.ui.interaction:
+  keyboard:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with keyboard shortcuts.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  menu_bar:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the menu bar.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  tabs_bar:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the tab bar.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  nav_bar:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the nav bar.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  bookmarks_bar:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the bookmarks bar.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  app_menu:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the app menu.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  tabs_context:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the tab context menu.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  content_context:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the content context menu.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  overflow_menu:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the overflow menu.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  pinned_overflow_menu:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with items in the pinned area of the
+      overflow menu.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  pageaction_urlbar:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with page action items in the url bar.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  pageaction_panel:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records a count of interactions with page action items in the panel.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_paneHome:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the Home section of preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_paneGeneral:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the General section of preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_panePrivacy:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the Privacy section of preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_paneSearch:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the Search section of preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_paneSearchResults:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the Search results section of
+      preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_paneSync:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the Sync section of preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
+  preferences_paneContainers:
+    bug_numbers:
+      - 1620358
+    description: >
+      Records the items interacted with in the Containers section of
+      preferences.
+      See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/data/ecosystem-telemetry.html
+    expires: never
+    kind: uint
+    keyed: true
+    notification_emails:
+      - shong@mozilla.com
+      - dtownsend@mozilla.com
+    release_channel_collection: opt-out
+    products:
+      - 'firefox'
+    record_in_processes:
+      - 'main'
+
 gecko:
   version:
     bug_numbers:
       - 1611240
     description: >
       The version of the Gecko engine, example: '74.0a1'.
       It consists of the major and minor version, followed by the release life cycle phase.
       'a' stands for alpha or nightly, 'b' stands for beta.