Bug 1444302 - Part 1. Add the close button into the split console. r?nchevobbe draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Mon, 02 Apr 2018 16:10:53 +0900
changeset 796726 242362493fb00d6b2dc127d1508dcb8ecabff54e
parent 796662 54063deb2f1caf8c4acf6461d3ba779805835c96
child 796727 f4189cf23693b9325a15dc54f51fd0977dc3715a
push id110349
push userbmo:mantaroh@gmail.com
push dateFri, 18 May 2018 07:18:46 +0000
reviewersnchevobbe
bugs1444302
milestone62.0a1
Bug 1444302 - Part 1. Add the close button into the split console. r?nchevobbe This patch will display the button of closing split console. The FilterBar should display this close button if target is split console. MozReview-Commit-ID: 29VjaqHdh2S
devtools/client/themes/webconsole.css
devtools/client/webconsole/actions/ui.js
devtools/client/webconsole/components/App.js
devtools/client/webconsole/components/FilterBar.js
devtools/client/webconsole/constants.js
devtools/client/webconsole/new-console-output-wrapper.js
devtools/client/webconsole/new-webconsole.js
devtools/client/webconsole/reducers/ui.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_webconsole_split_close_button.js
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -501,36 +501,37 @@ a.learn-more-link.webconsole-learn-more-
 }
 
 /*
   This element contains the different toolbars in the console
     - primary, containing the clear messages button and the text search input.
       It can expand as much as it need.
     - filtered messages, containing the "X items hidden by filters" and the reset filters button.
       It should be on the same row than the primary bar if it fits there, or on its own 100% row if it is wrapped.
+    - close button, close the split console panel. This button will be displayed on righ-top of tool bar always.
     - secondary, containing the filter buttons (Error, Warning, …).
       It should be on its own 100% row.
 
   Basically here's what we can have :
 
-  -----------------------------------------------------------------------------------------------------------
-  | Clear button - Open filter bar button - Filter Input | X items hidden by filters - Reset Filters button |
-  -----------------------------------------------------------------------------------------------------------
-  | Error - Warning - Log - Info - Debug - CSS - Network - XHR                                              |
-  -----------------------------------------------------------------------------------------------------------
+  --------------------------------------------------------------------------------------------------------------------------
+  | Clear button - Open filter bar button - Filter Input | X items hidden by filters - Reset Filters button | Close button |
+  --------------------------------------------------------------------------------------------------------------------------
+  | Error - Warning - Log - Info - Debug - CSS - Network - XHR                                                             |
+  --------------------------------------------------------------------------------------------------------------------------
 
   or
 
-  ------------------------------------------------------------------------------------
-  | Clear button - Open filter bar button - Filter Input                             |
-  ------------------------------------------------------------------------------------
-  |                                X items hidden by filters  - Reset Filters button |
-  ------------------------------------------------------------------------------------
-  | Error - Warning - Log - Info - Debug - CSS - Network - XHR                       |
-  ------------------------------------------------------------------------------------
+  ---------------------------------------------------------------------------------------------------
+  | Clear button - Open filter bar button - Filter Input                             | Close button |
+  ---------------------------------------------------------------------------------------------------
+  |                                               X items hidden by filters  - Reset Filters button |
+  ---------------------------------------------------------------------------------------------------
+  | Error - Warning - Log - Info - Debug - CSS - Network - XHR                                      |
+  ---------------------------------------------------------------------------------------------------
 */
 .webconsole-filteringbar-wrapper {
   display: flex;
   grid-row: 1 / 2;
   /* Wrap so the "Hidden messages" bar can go to its own row if needed */
   flex-wrap: wrap;
   --primary-toolbar-height: 29px;
 }
@@ -546,16 +547,20 @@ a.learn-more-link.webconsole-learn-more-
 
 .devtools-toolbar.webconsole-filterbar-secondary {
   display: flex;
   width: 100%;
   align-items: center;
   -moz-user-select: none;
 }
 
+.split-console-close-button-wrapper {
+    min-height: var(--primary-toolbar-height);
+}
+
 .webconsole-filterbar-primary .devtools-plaininput {
   flex: 1 1 100%;
   align-self: stretch;
   margin-left: 1px;
   padding-inline-start: 4px;
   border: 1px solid transparent;
 }
 
@@ -1039,8 +1044,12 @@ html[dir="rtl"] .webconsole-output-wrapp
 
 .sidebar-contents .object-inspector {
   min-width: 100%;
 }
 
 .theme-twisty {
   cursor: default;
 }
