Bug 1444301 - Move split console function to meatball menu; r?jryans draft
authorBrian Birtles <birtles@gmail.com>
Thu, 05 Apr 2018 10:13:22 +0900
changeset 778571 12f8007c566a8a15355523a431589ba218acb659
parent 778570 6ce5cbaf96242fc5318dea0e0143bea426404abf
child 778572 6ac164723c8ebeeb3992bf39ee5a83ec00b678bf
push id105521
push userbbirtles@mozilla.com
push dateFri, 06 Apr 2018 14:07:54 +0000
reviewersjryans
bugs1444301
milestone61.0a1
Bug 1444301 - Move split console function to meatball menu; r?jryans MozReview-Commit-ID: GkMRdZMIUVa
devtools/client/definitions.js
devtools/client/framework/components/toolbox-controller.js
devtools/client/framework/components/toolbox-toolbar.js
devtools/client/framework/toolbox.js
devtools/client/jar.mn
devtools/client/locales/en-US/startup.properties
devtools/client/locales/en-US/toolbox.properties
devtools/client/themes/images/command-console.svg
devtools/client/themes/toolbox.css
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_persist.js
devtools/client/webconsole/test/browser_webconsole_split.js
devtools/client/webconsole/test/browser_webconsole_split_persist.js
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -503,32 +503,16 @@ exports.defaultThemes = [
   Tools.darkTheme,
   Tools.lightTheme,
 ];
 
 // White-list buttons that can be toggled to prevent adding prefs for
 // addons that have manually inserted toolbarbuttons into DOM.
 // (By default, supported target is only local tab)
 exports.ToolboxButtons = [
-  { id: "command-button-splitconsole",
-    description: l10n("toolbox.buttons.splitconsole", "Esc"),
-    isTargetSupported: target => !target.isAddon,
-    onClick(event, toolbox) {
-      toolbox.toggleSplitConsole();
-    },
-    isChecked(toolbox) {
-      return toolbox.splitConsole;
-    },
-    setup(toolbox, onChange) {
-      toolbox.on("split-console", onChange);
-    },
-    teardown(toolbox, onChange) {
-      toolbox.off("split-console", onChange);
-    }
-  },
   { id: "command-button-paintflashing",
     description: l10n("toolbox.buttons.paintflashing"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       CommandUtils.executeOnTarget(toolbox.target, "paintflashing toggle");
     },
     isChecked(toolbox) {
       return CommandState.isEnabledForTarget(toolbox.target, "paintflashing");
--- a/devtools/client/framework/components/toolbox-controller.js
+++ b/devtools/client/framework/components/toolbox-controller.js
@@ -23,31 +23,33 @@ class ToolboxController extends Componen
       focusedButton: ELEMENT_PICKER_ID,
       toolboxButtons: [],
       currentToolId: null,
       highlightedTools: new Set(),
       panelDefinitions: [],
       hostTypes: [],
       areDockOptionsEnabled: true,
       canCloseToolbox: true,
+      isSplitConsoleActive: false,
       canRender: false,
       buttonIds: [],
       checkedButtonsUpdated: () => {
         this.forceUpdate();
       }
     };
 
     this.setFocusedButton = this.setFocusedButton.bind(this);
     this.setToolboxButtons = this.setToolboxButtons.bind(this);
     this.setCurrentToolId = this.setCurrentToolId.bind(this);
     this.highlightTool = this.highlightTool.bind(this);
     this.unhighlightTool = this.unhighlightTool.bind(this);
     this.setHostTypes = this.setHostTypes.bind(this);
     this.setDockOptionsEnabled = this.setDockOptionsEnabled.bind(this);
     this.setCanCloseToolbox = this.setCanCloseToolbox.bind(this);
+    this.setIsSplitConsoleActive = this.setIsSplitConsoleActive.bind(this);
     this.setCanRender = this.setCanRender.bind(this);
     this.setPanelDefinitions = this.setPanelDefinitions.bind(this);
     this.updateButtonIds = this.updateButtonIds.bind(this);
     this.updateFocusedButton = this.updateFocusedButton.bind(this);
   }
 
   shouldComponentUpdate() {
     return this.state.canRender;
@@ -132,16 +134,20 @@ class ToolboxController extends Componen
   setHostTypes(hostTypes) {
     this.setState({ hostTypes });
   }
 
   setCanCloseToolbox(canCloseToolbox) {
     this.setState({ canCloseToolbox }, this.updateButtonIds);
   }
 
+  setIsSplitConsoleActive(isSplitConsoleActive) {
+    this.setState({ isSplitConsoleActive });
+  }
+
   setPanelDefinitions(panelDefinitions) {
     this.setState({ panelDefinitions }, this.updateButtonIds);
   }
 
   get panelDefinitions() {
     return this.state.panelDefinitions;
   }
 
--- a/devtools/client/framework/components/toolbox-toolbar.js
+++ b/devtools/client/framework/components/toolbox-toolbar.js
@@ -40,18 +40,22 @@ class ToolboxToolbar extends Component {
         switchHost: PropTypes.func.isRequired,
       })),
       // Should the docking options be enabled? They are disabled in some
       // contexts such as WebIDE.
       areDockButtonsEnabled: PropTypes.bool,
       // Do we need to add UI for closing the toolbox? We don't when the
       // toolbox is undocked, for example.
       canCloseToolbox: PropTypes.bool,
