Bug 1621022 - Change the workflow of enabling the profiler menu button; r=canaltinova
authorGreg Tatum <gtatum@mozilla.com>
Wed, 25 Mar 2020 15:34:59 +0000
changeset 520400 c2027a48130e2da4b8fd88477d135931883aba1a
parent 520399 081cfce87dbe4ee41e6b56f925a292e5640e7e57
child 520401 4695dbc761d726d1465b453df60482f77aa6b710
push id37249
push userdvarga@mozilla.com
push dateWed, 25 Mar 2020 21:39:06 +0000
treeherdermozilla-central@b3c3f7d0f044 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscanaltinova
bugs1621022
milestone76.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1621022 - Change the workflow of enabling the profiler menu button; r=canaltinova This patch makes it so that the profiler shortcuts work based on the location of the profiler menu button. It changes it so that if the menu button is in the navbar or other menus, the shortcuts will work. Otherwise, the shortcuts will be a no-op. This removes the Tools -> Web Developer - Enable Profiler Menu Button option. By default on Nightly and Dev Edition the profiler menu button will be available. On other channels, users must visit profiler.firefox.com. Differential Revision: https://phabricator.services.mozilla.com/D66785
devtools/client/framework/devtools-browser.js
devtools/client/locales/en-US/menus.properties
devtools/client/menus.js
devtools/client/performance-new/@types/perf.d.ts
devtools/client/performance-new/README.md
devtools/client/performance-new/popup/background.jsm.js
devtools/client/performance-new/popup/menu-button.jsm.js
devtools/client/performance-new/test/browser/head.js
devtools/startup/DevToolsStartup.jsm
modules/libpref/init/all.js
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -52,22 +52,16 @@ loader.lazyRequireGetter(
   "devtools/client/shared/stylesheet-utils",
   true
 );
 loader.lazyRequireGetter(
   this,
   "ResponsiveUIManager",
   "devtools/client/responsive/manager"
 );