+
+#split-console-close-button::before {
+  background-image: var(--close-button-image);
+}
--- a/devtools/client/webconsole/actions/ui.js
+++ b/devtools/client/webconsole/actions/ui.js
@@ -13,16 +13,17 @@ const {
   FILTER_BAR_TOGGLE,
   INITIALIZE,
   PERSIST_TOGGLE,
   PREFS,
   SELECT_NETWORK_MESSAGE_TAB,
   SIDEBAR_CLOSE,
   SHOW_OBJECT_IN_SIDEBAR,
   TIMESTAMPS_TOGGLE,
+  SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE,
 } = require("devtools/client/webconsole/constants");
 
 function filterBarToggle(show) {
   return (dispatch, getState, {prefsService}) => {
     dispatch({
       type: FILTER_BAR_TOGGLE,
     });
     const {filterBarVisible} = getAllUi(getState());
@@ -61,16 +62,23 @@ function initialize() {
 }
 
 function sidebarClose(show) {
   return {
     type: SIDEBAR_CLOSE,
   };
 }
 
+function splitConsoleCloseButtonToggle(shouldDisplayButton) {
+  return {
+    type: SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE,
+    shouldDisplayButton,
+  };
+}
+
 function showObjectInSidebar(actorId, messageId) {
   return (dispatch, getState) => {
     let { parameters } = getMessage(getState(), messageId);
     if (Array.isArray(parameters)) {
       for (let parameter of parameters) {
         if (parameter.actor === actorId) {
           dispatch({
             type: SHOW_OBJECT_IN_SIDEBAR,
@@ -86,9 +94,10 @@ function showObjectInSidebar(actorId, me
 module.exports = {
   filterBarToggle,
   initialize,
   persistToggle,
   selectNetworkMessageTab,
   sidebarClose,
   showObjectInSidebar,
   timestampsToggle,
+  splitConsoleCloseButtonToggle,
 };
--- a/devtools/client/webconsole/components/App.js
+++ b/devtools/client/webconsole/components/App.js
@@ -37,16 +37,17 @@ class App extends Component {
   static get propTypes() {
     return {
       attachRefToHud: PropTypes.func.isRequired,
       dispatch: PropTypes.func.isRequired,
       hud: PropTypes.object.isRequired,
       notifications: PropTypes.object,
       onFirstMeaningfulPaint: PropTypes.func.isRequired,
       serviceContainer: PropTypes.object.isRequired,
+      closeSplitConsole: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.onPaste = this.onPaste.bind(this);
   }
@@ -115,16 +116,17 @@ class App extends Component {
 
   render() {
     const {
       attachRefToHud,
       hud,
       notifications,
       onFirstMeaningfulPaint,
       serviceContainer,
+      closeSplitConsole,
     } = this.props;
 
     // Render the entire Console panel. The panel consists
     // from the following parts:
     // * FilterBar - Buttons & free text for content filtering
     // * Content - List of logs & messages
     // * SideBar - Object inspector
     // * NotificationBox - Notifications for JSTerm (self-xss warning at the moment)
@@ -134,17 +136,18 @@ class App extends Component {
         className: "webconsole-output-wrapper",
         ref: node => {
           this.node = node;
         }},
         FilterBar({
           hidePersistLogsCheckbox: hud.isBrowserConsole,
           serviceContainer: {
             attachRefToHud
-          }
+          },
+          closeSplitConsole
         }),
         ConsoleOutput({
           serviceContainer,
           onFirstMeaningfulPaint,
         }),
         SideBar({
           serviceContainer,
         }),
--- a/devtools/client/webconsole/components/FilterBar.js
+++ b/devtools/client/webconsole/components/FilterBar.js
@@ -28,16 +28,18 @@ class FilterBar extends Component {
       filter: PropTypes.object.isRequired,
       serviceContainer: PropTypes.shape({
         attachRefToHud: PropTypes.func.isRequired,
       }).isRequired,
       filterBarVisible: PropTypes.bool.isRequired,
       persistLogs: PropTypes.bool.isRequired,
       hidePersistLogsCheckbox: PropTypes.bool.isRequired,
       filteredMessagesCount: PropTypes.object.isRequired,
+      closeButtonVisible: PropTypes.bool,
+      closeSplitConsole: PropTypes.func,
     };
   }
 
   static get defaultProps() {
     return {
       hidePersistLogsCheckbox: false,
     };
   }
@@ -57,35 +59,47 @@ class FilterBar extends Component {
   componentDidMount() {
     this.props.serviceContainer.attachRefToHud(
       "filterBox",
       this.wrapperNode.querySelector(".text-filter")
     );
   }
 
   shouldComponentUpdate(nextProps, nextState) {
-    if (nextProps.filter !== this.props.filter) {
+    const {
+      filter,
+      filterBarVisible,
+      persistLogs,
+      filteredMessagesCount,
+      closeButtonVisible,
+    } = this.props;
+
+    if (nextProps.filter !== filter) {
       return true;
     }
 
-    if (nextProps.filterBarVisible !== this.props.filterBarVisible) {
+    if (nextProps.filterBarVisible !== filterBarVisible) {
       return true;
     }
 
-    if (nextProps.persistLogs !== this.props.persistLogs) {
+    if (nextProps.persistLogs !== persistLogs) {
       return true;
     }
 
     if (
-      JSON.stringify(nextProps.filteredMessagesCount)
-      !== JSON.stringify(this.props.filteredMessagesCount)
+      JSON.stringify(nextProps.filteredMessagesCount) !==
+        JSON.stringify(filteredMessagesCount)
     ) {
       return true;
     }
 
+    if (nextProps.closeButtonVisible != closeButtonVisible) {
+      return true;
+    }
+
     return false;
   }
 
   onClickMessagesClear() {
     this.props.dispatch(actions.messagesClear());
   }
 
   onClickFilterBarToggle() {
@@ -223,16 +237,17 @@ class FilterBar extends Component {
 
   render() {
     const {
       filter,
       filterBarVisible,
       persistLogs,
       filteredMessagesCount,
       hidePersistLogsCheckbox,
+      closeSplitConsole,
     } = this.props;
 
     let children = [
       dom.div({
         className: "devtools-toolbar webconsole-filterbar-primary",
         key: "primary-bar",
       },
         dom.button({
@@ -264,16 +279,31 @@ class FilterBar extends Component {
         }),
       )
     ];
 
     if (filteredMessagesCount.global > 0) {
       children.push(this.renderFilteredMessagesBar());
     }
 
+    if (this.props.closeButtonVisible) {
+      children.push(dom.div(
+        {
+          className: "devtools-toolbar split-console-close-button-wrapper"
+        },
+        dom.button({
+          id: "split-console-close-button",
+          className: "devtools-button",
+          onClick: () => {
+            closeSplitConsole();
+          },
+        })
+      ));
+    }
+
     if (filterBarVisible) {
       children.push(this.renderFiltersConfigBar());
     }
 
     return (
       dom.div({
         className: "webconsole-filteringbar-wrapper",
         ref: node => {
@@ -287,12 +317,13 @@ class FilterBar extends Component {
 
 function mapStateToProps(state) {
   let uiState = getAllUi(state);
   return {
     filter: getAllFilters(state),
     filterBarVisible: uiState.filterBarVisible,
     persistLogs: uiState.persistLogs,
     filteredMessagesCount: getFilteredMessagesCount(state),
+    closeButtonVisible: uiState.closeButtonVisible,
   };
 }
 
 module.exports = connect(mapStateToProps)(FilterBar);
--- a/devtools/client/webconsole/constants.js
+++ b/devtools/client/webconsole/constants.js
@@ -24,16 +24,17 @@ const actionTypes = {
   PRIVATE_MESSAGES_CLEAR: "PRIVATE_MESSAGES_CLEAR",
   REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
   SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
   SIDEBAR_CLOSE: "SIDEBAR_CLOSE",
   SHOW_OBJECT_IN_SIDEBAR: "SHOW_OBJECT_IN_SIDEBAR",
   TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
   APPEND_NOTIFICATION: "APPEND_NOTIFICATION",
   REMOVE_NOTIFICATION: "REMOVE_NOTIFICATION",
+  SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE: "SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE",
 };
 
 const prefs = {
   PREFS: {
     // Filter preferences only have the suffix since they can be used either for the
     // webconsole or the browser console.
     FILTER: {
       ERROR: "filter.error",
--- a/devtools/client/webconsole/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output-wrapper.js
@@ -208,16 +208,17 @@ NewConsoleOutputWrapper.prototype = {
         });
       }
 
       const app = App({
         attachRefToHud,
         serviceContainer,
         hud,
         onFirstMeaningfulPaint: resolve,
+        closeSplitConsole: this.closeSplitConsole.bind(this),
       });
 
       // Render the root Application component.
       let provider = createElement(Provider, { store }, app);
       this.body = ReactDOM.render(provider, this.parentNode);
     });
   },
 
@@ -340,16 +341,21 @@ NewConsoleOutputWrapper.prototype = {
   dispatchRequestUpdate: function(id, data) {
     this.batchedRequestUpdates({ id, data });
   },
 
   dispatchSidebarClose: function() {
     store.dispatch(actions.sidebarClose());
   },
 
+  dispatchSplitConsoleCloseButtonToggle: function() {
+    store.dispatch(actions.splitConsoleCloseButtonToggle(
+      this.toolbox && this.toolbox.currentToolId !== "webconsole"));
+  },
+
   batchedMessageUpdates: function(info) {
     this.queuedMessageUpdates.push(info);
     this.setTimeoutIfNeeded();
   },
 
   batchedRequestUpdates: function(message) {
     this.queuedRequestUpdates.push(message);
     this.setTimeoutIfNeeded();
@@ -412,13 +418,18 @@ NewConsoleOutputWrapper.prototype = {
         done();
       }, 50);
     });
   },
 
   // Should be used for test purpose only.
   getStore: function() {
     return store;
+  },
+
+  // Called by pushing close button.
+  closeSplitConsole() {
+    this.toolbox.closeSplitConsole();
   }
 };
 
 // Exports from this module
 module.exports = NewConsoleOutputWrapper;
--- a/devtools/client/webconsole/new-webconsole.js
+++ b/devtools/client/webconsole/new-webconsole.js
@@ -43,16 +43,17 @@ const PREF_SIDEBAR_ENABLED = "devtools.w
 function NewWebConsoleFrame(webConsoleOwner) {
   this.owner = webConsoleOwner;
   this.hudId = this.owner.hudId;
   this.isBrowserConsole = this.owner._browserConsole;
   this.window = this.owner.iframeWindow;
 
   this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
   this._onPanelSelected = this._onPanelSelected.bind(this);
+  this._onChangeSplitConsoleState = this._onChangeSplitConsoleState.bind(this);
 
   EventEmitter.decorate(this);
 }
 NewWebConsoleFrame.prototype = {
   /**
    * Getter for the debugger WebConsoleClient.
    * @type object
    */
@@ -98,16 +99,18 @@ NewWebConsoleFrame.prototype = {
     if (this.jsterm) {
       this.jsterm.destroy();
       this.jsterm = null;
     }
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
     if (toolbox) {
       toolbox.off("webconsole-selected", this._onPanelSelected);
+      toolbox.off("split-console", this._onChangeSplitConsoleState);
+      toolbox.off("select", this._onChangeSplitConsoleState);
     }
 
     this.window = this.owner = this.newConsoleOutput = null;
 
     let onDestroy = () => {
       this._destroyer.resolve(null);
     };
     if (this.proxy) {
@@ -207,16 +210,18 @@ NewWebConsoleFrame.prototype = {
     // Toggle the timestamp on preference change
     Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this._onToolboxPrefChanged();
 
     this._initShortcuts();
 
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
+      toolbox.on("split-console", this._onChangeSplitConsoleState);
+      toolbox.on("select", this._onChangeSplitConsoleState);
     }
   },
 
   _initShortcuts: function() {
     let shortcuts = new KeyShortcuts({
       window: this.window
     });
 
@@ -291,16 +296,20 @@ NewWebConsoleFrame.prototype = {
    * Sets the focus to JavaScript input field when the web console tab is
    * selected or when there is a split console present.
    * @private
    */
   _onPanelSelected: function() {
     this.jsterm.focus();
   },
 
+  _onChangeSplitConsoleState: function() {
+    this.newConsoleOutput.dispatchSplitConsoleCloseButtonToggle();
+  },
+
   /**
    * Handler for the tabNavigated notification.
    *
    * @param string event
    *        Event name.
    * @param object packet
    *        Notification packet received from the server.
    */
--- a/devtools/client/webconsole/reducers/ui.js
+++ b/devtools/client/webconsole/reducers/ui.js
@@ -9,30 +9,32 @@ const {
   FILTER_BAR_TOGGLE,
   INITIALIZE,
   PERSIST_TOGGLE,
   SELECT_NETWORK_MESSAGE_TAB,
   SIDEBAR_CLOSE,
   SHOW_OBJECT_IN_SIDEBAR,
   TIMESTAMPS_TOGGLE,
   MESSAGES_CLEAR,
+  SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE,
 } = require("devtools/client/webconsole/constants");
 
 const {
   PANELS,
 } = require("devtools/client/netmonitor/src/constants");
 
 const UiState = (overrides) => Object.freeze(Object.assign({
   filterBarVisible: false,
   initialized: false,
   networkMessageActiveTabId: PANELS.HEADERS,
   persistLogs: false,
   sidebarVisible: false,
   timestampsVisible: true,
-  gripInSidebar: null
+  gripInSidebar: null,
+  closeButtonVisible: false,
 }, overrides));
 
 function ui(state = UiState(), action) {
   switch (action.type) {
     case FILTER_BAR_TOGGLE:
       return Object.assign({}, state, {filterBarVisible: !state.filterBarVisible});
     case PERSIST_TOGGLE:
       return Object.assign({}, state, {persistLogs: !state.persistLogs});
@@ -49,16 +51,18 @@ function ui(state = UiState(), action) {
       return Object.assign({}, state, {initialized: true});
     case MESSAGES_CLEAR:
       return Object.assign({}, state, {sidebarVisible: false, gripInSidebar: null});
     case SHOW_OBJECT_IN_SIDEBAR:
       if (action.grip === state.gripInSidebar) {
         return state;
       }
       return Object.assign({}, state, {sidebarVisible: true, gripInSidebar: action.grip});
+    case SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE:
+      return Object.assign({}, state, {closeButtonVisible: action.shouldDisplayButton});
   }
 
   return state;
 }
 
 module.exports = {
   UiState,
   ui,
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -337,16 +337,17 @@ subsuite = clipboard
 [browser_webconsole_shows_reqs_in_netmonitor.js]
 [browser_webconsole_sidebar_object_expand_when_message_pruned.js]
 [browser_webconsole_sourcemap_css.js]
 [browser_webconsole_sourcemap_error.js]
 [browser_webconsole_sourcemap_invalid.js]
 [browser_webconsole_sourcemap_nosource.js]
 [browser_webconsole_split.js]
 skip-if = (os == 'mac') || (os == 'linux' && !debug && bits == 64) || (os == 'win') # Bug 1454123 disabled on OS X, Windows and Linux64 for frequent failures
+[browser_webconsole_split_close_button.js]
 [browser_webconsole_split_escape_key.js]
 [browser_webconsole_split_focus.js]
 [browser_webconsole_split_persist.js]
 [browser_webconsole_stacktrace_location_debugger_link.js]
 [browser_webconsole_stacktrace_location_scratchpad_link.js]
 [browser_webconsole_strict_mode_errors.js]
 [browser_webconsole_string.js]
 [browser_webconsole_time_methods.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_split_close_button.js
@@ -0,0 +1,55 @@
+/* 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,<p>Web Console test for close button of " +
+                 "split console";
+
+add_task(async function() {
+  let toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
+
+  info("Check the split console toolbar has a close button.");
+
+  let onSplitConsoleReady = toolbox.once("webconsole-ready");
+  toolbox.toggleSplitConsole();
+  await onSplitConsoleReady;
+
+  let closeButton = getCloseButton(toolbox);
+  ok(closeButton, "The split console has close button.");
+
+  info("Check we can reopen split console after closing split console by using " +
+       "the close button");
+
+  let onSplitConsoleChange = toolbox.once("split-console");
+  closeButton.click();
+  await onSplitConsoleChange;
+  ok(!toolbox.splitConsole, "The split console has been closed.");
+
+  onSplitConsoleChange = toolbox.once("split-console");
+  toolbox.toggleSplitConsole();
+  await onSplitConsoleChange;
+
+  ok(toolbox.splitConsole, "The split console has been displayed.");
+  closeButton = getCloseButton(toolbox);
+  ok(closeButton, "The split console has the close button after reopening.");
+
+  info("Check the close button is not displayed on console panel.");
+
+  await toolbox.selectTool("webconsole");
+  closeButton = getCloseButton(toolbox);
+  ok(!closeButton, "The console panel should not have the close button.");
+
+  info("The split console has the close button if back to the inspector.");
+
+  await toolbox.selectTool("inspector");
+  ok(toolbox.splitConsole, "The split console has been displayed with inspector.");
+  closeButton = getCloseButton(toolbox);
+  ok(closeButton, "The split console on the inspector has the close button.");
+});
+
+function getCloseButton(toolbox) {
+  let hud = toolbox.getPanel("webconsole").hud;
+  let doc = hud.ui.outputNode.ownerDocument;
+  return doc.getElementById("split-console-close-button");
+}