+      // Is the split console currently visible?
+      isSplitConsoleActive: PropTypes.bool,
       // Function to select a tool based on its id.
       selectTool: PropTypes.func,
+      // Function to turn the split console on / off.
+      toggleSplitConsole: PropTypes.func,
       // Function to completely close the toolbox.
       closeToolbox: PropTypes.func,
       // Keep a record of what button is focused.
       focusButton: PropTypes.func,
       // Hold off displaying the toolbar until enough information is ready for
       // it to render nicely.
       canRender: PropTypes.bool,
       // Localization interface.
@@ -174,18 +178,23 @@ function renderSeparator() {
  * @param {Function} hostTypes[].switchHost
  *        Function to switch the host.
  * @param {boolean} areDockOptionsEnabled
  *        They are not enabled in certain situations like when they are in the
  *        WebIDE.
  * @param {boolean} canCloseToolbox
  *        Do we need to add UI for closing the toolbox? We don't when the
  *        toolbox is undocked, for example.
+ * @param {boolean} isSplitConsoleActive
+ *         Is the split console currently visible?
+ *        toolbox is undocked, for example.
  * @param {Function} selectTool
  *        Function to select a tool based on its id.
+ * @param {Function} toggleSplitConsole
+ *        Function to turn the split console on / off.
  * @param {Function} closeToolbox
  *        Completely close the toolbox.
  * @param {Function} focusButton
  *        Keep a record of the currently focused button.
  * @param {Object} L10N
  *        Localization interface.
  */
 function renderToolboxControls(props) {
@@ -239,46 +248,77 @@ function renderToolboxControls(props) {
 /**
  * Display the "..." menu (affectionately known as the meatball menu).
  *
  * @param {Object} menuButton
  *        The <button> element from which the menu should pop out. The geometry
  *        of this element is used to position the menu.
  * @param {Object} props
  *        Properties as described below.
+ * @param {string} props.currentToolId
+ *        The id of the currently selected tool.
  * @param {Object[]} props.hostTypes
  *        Array of host type objects.
  * @param {string} props.hostTypes[].position
  *        Position name.
  * @param {Function} props.hostTypes[].switchHost
  *        Function to switch the host.
  *        This array will be empty if we shouldn't shouldn't show any dock
  *        options.
+ * @param {boolean} isSplitConsoleActive
+ *        Is the split console currently visible?
  * @param {Function} props.selectTool
  *        Function to select a tool based on its id.
+ * @param {Function} toggleSplitConsole
+ *        Function to turn the split console on / off.
  * @param {Object} props.L10N
  *        Localization interface.
  * @param {Object} props.toolbox
  *        The devtools toolbox. Used by the Menu component to determine which
  *        document to use.
  */
-function showMeatballMenu(menuButton, {hostTypes, selectTool, L10N, toolbox}) {
+function showMeatballMenu(
+  menuButton,
+  {
+    currentToolId,
+    hostTypes,
+    isSplitConsoleActive,
+    selectTool,
+    toggleSplitConsole,
+    L10N,
+    toolbox,
+  }
+) {
   const menu = new Menu({ id: "toolbox-meatball-menu" });
 
   // Dock options
   for (const hostType of hostTypes) {
     menu.append(new MenuItem({
       id: `toolbox-meatball-menu-dock-${hostType.position}`,
       label: L10N.getStr(
         `toolbox.meatballMenu.dock.${hostType.position}.label`
       ),
       click: () => hostType.switchHost(),
     }));
   }
 
+  // Split console
+  if (currentToolId !== "webconsole") {
+    menu.append(new MenuItem({
+      id: "toolbox-meatball-menu-splitconsole",
+      label: L10N.getStr(
+        `toolbox.meatballMenu.${
+          isSplitConsoleActive ? "hideconsole" : "splitconsole"
+        }.label`
+      ),
+      accelerator: "Esc",
+      click: toggleSplitConsole,
+    }));
+  }
+
   if (menu.items.length) {
     menu.append(new MenuItem({ type: "separator" }));
   }
 
   // Settings
   menu.append(new MenuItem({
     id: "toolbox-meatball-menu-settings",
     label: L10N.getStr("toolbox.meatballMenu.settings.label"),
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -147,16 +147,17 @@ function Toolbox(target, selectedTool, h
   this._onPickerClick = this._onPickerClick.bind(this);
   this._onPickerKeypress = this._onPickerKeypress.bind(this);
   this._onPickerStarted = this._onPickerStarted.bind(this);
   this._onPickerStopped = this._onPickerStopped.bind(this);
   this._onInspectObject = this._onInspectObject.bind(this);
   this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this);
   this._updatePickerButton = this._updatePickerButton.bind(this);
   this.selectTool = this.selectTool.bind(this);
+  this.toggleSplitConsole = this.toggleSplitConsole.bind(this);
 
   this._target.on("close", this.destroy);
 
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   this._defaultToolId = selectedTool;
 
@@ -1106,16 +1107,17 @@ Toolbox.prototype = {
   },
 
   _mountReactComponent: function() {
     // Ensure the toolbar doesn't try to render until the tool is ready.
     const element = this.React.createElement(this.ToolboxController, {
       L10N,
       currentToolId: this.currentToolId,
       selectTool: this.selectTool,
+      toggleSplitConsole: this.toggleSplitConsole,
       closeToolbox: this.destroy,
       focusButton: this._onToolbarFocus,
       toolbox: this
     });
 
     this.component = this.ReactDOM.render(element, this._componentMount);
   },
 
@@ -1902,31 +1904,33 @@ Toolbox.prototype = {
 
     // Ensure split console is visible if console was already loaded in background
     let iframe = this.webconsolePanel.querySelector(".toolbox-panel-iframe");
     if (iframe) {
       this.setIframeVisible(iframe, true);
     }
 
     return this.loadTool("webconsole").then(() => {
+      this.component.setIsSplitConsoleActive(true);
       this.emit("split-console");
       this.focusConsoleInput();
     });
   },
 
   /**
    * Closes the split console.
    *
    * @returns {Promise} a promise that resolves once the tool has been
    *          closed.
    */
   closeSplitConsole: function() {
     this._splitConsole = false;
     Services.prefs.setBoolPref(SPLITCONSOLE_ENABLED_PREF, false);
     this._refreshConsoleDisplay();
+    this.component.setIsSplitConsoleActive(false);
     this.emit("split-console");
 
     if (this._lastFocusedElement) {
       this._lastFocusedElement.focus();
     }
     return promise.resolve();
   },
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -144,17 +144,16 @@ devtools.jar:
     skin/rules.css (themes/rules.css)
     skin/commandline.css (themes/commandline.css)
     skin/images/command-paintflashing.svg (themes/images/command-paintflashing.svg)
     skin/images/command-screenshot.svg (themes/images/command-screenshot.svg)
     skin/images/command-responsivemode.svg (themes/images/command-responsivemode.svg)
     skin/images/command-pick.svg (themes/images/command-pick.svg)
     skin/images/command-pick-accessibility.svg (themes/images/command-pick-accessibility.svg)
     skin/images/command-frames.svg (themes/images/command-frames.svg)
-    skin/images/command-console.svg (themes/images/command-console.svg)
     skin/images/command-eyedropper.svg (themes/images/command-eyedropper.svg)
     skin/images/command-rulers.svg (themes/images/command-rulers.svg)
     skin/images/command-measure.svg (themes/images/command-measure.svg)
     skin/images/command-noautohide.svg (themes/images/command-noautohide.svg)
     skin/markup.css (themes/markup.css)
     skin/images/editor-error.png (themes/images/editor-error.png)
     skin/images/breakpoint.svg (themes/images/breakpoint.svg)
     skin/webconsole.css (themes/webconsole.css)
--- a/devtools/client/locales/en-US/startup.properties
+++ b/devtools/client/locales/en-US/startup.properties
@@ -257,22 +257,16 @@ accessibility.panelLabel=Accessibility P
 accessibility.accesskey=y
 
 # LOCALIZATION NOTE (accessibility.tooltip2):
 # This string is displayed in the tooltip of the tab when the Accessibility is
 # displayed inside the developer tools window.
 # Keyboard shortcut for Accessibility panel will be shown inside the brackets.
 accessibility.tooltip2=Accessibility
 
-# LOCALIZATION NOTE (toolbox.buttons.splitconsole):
-# This is the tooltip of the button in the toolbox toolbar used to toggle
-# the split console.
-# Keyboard shortcut will be shown inside brackets.
-toolbox.buttons.splitconsole = Toggle split console (%S)
-
 # LOCALIZATION NOTE (toolbox.buttons.responsive):
 # This is the tooltip of the button in the toolbox toolbar that toggles
 # the Responsive mode.
 # Keyboard shortcut will be shown inside brackets.
 toolbox.buttons.responsive = Responsive Design Mode (%S)
 
 # LOCALIZATION NOTE (toolbox.buttons.paintflashing):
 # This is the tooltip of the paintflashing button in the toolbox toolbar
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -158,16 +158,23 @@ toolbox.meatballMenu.button.tooltip=Cust
 
 # LOCALIZATION NOTE (toolbox.meatballMenu.dock.*.label): These labels are shown
 # in the "..." menu in the toolbox and represent the different arrangements for
 # docking (or undocking) the developer tools toolbox.
 toolbox.meatballMenu.dock.bottom.label=Dock to bottom
 toolbox.meatballMenu.dock.side.label=Dock to side
 toolbox.meatballMenu.dock.window.label=Undock
 
+# LOCALIZATION NOTE (toolbox.meatballMenu.{splitconsole,hideconsole}.label):
+# These are the labels in the "..." menu in the toolbox for toggling the split
+# console window.
+# The keyboard shortcut will be shown to the side of the label.
+toolbox.meatballMenu.splitconsole.label=Show split console
+toolbox.meatballMenu.hideconsole.label=Hide split console
+
 # LOCALIZATION NOTE (toolbox.meatballMenu.settings.label): This is the label for
 # the item in the "..." menu in the toolbox that brings up the Settings
 # (Options) panel.
 # The keyboard shortcut will be shown to the side of the label.
 toolbox.meatballMenu.settings.label=Settings
 
 # LOCALIZATION NOTE (toolbox.closebutton.tooltip): This is the tooltip for
 # the close button the developer tools toolbox.
deleted file mode 100644
--- a/devtools/client/themes/images/command-console.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill #0b0b0b">
-  <path d="M6.8 9.7c0-.2 0-.3-.2-.4L4.9 7.6c-.3-.3-.7-.3-.9 0s-.3.6 0 .9l1.3 1.4L4 11.3c-.3.3-.3.6 0 .9s.6.3.9 0l1.8-1.8c.1-.2.2-.5.1-.7z"/>
-  <path d="M14.2 2H1.8c-.4 0-.8.4-.8.9v11.2c0 .4.3.9.8.9h12.4c.4 0 .8-.4.8-.9V2.9c0-.7-.6-.9-.8-.9zM14 14H2V6h12v8zm0-9H2V3h12v2z"/>
-</svg>
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -9,17 +9,16 @@
 
   --command-paintflashing-image: url(images/command-paintflashing.svg);
   --command-screenshot-image: url(images/command-screenshot.svg);
   --command-responsive-image: url(images/command-responsivemode.svg);
   --command-scratchpad-image: url(images/tool-scratchpad.svg);
   --command-pick-image: url(images/command-pick.svg);
   --command-pick-accessibility-image: url(images/command-pick-accessibility.svg);
   --command-frames-image: url(images/command-frames.svg);
-  --command-splitconsole-image: url(images/command-console.svg);
   --command-noautohide-image: url(images/command-noautohide.svg);
   --command-rulers-image: url(images/command-rulers.svg);
   --command-measure-image: url(images/command-measure.svg);
 }
 
 /* Toolbox tabbar */
 
 .devtools-tabbar {
@@ -208,20 +207,16 @@
 #command-button-pick::before {
   background-image: var(--command-pick-image);
 }
 
 #command-button-pick.accessibility::before {
   background-image: var(--command-pick-accessibility-image);
 }
 
-#command-button-splitconsole::before {
-  background-image: var(--command-splitconsole-image);
-}
-
 #command-button-noautohide::before {
   background-image: var(--command-noautohide-image);
 }
 
 #command-button-eyedropper::before {
   background-image: var(--command-eyedropper-image);
 }
 
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split.js
@@ -2,16 +2,19 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for splitting";
 const {Toolbox} = require("devtools/client/framework/toolbox");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N =
+  new LocalizationHelper("devtools/client/locales/toolbox.properties");
 
 // Test is slow on Linux EC2 instances - Bug 962931
 requestLongerTimeout(2);
 
 add_task(async function() {
   let toolbox;
 
   await addTab(TEST_URI);
@@ -29,18 +32,18 @@ add_task(async function() {
   checkHostType(Toolbox.HostType.WINDOW);
   checkToolboxUI();
   await toolbox.switchHost(Toolbox.HostType.BOTTOM);
 
   async function testConsoleLoadOnDifferentPanel() {
     info("About to check console loads even when non-webconsole panel is open");
 
     await openPanel("inspector");
-    let webconsoleReady = toolbox.once("webconsole-ready");
-    toolbox.toggleSplitConsole();
+    const webconsoleReady = toolbox.once("webconsole-ready");
+    await toolbox.toggleSplitConsole();
     await webconsoleReady;
     ok(true, "Webconsole has been triggered as loaded while another tool is active");
   }
 
   async function testKeyboardShortcuts() {
     info("About to check that panel responds to ESCAPE keyboard shortcut");
 
     let splitConsoleReady = toolbox.once("split-console");
@@ -55,144 +58,176 @@ add_task(async function() {
     await openAndCheckPanel("inspector");
     await openAndCheckPanel("styleeditor");
     await openAndCheckPanel("performance");
     await openAndCheckPanel("netmonitor");
 
     await checkWebconsolePanelOpened();
   }
 
-  function getCurrentUIState() {
+  async function getCurrentUIState() {
     let deck = toolbox.doc.querySelector("#toolbox-deck");
     let webconsolePanel = toolbox.webconsolePanel;
     let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
 
     let containerHeight = deck.parentNode.getBoundingClientRect().height;
     let deckHeight = deck.getBoundingClientRect().height;
     let webconsoleHeight = webconsolePanel.getBoundingClientRect().height;
     let splitterVisibility = !splitter.getAttribute("hidden");
     let openedConsolePanel = toolbox.currentToolId === "webconsole";
-    let cmdButton = toolbox.doc.querySelector("#command-button-splitconsole");
+    let menuLabel = await getMenuLabel(toolbox);
 
     return {
       deckHeight: deckHeight,
       containerHeight: containerHeight,
       webconsoleHeight: webconsoleHeight,
       splitterVisibility: splitterVisibility,
       openedConsolePanel: openedConsolePanel,
-      buttonSelected: cmdButton.classList.contains("checked")
+      menuLabel,
     };
   }
 
+  function getMenuLabel() {
+    return new Promise(resolve => {
+      const button = toolbox.doc.getElementById("toolbox-meatball-menu-button");
+      EventUtils.sendMouseEvent({ type: "click" }, button);
+
+      toolbox.doc.addEventListener("popupshown", () => {
+        const menuItem =
+          toolbox.doc.getElementById("toolbox-meatball-menu-splitconsole");
+
+        // Return undefined if the menu item is not available
+        let label;
+        if (menuItem) {
+          label =
+            menuItem.label ===
+            L10N.getStr("toolbox.meatballMenu.hideconsole.label")
+              ? "hide"
+              : "split";
+        }
+
+        // Wait for menu to close
+        toolbox.doc.addEventListener("popuphidden", () => {
+          resolve(label);
+        }, { once: true });
+        EventUtils.synthesizeKey("KEY_Escape");
+      }, { once: true });
+    });
+  }
+
   async function checkWebconsolePanelOpened() {
     info("About to check special cases when webconsole panel is open.");
 
     // Start with console split, so we can test for transition to main panel.
     await toolbox.toggleSplitConsole();
 
-    let currentUIState = getCurrentUIState();
+    let currentUIState = await getCurrentUIState();
 
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(currentUIState.buttonSelected, "The command button is selected");
+    is(currentUIState.menuLabel, "hide",
+       "The menu item indicates the console is split");
 
     await openPanel("webconsole");
-    currentUIState = getCurrentUIState();
+    currentUIState = await getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility,
        "Splitter is hidden when console is opened.");
     is(currentUIState.deckHeight, 0,
        "Deck has a height == 0 when console is opened.");
     is(currentUIState.webconsoleHeight, currentUIState.containerHeight,
        "Web console is full height.");
     ok(currentUIState.openedConsolePanel,
        "The console panel is the current tool");
-    ok(currentUIState.buttonSelected,
-       "The command button is still selected.");
+    is(currentUIState.menuLabel, undefined,
+       "The menu item is hidden when console is opened");
 
     // Make sure splitting console does nothing while webconsole is opened
     await toolbox.toggleSplitConsole();
 
-    currentUIState = getCurrentUIState();
+    currentUIState = await getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility,
        "Splitter is hidden when console is opened.");
     is(currentUIState.deckHeight, 0,
        "Deck has a height == 0 when console is opened.");
     is(currentUIState.webconsoleHeight, currentUIState.containerHeight,
        "Web console is full height.");
     ok(currentUIState.openedConsolePanel,
        "The console panel is the current tool");
-    ok(currentUIState.buttonSelected,
-       "The command button is still selected.");
+    is(currentUIState.menuLabel, undefined,
+       "The menu item is hidden when console is opened");
 
     // Make sure that split state is saved after opening another panel
     await openPanel("inspector");
-    currentUIState = getCurrentUIState();
+    currentUIState = await getCurrentUIState();
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(currentUIState.buttonSelected,
-       "The command button is still selected.");
+    is(currentUIState.menuLabel, "hide",
+       "The menu item still indicates the console is split");
 
     await toolbox.toggleSplitConsole();
   }
 
   async function checkToolboxUI() {
-    let currentUIState = getCurrentUIState();
+    let currentUIState = await getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility, "Splitter is hidden by default");
     is(currentUIState.deckHeight, currentUIState.containerHeight,
        "Deck has a height > 0 by default");
     is(currentUIState.webconsoleHeight, 0,
        "Web console is collapsed by default");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(!currentUIState.buttonSelected, "The command button is not selected.");
+    is(currentUIState.menuLabel, "split",
+       "The menu item indicates the console is not split");
 
     await toolbox.toggleSplitConsole();
 
-    currentUIState = getCurrentUIState();
+    currentUIState = await getCurrentUIState();
 
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     is(Math.round(currentUIState.deckHeight + currentUIState.webconsoleHeight),
        currentUIState.containerHeight,
        "Everything adds up to container height");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(currentUIState.buttonSelected, "The command button is selected.");
+    is(currentUIState.menuLabel, "hide",
+       "The menu item indicates the console is split");
 
     await toolbox.toggleSplitConsole();
 
-    currentUIState = getCurrentUIState();
+    currentUIState = await getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility, "Splitter is hidden after toggling");
     is(currentUIState.deckHeight, currentUIState.containerHeight,
        "Deck has a height > 0 after toggling");
     is(currentUIState.webconsoleHeight, 0,
        "Web console is collapsed after toggling");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(!currentUIState.buttonSelected, "The command button is not selected.");
+    is(currentUIState.menuLabel, "split",
+       "The menu item indicates the console is not split");
   }
 
   async function openPanel(toolId) {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     toolbox = await gDevTools.showToolbox(target, toolId);
   }
 
   async function openAndCheckPanel(toolId) {
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_persist.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_split_persist.js
@@ -2,57 +2,63 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the split console state is persisted.
 
+let {LocalizationHelper} = require("devtools/shared/l10n");
+let L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
+
 const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for splitting</p>";
 
 add_task(async function() {
   info("Opening a tab while there is no user setting on split console pref");
   let toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
   ok(!toolbox.splitConsole, "Split console is hidden by default");
-  ok(!isCommandButtonChecked(toolbox), "Split console button is unchecked by default.");
+  ok(!(await doesMenuSayHide(toolbox)),
+     "Split console menu item says split by default");
 
   await toggleSplitConsoleWithEscape(toolbox);
   ok(toolbox.splitConsole, "Split console is now visible.");
-  ok(isCommandButtonChecked(toolbox), "Split console button is now checked.");
+  ok(await doesMenuSayHide(toolbox), "Split console menu item now says hide");
   ok(getVisiblePrefValue(), "Visibility pref is true");
 
   is(getHeightPrefValue(), toolbox.webconsolePanel.height,
      "Panel height matches the pref");
   toolbox.webconsolePanel.height = 200;
 
   await toolbox.destroy();
 
   info("Opening a tab while there is a true user setting on split console pref");
   toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
   ok(toolbox.splitConsole, "Split console is visible by default.");
 
-  ok(isCommandButtonChecked(toolbox), "Split console button is checked by default.");
+  ok(await doesMenuSayHide(toolbox),
+     "Split console menu item initially says hide");
   is(getHeightPrefValue(), 200, "Height is set based on panel height after closing");
 
   let activeElement = getActiveElement(toolbox.doc);
   let inputNode = toolbox.getPanel("webconsole").hud.jsterm.inputNode;
   is(activeElement, inputNode, "Split console input is focused by default");
 
   toolbox.webconsolePanel.height = 1;
   ok(toolbox.webconsolePanel.clientHeight > 1,
      "The actual height of the console is bound with a min height");
 
   toolbox.webconsolePanel.height = 10000;
   ok(toolbox.webconsolePanel.clientHeight < 10000,
      "The actual height of the console is bound with a max height");
 
   await toggleSplitConsoleWithEscape(toolbox);
   ok(!toolbox.splitConsole, "Split console is now hidden.");
-  ok(!isCommandButtonChecked(toolbox), "Split console button is now unchecked.");
+  ok(!(await doesMenuSayHide(toolbox)),
+     "Split console menu item now says split");
   ok(!getVisiblePrefValue(), "Visibility pref is false");
 
   await toolbox.destroy();
 
   is(getHeightPrefValue(), 10000, "Height is set based on panel height after closing");
 
   info("Opening a tab while there is a false user setting on split " +
        "console pref");
@@ -75,19 +81,38 @@ function getActiveElement(doc) {
 function getVisiblePrefValue() {
   return Services.prefs.getBoolPref("devtools.toolbox.splitconsoleEnabled");
 }
 
 function getHeightPrefValue() {
   return Services.prefs.getIntPref("devtools.toolbox.splitconsoleHeight");
 }
 
-function isCommandButtonChecked(toolbox) {
-  return toolbox.doc.querySelector("#command-button-splitconsole")
-    .classList.contains("checked");
+function doesMenuSayHide(toolbox) {
+  return new Promise(resolve => {
+    const button = toolbox.doc.getElementById("toolbox-meatball-menu-button");
+    EventUtils.sendMouseEvent({ type: "click" }, button);
+
+    toolbox.doc.addEventListener("popupshown", () => {
+      const menuItem =
+        toolbox.doc.getElementById("toolbox-meatball-menu-splitconsole");
+
+      const result =
+        menuItem &&
+        menuItem.label ===
+          L10N.getStr("toolbox.meatballMenu.hideconsole.label");
+
+      toolbox.doc.addEventListener("popuphidden", () => {
+        resolve(result);
+      },
+      { once: true });
+      EventUtils.synthesizeKey("KEY_Escape");
+    },
+    { once: true });
+  });
 }
 
 function toggleSplitConsoleWithEscape(toolbox) {
   let onceSplitConsole = toolbox.once("split-console");
   let toolboxWindow = toolbox.win;
   toolboxWindow.focus();
   EventUtils.sendKey("ESCAPE", toolboxWindow);
   return onceSplitConsole;
--- a/devtools/client/webconsole/test/browser_webconsole_split.js
+++ b/devtools/client/webconsole/test/browser_webconsole_split.js
@@ -8,32 +8,39 @@
 const TEST_URI = "data:text/html;charset=utf-8,Web Console test for splitting";
 
 function test() {
   waitForExplicitFinish();
   // Test is slow on Linux EC2 instances - Bug 962931
   requestLongerTimeout(2);
 
   let {Toolbox} = require("devtools/client/framework/toolbox");
+  let {LocalizationHelper} = require("devtools/shared/l10n");
+  let L10N =
+    new LocalizationHelper("devtools/client/locales/toolbox.properties");
   let toolbox;
 
   loadTab(TEST_URI).then(testConsoleLoadOnDifferentPanel);
 
   function testConsoleLoadOnDifferentPanel() {
     info("About to check console loads even when non-webconsole panel is open");
 
     openPanel("inspector").then(() => {
+      let initialOpenComplete;
+
       toolbox.on("webconsole-ready", () => {
         ok(true, "Webconsole has been triggered as loaded while another tool " +
                  "is active");
-        testKeyboardShortcuts();
+        initialOpenComplete.then(() => {
+          testKeyboardShortcuts();
+        });
       });
 
       // Opens split console.
-      toolbox.toggleSplitConsole();
+      initialOpenComplete = toolbox.toggleSplitConsole();
     });
   }
 
   function testKeyboardShortcuts() {
     info("About to check that panel responds to ESCAPE keyboard shortcut");
 
     toolbox.once("split-console", () => {
       ok(true, "Split console has been triggered via ESCAPE keypress");
@@ -54,148 +61,179 @@ function test() {
       yield openAndCheckPanel("performance");
       yield openAndCheckPanel("netmonitor");
 
       yield checkWebconsolePanelOpened();
       testBottomHost();
     });
   }
 
-  function getCurrentUIState() {
+  async function getCurrentUIState() {
     let win = toolbox.win;
     let deck = toolbox.doc.querySelector("#toolbox-deck");
     let webconsolePanel = toolbox.webconsolePanel;
     let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
 
     let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode)
       .getPropertyValue("height"));
     let deckHeight = parseFloat(win.getComputedStyle(deck)
       .getPropertyValue("height"));
     let webconsoleHeight = parseFloat(win.getComputedStyle(webconsolePanel)
       .getPropertyValue("height"));
     let splitterVisibility = !splitter.getAttribute("hidden");
     let openedConsolePanel = toolbox.currentToolId === "webconsole";
-    let cmdButton = toolbox.doc.querySelector("#command-button-splitconsole");
 
     return {
       deckHeight: deckHeight,
       containerHeight: containerHeight,
       webconsoleHeight: webconsoleHeight,
       splitterVisibility: splitterVisibility,
       openedConsolePanel: openedConsolePanel,
-      buttonSelected: cmdButton.classList.contains("checked")
+      menuLabel: await getMenuLabel(toolbox),
     };
   }
 
+  function getMenuLabel(toolbox) {
+    return new Promise(resolve => {
+      const button = toolbox.doc.getElementById("toolbox-meatball-menu-button");
+      EventUtils.sendMouseEvent({ type: "click" }, button);
+
+      toolbox.doc.addEventListener("popupshown", () => {
+        const menuItem =
+          toolbox.doc.getElementById("toolbox-meatball-menu-splitconsole");
+
+        // Return undefined if the menu item is not available
+        let label;
+        if (menuItem) {
+          label =
+            menuItem.label ===
+            L10N.getStr("toolbox.meatballMenu.hideconsole.label")
+              ? "hide"
+              : "split";
+        }
+
+        // Wait for menu to close
+        toolbox.doc.addEventListener("popuphidden", () => {
+          resolve(label);
+        }, { once: true });
+        EventUtils.synthesizeKey("KEY_Escape");
+      }, { once: true });
+    });
+  }
+
   const checkWebconsolePanelOpened = Task.async(function* () {
     info("About to check special cases when webconsole panel is open.");
 
     // Start with console split, so we can test for transition to main panel.
     yield toolbox.toggleSplitConsole();
 
-    let currentUIState = getCurrentUIState();
+    let currentUIState = yield getCurrentUIState();
 
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(currentUIState.buttonSelected, "The command button is selected");
+    is(currentUIState.menuLabel, "hide",
+       "The menu item indicates the console is split");
 
     yield openPanel("webconsole");
-    currentUIState = getCurrentUIState();
+    currentUIState = yield getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility,
        "Splitter is hidden when console is opened.");
     is(currentUIState.deckHeight, 0,
        "Deck has a height == 0 when console is opened.");
     is(currentUIState.webconsoleHeight, currentUIState.containerHeight,
        "Web console is full height.");
     ok(currentUIState.openedConsolePanel,
        "The console panel is the current tool");
-    ok(currentUIState.buttonSelected,
-       "The command button is still selected.");
+    is(currentUIState.menuLabel, undefined,
+       "The menu item is hidden when console is opened");
 
     // Make sure splitting console does nothing while webconsole is opened
     yield toolbox.toggleSplitConsole();
 
-    currentUIState = getCurrentUIState();
+    currentUIState = yield getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility,
        "Splitter is hidden when console is opened.");
     is(currentUIState.deckHeight, 0,
        "Deck has a height == 0 when console is opened.");
     is(currentUIState.webconsoleHeight, currentUIState.containerHeight,
        "Web console is full height.");
     ok(currentUIState.openedConsolePanel,
        "The console panel is the current tool");
-    ok(currentUIState.buttonSelected,
-       "The command button is still selected.");
+    is(currentUIState.menuLabel, undefined,
+       "The menu item is hidden when console is opened");
 
     // Make sure that split state is saved after opening another panel
     yield openPanel("inspector");
-    currentUIState = getCurrentUIState();
+    currentUIState = yield getCurrentUIState();
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(currentUIState.buttonSelected,
-       "The command button is still selected.");
+    is(currentUIState.menuLabel, "hide",
+       "The menu item still indicates the console is split");
 
     yield toolbox.toggleSplitConsole();
   });
 
   const checkToolboxUI = Task.async(function* () {
-    let currentUIState = getCurrentUIState();
+    let currentUIState = yield getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility, "Splitter is hidden by default");
     is(currentUIState.deckHeight, currentUIState.containerHeight,
        "Deck has a height > 0 by default");
     is(currentUIState.webconsoleHeight, 0,
        "Web console is collapsed by default");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(!currentUIState.buttonSelected, "The command button is not selected.");
+    is(currentUIState.menuLabel, "split",
+       "The menu item indicates the console is not split");
 
     yield toolbox.toggleSplitConsole();
 
-    currentUIState = getCurrentUIState();
+    currentUIState = yield getCurrentUIState();
 
     ok(currentUIState.splitterVisibility,
        "Splitter is visible when console is split");
     ok(currentUIState.deckHeight > 0,
        "Deck has a height > 0 when console is split");
     ok(currentUIState.webconsoleHeight > 0,
        "Web console has a height > 0 when console is split");
     is(Math.round(currentUIState.deckHeight + currentUIState.webconsoleHeight),
        currentUIState.containerHeight,
        "Everything adds up to container height");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(currentUIState.buttonSelected, "The command button is selected.");
+    is(currentUIState.menuLabel, "hide",
+       "The menu item indicates the console is split");
 
     yield toolbox.toggleSplitConsole();
 
-    currentUIState = getCurrentUIState();
+    currentUIState = yield getCurrentUIState();
 
     ok(!currentUIState.splitterVisibility, "Splitter is hidden after toggling");
     is(currentUIState.deckHeight, currentUIState.containerHeight,
        "Deck has a height > 0 after toggling");
     is(currentUIState.webconsoleHeight, 0,
        "Web console is collapsed after toggling");
     ok(!currentUIState.openedConsolePanel,
        "The console panel is not the current tool");
-    ok(!currentUIState.buttonSelected, "The command button is not selected.");
+    is(currentUIState.menuLabel, "split",
+       "The menu item indicates the console is not split");
   });
 
   function openPanel(toolId) {
     let deferred = defer();
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.showToolbox(target, toolId).then(function (box) {
       toolbox = box;
       deferred.resolve();
--- a/devtools/client/webconsole/test/browser_webconsole_split_persist.js
+++ b/devtools/client/webconsole/test/browser_webconsole_split_persist.js
@@ -3,50 +3,54 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
  "use strict";
 
  function test() {
   info("Test that the split console state is persisted");
 
+  let {LocalizationHelper} = require("devtools/shared/l10n");
+  let L10N =
+    new LocalizationHelper("devtools/client/locales/toolbox.properties");
+
   let toolbox;
   let TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for " +
                  "splitting</p>";
 
   Task.spawn(runner).then(finish);
 
   function* runner() {
     info("Opening a tab while there is no user setting on split console pref");
     let {tab} = yield loadTab(TEST_URI);
     let target = TargetFactory.forTab(tab);
     toolbox = yield gDevTools.showToolbox(target, "inspector");
 
     ok(!toolbox.splitConsole, "Split console is hidden by default.");
-    ok(!isCommandButtonChecked(), "Split console button is unchecked by " +
-                                  "default.");
+    ok(!(yield doesMenuSayHide()),
+       "Split console menu item says split by default");
     yield toggleSplitConsoleWithEscape();
     ok(toolbox.splitConsole, "Split console is now visible.");
-    ok(isCommandButtonChecked(), "Split console button is now checked.");
+    ok(yield doesMenuSayHide(), "Split console menu item now says hide");
     ok(getVisiblePrefValue(), "Visibility pref is true");
 
     is(getHeightPrefValue(), toolbox.webconsolePanel.height,
        "Panel height matches the pref");
     toolbox.webconsolePanel.height = 200;
 
     yield toolbox.destroy();
 
     info("Opening a tab while there is a true user setting on split console " +
          "pref");
     ({tab} = yield loadTab(TEST_URI));
     target = TargetFactory.forTab(tab);
     toolbox = yield gDevTools.showToolbox(target, "inspector");
 
     ok(toolbox.splitConsole, "Split console is visible by default.");
-    ok(isCommandButtonChecked(), "Split console button is checked by default.");
+    ok(yield doesMenuSayHide(), "Split console menu item initially says hide");
     is(getHeightPrefValue(), 200, "Height is set based on panel height after " +
                                   "closing");
 
     // Use the binding element since jsterm.inputNode is a XUL textarea element.
     let activeElement = getActiveElement(toolbox.doc);
     activeElement = activeElement.ownerDocument.getBindingParent(activeElement);
     let inputNode = toolbox.getPanel("webconsole").hud.jsterm.inputNode;
     is(activeElement, inputNode, "Split console input is focused by default");
@@ -56,17 +60,17 @@
        "The actual height of the console is bound with a min height");
 
     toolbox.webconsolePanel.height = 10000;
     ok(toolbox.webconsolePanel.clientHeight < 10000,
        "The actual height of the console is bound with a max height");
 
     yield toggleSplitConsoleWithEscape();
     ok(!toolbox.splitConsole, "Split console is now hidden.");
-    ok(!isCommandButtonChecked(), "Split console button is now unchecked.");
+    ok(!(yield doesMenuSayHide()), "Split console menu item now says split");
     ok(!getVisiblePrefValue(), "Visibility pref is false");
 
     yield toolbox.destroy();
 
     is(getHeightPrefValue(), 10000,
        "Height is set based on panel height after closing");
 
     info("Opening a tab while there is a false user setting on split " +
@@ -92,26 +96,45 @@
   function getVisiblePrefValue() {
     return Services.prefs.getBoolPref("devtools.toolbox.splitconsoleEnabled");
   }
 
   function getHeightPrefValue() {
     return Services.prefs.getIntPref("devtools.toolbox.splitconsoleHeight");
   }
 
-  function isCommandButtonChecked() {
-    return toolbox.doc.querySelector("#command-button-splitconsole")
-      .classList.contains("checked");
+  function doesMenuSayHide() {
+    return new Promise(resolve => {
+      const button = toolbox.doc.getElementById("toolbox-meatball-menu-button");
+      EventUtils.sendMouseEvent({ type: "click" }, button);
+
+      toolbox.doc.addEventListener("popupshown", () => {
+        const menuItem =
+          toolbox.doc.getElementById("toolbox-meatball-menu-splitconsole");
+
+        const result =
+          menuItem &&
+          menuItem.label ===
+            L10N.getStr("toolbox.meatballMenu.hideconsole.label");
+
+        toolbox.doc.addEventListener("popuphidden", () => {
+          resolve(result);
+        },
+        { once: true });
+        EventUtils.synthesizeKey("KEY_Escape");
+      },
+      { once: true });
+    });
   }
 
   function toggleSplitConsoleWithEscape() {
     let onceSplitConsole = toolbox.once("split-console");
     let contentWindow = toolbox.win;
     contentWindow.focus();
-    EventUtils.sendKey("ESCAPE", contentWindow);
+    EventUtils.synthesizeKey("KEY_Escape");
     return onceSplitConsole;
   }
 
   function finish() {
     toolbox = TEST_URI = null;
     finishTest();
   }
 }