-loader.lazyRequireGetter(
-  this,
-  "AppConstants",
-  "resource://gre/modules/AppConstants.jsm",
-  true
-);
 loader.lazyImporter(
   this,
   "BrowserToolboxLauncher",
   "resource://devtools/client/framework/browser-toolbox/Launcher.jsm"
 );
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper(
@@ -139,39 +133,16 @@ var gDevToolsBrowser = (exports.gDevTool
       "devtools.debugger.remote-enabled"
     );
     const remoteEnabled = chromeEnabled && devtoolsRemoteEnabled;
     toggleMenuItem("menu_browserToolbox", remoteEnabled);
     toggleMenuItem(
       "menu_browserContentToolbox",
       remoteEnabled && win.gMultiProcessBrowser
     );
-
-    // The profiler's popup is experimental. The plan is to eventually turn it on
-    // everywhere, but while it's under active development we don't want everyone
-    // having it enabled. For now the default pref is to turn it on with Nightly,
-    // with the option to flip the pref in other releases. This feature flag will
-    // go away once it is fully shipped.
-    const isPopupFeatureFlagEnabled = Services.prefs.getBoolPref(
-      "devtools.performance.popup.feature-flag",
-      AppConstants.NIGHTLY_BUILD
-    );
-    // If the feature flag is disabled, hide the menu item.
-    toggleMenuItem("menu_toggleProfilerButtonMenu", isPopupFeatureFlagEnabled);
-
-    if (isPopupFeatureFlagEnabled) {
-      // Did the user enable the profiler button in the menu? If it is then update the
-      // initial UI to show the menu item as checked.
-      if (
-        Services.prefs.getBoolPref("devtools.performance.popup.enabled", false)
-      ) {
-        const cmd = doc.getElementById("menu_toggleProfilerButtonMenu");
-        cmd.setAttribute("checked", "true");
-      }
-    }
   },
 
   /**
    * This function makes sure that the "devtoolstheme" attribute is set on the browser
    * window to make it possible to change colors on elements in the browser (like the
    * splitter between the toolbox and web content).
    */
   updateDevtoolsThemeAttribute(win) {
--- a/devtools/client/locales/en-US/menus.properties
+++ b/devtools/client/locales/en-US/menus.properties
@@ -23,18 +23,13 @@ browserToolboxMenu.label = Browser Toolb
 browserToolboxMenu.accesskey = e
 
 # LOCALIZATION NOTE (browserContentToolboxMenu.label): This is the label for the
 # application menu item that opens the browser content toolbox UI in the Tools menu.
 # This toolbox allows to debug the chrome of the content process in multiprocess builds.
 browserContentToolboxMenu.label = Browser Content Toolbox
 browserContentToolboxMenu.accesskey = x
 
-# LOCALIZATION NOTE (toggleProfilerButtonMenu.label): This is the label for the
-# application menu item that toggles the profiler button to record performance profiles.
-toggleProfilerButtonMenu.label = Enable Profiler Toolbar Icon
-toggleProfilerButtonMenu.accesskey = P
-
 devToolboxMenuItem.label = Toggle Tools
 devToolboxMenuItem.accesskey = T
 
 getMoreDevtoolsCmd.label = Get More Tools
 getMoreDevtoolsCmd.accesskey = M
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -51,21 +51,16 @@ loader.lazyRequireGetter(
   true
 );
 
 loader.lazyImporter(
   this,
   "BrowserToolboxLauncher",
   "resource://devtools/client/framework/browser-toolbox/Launcher.jsm"
 );
-loader.lazyImporter(
-  this,
-  "ProfilerMenuButton",
-  "resource://devtools/client/performance-new/popup/menu-button.jsm.js"
-);
 loader.lazyRequireGetter(
   this,
   "ResponsiveUIManager",
   "devtools/client/responsive/manager"
 );
 loader.lazyRequireGetter(
   this,
   "PICKER_TYPES",
@@ -119,24 +114,16 @@ exports.menuitems = [
       const {
         BrowserConsoleManager,
       } = require("devtools/client/webconsole/browser-console-manager");
       BrowserConsoleManager.openBrowserConsoleOrFocus();
     },
     keyId: "browserConsole",
   },
   {
-    id: "menu_toggleProfilerButtonMenu",
-    l10nKey: "toggleProfilerButtonMenu",
-    checkbox: true,
-    oncommand(event) {
-      ProfilerMenuButton.toggle(event.target.ownerDocument);
-    },
-  },
-  {
     id: "menu_responsiveUI",
     l10nKey: "responsiveDesignMode",
     oncommand(event) {
       const window = event.target.ownerDocument.defaultView;
       ResponsiveUIManager.toggle(window, window.gBrowser.selectedTab, {
         trigger: "menu",
       });
     },
--- a/devtools/client/performance-new/@types/perf.d.ts
+++ b/devtools/client/performance-new/@types/perf.d.ts
@@ -372,31 +372,24 @@ export interface PerformancePref {
    */
   UIBaseUrl: "devtools.performance.recording.ui-base-url";
   /**
    * This pref allows tests to override the /from-addon in order to more easily
    * test the profile injection mechanism.
    */
   UIBaseUrlPathPref: "devtools.performance.recording.ui-base-url-path";
   /**
-   * The profiler's menu button and its popup can be enabled/disabled by the user.
-   * This pref controls whether the user has turned it on or not. Note that this
-   * preference is also used outside of the type-checked files, so make sure
-   * and update it elsewhere.
-   */
-  PopupEnabled: "devtools.performance.popup.enabled";
-  /**
    * The profiler popup has some introductory text explaining what it is the first
    * time that you open it. After that, it is not displayed by default.
    */
   PopupIntroDisplayed: "devtools.performance.popup.intro-displayed";
   /**
    * This preference is used outside of the performance-new type system
-   * (in DevToolsStartup). It toggles the availability of the menu item
-   * "Tools -> Web Developer -> Enable Profiler Toolbar Icon".
+   * (in DevToolsStartup). It toggles the availability of the profiler menu
+   * button in the customization palette.
    */
   PopupFeatureFlag: "devtools.performance.popup.feature-flag";
 }
 
 /**
  * This interface represents the global values that are potentially on the window
  * object in the popup. Coerce the "window" object into this interface.
  */
--- a/devtools/client/performance-new/README.md
+++ b/devtools/client/performance-new/README.md
@@ -27,17 +27,17 @@ This page is initialized with the `PerfA
 This view uses React/Redux for the UI, and is a full page for configuring the profiler. There are no controls for recording a profile, only editing the settings. It shares the same Redux store code as DevTools (instantiated separately), but uses different React components.
 
 ### about:profiling Remote
 
 This is the remote view of the about:profiling page. It is embedded in the about:debugging profiler modal dialog, and it is initialized by about:debugging. It uses preferences that are postfixed with ".remote", so that a second set of preferences are shared for how remote profiling is configured.
 
 ### Profiler Popup
 
-The popup can be enabled by going to `Tools -> Web Developer -> Enable Profiler Toolbar Icon", or by visiting [profiler.firefox.com] and clicking `Enable Profiler Menu Button`. This UI is not a React Redux app, but has a vanilla browser chrome implementation. This was done to make the popup as fast as possible, with a trade-off of some complexity with dealing with the non-standard (i.e. not a normal webpage) browser chrome environment. The popup is designed to be as low overhead as possible in order to get the cleanest performance profiles. Special care must be taken to not impact browser startup times when working with this implementation, as it also turns on the global profiler shortcuts.
+The popup is enabled by default on Nightly and Dev Edition, but it's not added to the navbar. Once the profiler menu button is added to the navbar, or other places in the UI, the shortcuts for the profiler will work. In any release channel the popup can be enabled by visiting [profiler.firefox.com] and clicking `Enable Profiler Menu Button`. This flips the pref `"devtools.performance.popup.feature-flag"` and the profiler button will always be available in the list of buttons for the Firefox UI.
 
-At the time of this writing, the popup feature is not enabled by default. This pref is `"devtools.performance.popup.feature-flag"`.
+The popup UI is not a React Redux app, but has a vanilla browser chrome implementation. This was done to make the popup as fast as possible, with a trade-off of some complexity with dealing with the non-standard (i.e. not a normal webpage) browser chrome environment. The popup is designed to be as low overhead as possible in order to get the cleanest performance profiles. Special care must be taken to not impact browser startup times when working with this implementation, as it also turns on the global profiler shortcuts.
 
 ## Injecting profiles into [profiler.firefox.com]
 
 After a profile has been collected, it needs to be sent to [profiler.firefox.com] for analysis. This is done by using browser APIs to open a new tab, and then injecting the profile into the page through a frame script. See `frame-script.js` for implementation details. Both the DevTools Panel and the Popup use this frame script.
 
 [profiler.firefox.com]: https://profiler.firefox.com
--- a/devtools/client/performance-new/popup/background.jsm.js
+++ b/devtools/client/performance-new/popup/background.jsm.js
@@ -122,16 +122,21 @@ const lazyUtils = requireLazy(() => {
 
 const lazyProfilerMenuButton = requireLazy(() =>
   /** @type {import("devtools/client/performance-new/popup/menu-button.jsm.js")} */
   (ChromeUtils.import(
     "resource://devtools/client/performance-new/popup/menu-button.jsm.js"
   ))
 );
 
+const lazyCustomizableUI = requireLazy(() =>
+  /** @type {import("resource:///modules/CustomizableUI.jsm")} */
+  (ChromeUtils.import("resource:///modules/CustomizableUI.jsm"))
+);
+
 /** @type {Presets} */
 const presets = {
   "web-developer": {
     label: "Web Developer",
     description:
       "Recommended preset for most web app debugging, with low overhead.",
     entries: 10000000,
     interval: 1,
@@ -508,40 +513,45 @@ function handleWebChannelMessage(channel
   switch (messageFromFrontend.type) {
     case "STATUS_QUERY": {
       // The content page wants to know if this channel exists. It does, so respond
       // back to the ping.
       const { ProfilerMenuButton } = lazyProfilerMenuButton();
       channel.send(
         {
           type: "STATUS_RESPONSE",
-          menuButtonIsEnabled: ProfilerMenuButton.isEnabled(),
+          menuButtonIsEnabled: ProfilerMenuButton.isInNavbar(),
           requestId,
         },
         target
       );
       break;
     }
     case "ENABLE_MENU_BUTTON": {
+      const { ownerDocument } = target.browser;
+      if (!ownerDocument) {
+        throw new Error(
+          "Could not find the owner document for the current browser while enabling " +
+            "the profiler menu button"
+        );
+      }
+      // Ensure the widget is enabled.
+      Services.prefs.setBoolPref(POPUP_FEATURE_FLAG_PREF, true);
+
       // Enable the profiler menu button.
       const { ProfilerMenuButton } = lazyProfilerMenuButton();
-      if (!ProfilerMenuButton.isEnabled()) {
-        const { ownerDocument } = target.browser;
-        if (!ownerDocument) {
-          throw new Error(
-            "Could not find the owner document for the current browser while enabling " +
-              "the profiler menu button"
-          );
-        }
-        // The menu button toggle is only enabled on Nightly by default. Once the profiler
-        // is turned on once, make sure that the menu button is also available.
-        Services.prefs.setBoolPref(POPUP_FEATURE_FLAG_PREF, true);
+      ProfilerMenuButton.addToNavbar(ownerDocument);
 
-        ProfilerMenuButton.toggle(ownerDocument);
-      }
+      // Dispatch the change event manually, so that the shortcuts will also be
+      // added.
+      const { CustomizableUI } = lazyCustomizableUI();
+      CustomizableUI.dispatchToolboxEvent("customizationchange");
+
+      // Open the popup with a message.
+      ProfilerMenuButton.openPopup(ownerDocument);
 
       // Respond back that we've done it.
       channel.send(
         {
           type: "ENABLE_MENU_BUTTON_DONE",
           requestId,
         },
         target
--- a/devtools/client/performance-new/popup/menu-button.jsm.js
+++ b/devtools/client/performance-new/popup/menu-button.jsm.js
@@ -50,115 +50,119 @@ const lazyCustomizableWidgets = requireL
 );
 const lazyPopupPanel = requireLazy(() =>
   /** @type {import("devtools/client/performance-new/popup/panel.jsm.js")} */
   (ChromeUtils.import(
     "resource://devtools/client/performance-new/popup/panel.jsm.js"
   ))
 );
 
-/** @type {PerformancePref["PopupEnabled"]} */
-const BUTTON_ENABLED_PREF = "devtools.performance.popup.enabled";
 const WIDGET_ID = "profiler-button";
 
 /**
- * @return {boolean}
- */
-function isEnabled() {
-  const { Services } = lazyServices();
-  return Services.prefs.getBoolPref(BUTTON_ENABLED_PREF, false);
-}
-
-/**
- * @param {HTMLDocument} document
- * @param {boolean} isChecked
+ * Add the profiler button to the navbar.
+ *
+ * @param {ChromeDocument} document  The browser's document.
  * @return {void}
  */
-function setMenuItemChecked(document, isChecked) {
-  const menuItem = document.querySelector("#menu_toggleProfilerButtonMenu");
-  if (!menuItem) {
-    return;
-  }
-  menuItem.setAttribute("checked", isChecked.toString());
+function addToNavbar(document) {
+  const { CustomizableUI } = lazyCustomizableUI();
+
+  CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR);
 }
 
 /**
- * Toggle the menu button, and initialize the widget if needed.
+ * Remove the widget and place it in the customization palette. This will also
+ * disable the shortcuts.
  *
- * @param {ChromeDocument} document - The browser's document.
  * @return {void}
  */
-function toggle(document) {
+function remove() {
   const { CustomizableUI } = lazyCustomizableUI();
-  const { Services } = lazyServices();
-
-  const toggledValue = !isEnabled();
-  Services.prefs.setBoolPref(BUTTON_ENABLED_PREF, toggledValue);
-
-  if (toggledValue) {
-    initialize();
-    CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR);
-  } else {
-    setMenuItemChecked(document, false);
-    CustomizableUI.destroyWidget(WIDGET_ID);
-
-    // The widgets are not being properly destroyed. This is a workaround
-    // until Bug 1552565 lands.
-    const element = document.getElementById("PanelUI-profiler");
-    delete (/** @type {any} */ (element._addedEventListeners));
-  }
+  CustomizableUI.removeWidgetFromArea(WIDGET_ID);
 }
 
 /**
- * This function takes the button element, and returns a function that's used to
- * update the profiler button whenever the profiler activation status changed.
+ * See if the profiler menu button is in the navbar, or other active areas. The
+ * placement is null when it's inactive in the customization palette.
  *
- * @param {HTMLElement} buttonElement
- * @returns {() => void}
+ * @return {boolean}
  */
-function updateButtonColorForElement(buttonElement) {
-  return () => {
-    const { Services } = lazyServices();
-    const isRunning = Services.profiler.IsActive();
+function isInNavbar() {
+  const { CustomizableUI } = lazyCustomizableUI();
+  return Boolean(CustomizableUI.getPlacementOfWidget("profiler-button"));
+}
 
-    // Use photon blue-60 when active.
-    buttonElement.style.fill = isRunning ? "#0060df" : "";
-  };
+/**
+ * Opens the popup for the profiler.
+ * @param {Document} document
+ */
+function openPopup(document) {
+  // First find the button.
+  /** @type {HTMLButtonElement | null} */
+  const button = document.querySelector("#profiler-button");
+  if (!button) {
+    throw new Error("Could not find the profiler button.");
+  }
+  button.click();
 }
 
 /**
  * This function creates the widget definition for the CustomizableUI. It should
  * only be run if the profiler button is enabled.
+ * @param {(isEnabled: boolean) => void} toggleProfilerKeyShortcuts
  * @return {void}
  */
-function initialize() {
+function initialize(toggleProfilerKeyShortcuts) {
   const { CustomizableUI } = lazyCustomizableUI();
   const { CustomizableWidgets } = lazyCustomizableWidgets();
   const { Services } = lazyServices();
 
   const widget = CustomizableUI.getWidget(WIDGET_ID);
   if (widget && widget.provider == CustomizableUI.PROVIDER_API) {
     // This widget has already been created.
     return;
   }
 
-  /** @type {null | (() => void)} */
-  let observer = null;
   const viewId = "PanelUI-profiler";
 
   /**
    * This is mutable state that will be shared between panel displays.
    *
    * @type {import("devtools/client/performance-new/popup/panel.jsm.js").State}
    */
   const panelState = {
     cleanup: [],
     isInfoCollapsed: true,
   };
 
+  /**
+   * Handle when the customization changes for the button. This event is not
+   * very specific, and fires for any CustomizableUI widget. This event is
+   * pretty rare to fire, and only affects users of the profiler button,
+   * so it shouldn't have much overhead even if it runs a lot.
+   */
+  function handleCustomizationChange() {
+    const isEnabled = isInNavbar();
+    toggleProfilerKeyShortcuts(isEnabled);
+
+    if (!isEnabled) {
+      // The profiler menu button is no longer in the navbar, make sure that the
+      // "intro-displayed" preference is reset.
+      /** @type {PerformancePref["PopupIntroDisplayed"]} */
+      const popupIntroDisplayedPref =
+        "devtools.performance.popup.intro-displayed";
+      Services.prefs.setBoolPref(popupIntroDisplayedPref, false);
+
+      if (Services.profiler.IsActive()) {
+        Services.profiler.StopProfiler();
+      }
+    }
+  }
+
   const item = {
     id: WIDGET_ID,
     type: "view",
     viewId,
     tooltiptext: "profiler-button.tooltiptext",
 
     onViewShowing:
       /**
@@ -197,59 +201,88 @@ function initialize() {
     onViewHiding(event) {
       // Clean-up the view. This removes all of the event listeners.
       for (const fn of panelState.cleanup) {
         fn();
       }
       panelState.cleanup = [];
     },
 
-    /** @type {(document: HTMLDocument) => void} */
+    /**
+     * Perform any general initialization for this widget. This is called once per
+     * browser window.
+     *
+     * @type {(document: HTMLDocument) => void}
+     */
     onBeforeCreated: document => {
       /** @type {PerformancePref["PopupIntroDisplayed"]} */
       const popupIntroDisplayedPref =
         "devtools.performance.popup.intro-displayed";
 
       // Determine the state of the popup's info being collapsed BEFORE the view
       // is shown, and update the collapsed state. This way the transition animation
       // isn't run.
       panelState.isInfoCollapsed = Services.prefs.getBoolPref(
         popupIntroDisplayedPref
       );
       if (!panelState.isInfoCollapsed) {
         // We have displayed the intro, don't show it again by default.
         Services.prefs.setBoolPref(popupIntroDisplayedPref, true);
       }
 
-      setMenuItemChecked(document, true);
+      // Handle customization event changes. If the profiler is no longer in the
+      // navbar, then reset the popup intro preference.
+      const window = document.defaultView;
+      if (window) {
+        /** @type {any} */ (window).gNavToolbox.addEventListener(
+          "customizationchange",
+          handleCustomizationChange
+        );
+      }
+
+      toggleProfilerKeyShortcuts(isInNavbar());
     },
 
-    /** @type {(document: HTMLElement) => void} */
+    /**
+     * This method is used when we need to operate upon the button element itself.
+     * This is called once per browser window.
+     *
+     * @type {(buttonElement: HTMLElement) => void}
+     */
     onCreated: buttonElement => {
-      observer = updateButtonColorForElement(buttonElement);
-      Services.obs.addObserver(observer, "profiler-started");
-      Services.obs.addObserver(observer, "profiler-stopped");
+      const window = buttonElement.ownerDocument.defaultView;
 
-      // Also run the observer right away to update the color if the profiler is
-      // already running at startup.
-      observer();
-    },
+      function updateButtonColor() {
+        // Use photon blue-60 when active.
+        buttonElement.style.fill = Services.profiler.IsActive()
+          ? "#0060df"
+          : "";
+      }
 
-    onDestroyed: () => {
-      if (observer) {
-        Services.obs.removeObserver(observer, "profiler-started");
-        Services.obs.removeObserver(observer, "profiler-stopped");
-        observer = null;
-      }
+      updateButtonColor();
+
+      Services.obs.addObserver(updateButtonColor, "profiler-started");
+      Services.obs.addObserver(updateButtonColor, "profiler-stopped");
+
+      window.addEventListener("unload", () => {
+        Services.obs.removeObserver(updateButtonColor, "profiler-started");
+        Services.obs.removeObserver(updateButtonColor, "profiler-stopped");
+      });
     },
   };
 
   CustomizableUI.createWidget(item);
   CustomizableWidgets.push(item);
 }
 
-const ProfilerMenuButton = { toggle, initialize, isEnabled };
+const ProfilerMenuButton = {
+  initialize,
+  addToNavbar,
+  isInNavbar,
+  openPopup,
+  remove,
+};
 
 exports.ProfilerMenuButton = ProfilerMenuButton;
 
 // Object.keys() confuses the linting which expects a static array expression.
 // eslint-disable-next-line
 var EXPORTED_SYMBOLS = Object.keys(exports);
--- a/devtools/client/performance-new/test/browser/head.js
+++ b/devtools/client/performance-new/test/browser/head.js
@@ -158,37 +158,37 @@ function getIframeDocument() {
 async function makeSureProfilerPopupIsEnabled() {
   info("Make sure the profiler popup is enabled.");
 
   info("> Load the profiler menu button.");
   const { ProfilerMenuButton } = ChromeUtils.import(
     "resource://devtools/client/performance-new/popup/menu-button.jsm.js"
   );
 
-  if (!ProfilerMenuButton.isEnabled()) {
-    info("> The menu button is not enabled, turn it on.");
-    ProfilerMenuButton.toggle(document);
+  if (!ProfilerMenuButton.isInNavbar()) {
+    info("> The menu button is not in the nav bar, add it.");
+    ProfilerMenuButton.addToNavbar(document);
 
     await waitUntil(
       () => gBrowser.ownerDocument.getElementById("profiler-button"),
       "> Waiting until the profiler button is added to the browser."
     );
 
     await SimpleTest.promiseFocus(gBrowser.ownerGlobal);
 
     registerCleanupFunction(() => {
       info(
         "Clean up after the test by disabling the profiler popup menu button."
       );
-      if (!ProfilerMenuButton.isEnabled()) {
+      if (!ProfilerMenuButton.isInNavbar()) {
         throw new Error(
-          "Expected the profiler popup to still be enabled during the test cleanup."
+          "Expected the profiler popup to still be in the navbar during the test cleanup."
         );
       }
-      ProfilerMenuButton.toggle(document);
+      ProfilerMenuButton.remove();
     });
   } else {
     info("> The menu button was already enabled.");
   }
 }
 
 /**
  * This function toggles the profiler menu button, and then uses user gestures
@@ -540,29 +540,29 @@ async function waitForProfilerMenuButton
 async function makeSureProfilerPopupIsDisabled() {
   info("Make sure the profiler popup is dsiabled.");
 
   info("> Load the profiler menu button module.");
   const { ProfilerMenuButton } = ChromeUtils.import(
     "resource://devtools/client/performance-new/popup/menu-button.jsm.js"
   );
 
-  const originallyIsEnabled = ProfilerMenuButton.isEnabled();
+  const isOriginallyInNavBar = ProfilerMenuButton.isInNavbar();
 
-  if (originallyIsEnabled) {
-    info("> The menu button is enabled, turn it off for this test.");
-    ProfilerMenuButton.toggle(document);
+  if (isOriginallyInNavBar) {
+    info("> The menu button is in the navbar, remove it for this test.");
+    ProfilerMenuButton.remove();
   } else {
-    info("> The menu button was already disabled.");
+    info("> The menu button was not in the navbar yet.");
   }
 
   registerCleanupFunction(() => {
-    info("Revert the profiler menu button to its original enabled state.");
-    if (originallyIsEnabled !== ProfilerMenuButton.isEnabled()) {
-      ProfilerMenuButton.toggle(document);
+    info("Revert the profiler menu button to be back in its original place");
+    if (isOriginallyInNavBar !== ProfilerMenuButton.isInNavbar()) {
+      ProfilerMenuButton.remove();
     }
   });
 }
 
 /**
  * Open the WebChannel test document, that will enable the profiler popup via
  * WebChannel.
  * @param {Function} callback
--- a/devtools/startup/DevToolsStartup.jsm
+++ b/devtools/startup/DevToolsStartup.jsm
@@ -25,17 +25,16 @@
 const kDebuggerPrefs = [
   "devtools.debugger.remote-enabled",
   "devtools.chrome.enabled",
 ];
 
 const DEVTOOLS_ENABLED_PREF = "devtools.enabled";
 
 const DEVTOOLS_POLICY_DISABLED_PREF = "devtools.policy.disabled";
-const PROFILER_POPUP_ENABLED_PREF = "devtools.performance.popup.enabled";
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 ChromeUtils.defineModuleGetter(
   this,
   "ActorManagerParent",
@@ -229,17 +228,17 @@ XPCOMUtils.defineLazyGetter(this, "KeySh
     shortcuts.push({
       id: "inspectorMac",
       toolId: "inspector",
       shortcut: getLocalizedKeyShortcut("inspector.commandkey"),
       modifiers: "accel,shift",
     });
   }
 
-  if (isProfilerButtonEnabled()) {
+  if (ProfilerMenuButton.isInNavbar()) {
     shortcuts.push(...getProfilerKeyShortcuts());
   }
 
   return shortcuts;
 });
 
 function getProfilerKeyShortcuts() {
   return [
@@ -254,24 +253,16 @@ function getProfilerKeyShortcuts() {
       id: "profilerCapture",
       shortcut: getLocalizedKeyShortcut("profilerCapture.commandkey"),
       modifiers: "control,shift",
     },
   ];
 }
 
 /**
- * Instead of loading the ProfilerMenuButton.jsm file, provide an independent check
- * to see if it is turned on.
- */
-function isProfilerButtonEnabled() {
-  return Services.prefs.getBoolPref(PROFILER_POPUP_ENABLED_PREF, false);
-}
-
-/**
  * Validate the URL that will be used for the WebChannel for the profiler.
  *
  * @param {string} targetUrl
  * @returns {string}
  */
 function validateProfilerWebChannelUrl(targetUrl) {
   const frontEndUrl = "https://profiler.firefox.com";
 
@@ -385,23 +376,16 @@ DevToolsStartup.prototype = {
         "browser-delayed-startup-finished"
       );
 
       // Update menu items when devtools.enabled changes.
       Services.prefs.addObserver(
         DEVTOOLS_ENABLED_PREF,
         this.onEnabledPrefChanged
       );
-
-      // Watch for the profiler to enable or disable the profiler popup, then toggle
-      // the keyboard shortcuts on and off.
-      Services.prefs.addObserver(
-        PROFILER_POPUP_ENABLED_PREF,
-        this.toggleProfilerKeyShortcuts
-      );
       /* eslint-enable mozilla/balanced-observers */
 
       if (!this.isDisabledByPolicy()) {
         if (AppConstants.MOZ_DEV_EDITION) {
           // On DevEdition, the developer toggle is displayed by default in the navbar
           // area and should be created before the first paint.
           this.hookDeveloperToggle();
         }
@@ -619,48 +603,46 @@ DevToolsStartup.prototype = {
     };
     CustomizableUI.createWidget(item);
     CustomizableWidgets.push(item);
 
     this.developerToggleCreated = true;
   },
 
   /**
-   * Dynamically register a profiler recording button in the
-   * customization menu. You can use this button by right clicking
-   * on Firefox toolbar and dragging it from the customization panel
-   * to the toolbar. (i.e. this isn't displayed by default to users.)
+   * Register the profiler recording button. This button will be available
+   * in the customization palette for the Firefox toolbar. In addition, it can be
+   * enabled from profiler.firefox.com.
    */
   hookProfilerRecordingButton() {
     if (this.profilerRecordingButtonCreated) {
       return;
     }
-    this.profilerRecordingButtonCreated = true;
-
+    const featureFlagPref = "devtools.performance.popup.feature-flag";
     const isPopupFeatureFlagEnabled = Services.prefs.getBoolPref(
-      "devtools.performance.popup.feature-flag",
-      AppConstants.NIGHTLY_BUILD
+      featureFlagPref
     );
+    this.profilerRecordingButtonCreated = true;
 
     // Listen for messages from the front-end. This needs to happen even if the
     // button isn't enabled yet. This will allow the front-end to turn on the
     // popup for our users, regardless of if the feature is enabled by default.
     this.initializeProfilerWebChannel();
 
-    if (!isPopupFeatureFlagEnabled) {
-      // The profiler's popup is experimental. The plan is to eventually turn it on
-      // everywhere, but while it's under active development we don't want everyone
-      // having it enabled. For now the default pref is to turn it on with Nightly,
-      // with the option to flip the pref in other releases. This feature flag will
-      // go away once it is fully shipped.
-      return;
-    }
-
-    if (isProfilerButtonEnabled()) {
-      ProfilerMenuButton.initialize();
+    if (isPopupFeatureFlagEnabled) {
+      // Initialize the CustomizableUI widget.
+      ProfilerMenuButton.initialize(this.toggleProfilerKeyShortcuts);
+    } else {
+      // The feature flag is not enabled, but watch for it to be enabled. If it is,
+      // initialize everything.
+      const enable = () => {
+        ProfilerMenuButton.initialize(this.toggleProfilerKeyShortcuts);
+        Services.prefs.removeObserver(featureFlagPref, enable);
+      };
+      Services.prefs.addObserver(featureFlagPref, enable);
     }
   },
 
   /**
    * Initialize the WebChannel for profiler.firefox.com. This function happens at
    * startup, so care should be taken to minimize its performance impact. The WebChannel
    * is a mechanism that is used to communicate between the browser, and front-end code.
    */
@@ -827,44 +809,46 @@ DevToolsStartup.prototype = {
         keyElement.remove();
       }
     }
   },
 
   /**
    * We only want to have the keyboard shortcuts active when the menu button is on.
    * This function either adds or removes the elements.
+   * @param {boolean} isEnabled
    */
-  toggleProfilerKeyShortcuts() {
-    const isEnabled = isProfilerButtonEnabled();
+  toggleProfilerKeyShortcuts(isEnabled) {
     const profilerKeyShortcuts = getProfilerKeyShortcuts();
     for (const { document } of Services.wm.getEnumerator(null)) {
       const devtoolsKeyset = document.getElementById("devtoolsKeyset");
       const mainKeyset = document.getElementById("mainKeyset");
 
       if (!devtoolsKeyset || !mainKeyset) {
         // There may not be devtools keyset on this window.
         continue;
       }
 
+      const areProfilerKeysPresent = !!document.getElementById(
+        "key_profilerStartStop"
+      );
+      if (isEnabled === areProfilerKeysPresent) {
+        // Don't double add or double remove the shortcuts.
+        continue;
+      }
       if (isEnabled) {
         this.attachKeys(document, profilerKeyShortcuts);
       } else {
         this.removeKeys(document, profilerKeyShortcuts);
       }
       // Appending a <key> element is not always enough. The <keyset> needs
       // to be detached and reattached to make sure the <key> is taken into
       // account (see bug 832984).
       mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
     }
-
-    if (!isEnabled) {
-      // Ensure the profiler isn't left profiling in the background.
-      ProfilerPopupBackground.stopProfiler();
-    }
   },
 
   async onKey(window, key) {
     try {
       // The profiler doesn't care if DevTools is loaded, so provide a quick check
       // first to bail out of checking if DevTools is available.
       switch (key.id) {
         case "profilerStartStop": {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -847,17 +847,25 @@ pref("toolkit.dump.emit", false);
 // Preferences for the new performance panel. Note that some preferences are duplicated
 // with a ".remote" postfix. This is because we have one set of preference for local
 // profiling, and a second set for remote profiling.
 
 // This pref configures the base URL for the profiler.firefox.com instance to
 // use. This is useful so that a developer can change it while working on
 // profiler.firefox.com, or in tests. This isn't exposed directly to the user.
 pref("devtools.performance.recording.ui-base-url", "https://profiler.firefox.com");
-
+// The popup is only enabled by default on Nightly, Dev Edition, and debug buildsd since
+// it's a developer focused item. It can still be enabled by going to profiler.firefox.com,
+// but by default it is off on Release and Beta. Note that this only adds it to the
+// the customization palette, not to the navbar.
+#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG)
+  pref("devtools.performance.popup.feature-flag", true);
+#else
+  pref("devtools.performance.popup.feature-flag", false);
+#endif
 // The preset to use for the recording settings. If set to "custom" then the pref
 // values below will be used.
 #if defined(NIGHTLY_BUILD) || !defined(MOZILLA_OFFICIAL)
   // Use a more advanced preset on Nightly and local builds.
   pref("devtools.performance.recording.preset", "firefox-platform");
   pref("devtools.performance.recording.preset.remote", "firefox-platform");
 #else
   pref("devtools.performance.recording.preset", "web-developer");