Bug 1226272 - Part 2: Implement saving and loading the tabs order preference. r=jdescottes
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Thu, 19 Apr 2018 18:46:55 +0900
changeset 468147 59a62840bad1551a12d7a6535829a9cec94bbde5
parent 468146 dd92dd2d5ae74177f2db6a6463940bfe776f996f
child 468148 a30819ceb73420975af5ded60b0cfff0184435e1
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1226272
milestone61.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 1226272 - Part 2: Implement saving and loading the tabs order preference. r=jdescottes MozReview-Commit-ID: JoVcnPwvVW7
devtools/client/framework/components/toolbox-tabs.js
devtools/client/framework/components/toolbox-toolbar.js
devtools/client/framework/toolbox-tabs-order-manager.js
devtools/client/framework/toolbox.js
devtools/client/preferences/devtools-client.js
--- a/devtools/client/framework/components/toolbox-tabs.js
+++ b/devtools/client/framework/components/toolbox-tabs.js
@@ -7,33 +7,34 @@ const { Component, createFactory } = req
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const {findDOMNode} = require("devtools/client/shared/vendor/react-dom");
 const {button, div} = dom;
 
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 const ToolboxTab = createFactory(require("devtools/client/framework/components/toolbox-tab"));
-const ToolboxTabsOrderManager = require("devtools/client/framework/toolbox-tabs-order-manager");
+const { ToolboxTabsOrderManager } = require("devtools/client/framework/toolbox-tabs-order-manager");
 
 // 26px is chevron devtools button width.(i.e. tools-chevronmenu)
 const CHEVRON_BUTTON_WIDTH = 26;
 
 class ToolboxTabs extends Component {
   // See toolbox-toolbar propTypes for details on the props used here.
   static get propTypes() {
     return {
       currentToolId: PropTypes.string,
       focusButton: PropTypes.func,
       focusedButton: PropTypes.string,
       highlightedTools: PropTypes.object,
       panelDefinitions: PropTypes.array,
       selectTool: PropTypes.func,
       toolbox: PropTypes.object,
       L10N: PropTypes.object,
+      onTabsOrderUpdated: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
       // Array of overflowed tool id.
@@ -43,17 +44,17 @@ class ToolboxTabs extends Component {
     // Map with tool Id and its width size. This lifecycle is out of React's
     // lifecycle. If a tool is registered, ToolboxTabs will add target tool id
     // to this map. ToolboxTabs will never remove tool id from this cache.
     this._cachedToolTabsWidthMap = new Map();
 
     this._resizeTimerId = null;
     this.resizeHandler = this.resizeHandler.bind(this);
 
-    this._tabsOrderManager = new ToolboxTabsOrderManager();
+    this._tabsOrderManager = new ToolboxTabsOrderManager(props.onTabsOrderUpdated);
   }
 
   componentDidMount() {
     window.addEventListener("resize", this.resizeHandler);
     this.updateCachedToolTabsWidthMap();
     this.updateOverflowedTabs();
   }
 
--- a/devtools/client/framework/components/toolbox-toolbar.js
+++ b/devtools/client/framework/components/toolbox-toolbar.js
@@ -67,16 +67,18 @@ class ToolboxToolbar extends Component {
       focusButton: PropTypes.func,
       // Hold off displaying the toolbar until enough information is ready for
       // it to render nicely.
       canRender: PropTypes.bool,
       // Localization interface.
       L10N: PropTypes.object,
       // The devtools toolbox
       toolbox: PropTypes.object,
+      // Call back function to detect tabs order updated.
+      onTabsOrderUpdated: PropTypes.func.isRequired,
     };
   }
 
   /**
    * The render function is kept fairly short for maintainability. See the individual
    * render functions for how each of the sections is rendered.
    */
   render() {
--- a/devtools/client/framework/toolbox-tabs-order-manager.js
+++ b/devtools/client/framework/toolbox-tabs-order-manager.js
@@ -1,39 +1,48 @@
 /* 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/. */
 
 "use strict";
 
+const Services = require("Services");
+const PREFERENCE_NAME = "devtools.toolbox.tabsOrder";
+
 /**
  * Manage the order of devtools tabs.
  */
 class ToolboxTabsOrderManager {
-  constructor() {
+  constructor(onOrderUpdated) {
+    this.onOrderUpdated = onOrderUpdated;
+
     this.onMouseDown = this.onMouseDown.bind(this);
     this.onMouseMove = this.onMouseMove.bind(this);
     this.onMouseOut = this.onMouseOut.bind(this);
     this.onMouseUp = this.onMouseUp.bind(this);
+
+    Services.prefs.addObserver(PREFERENCE_NAME, this.onOrderUpdated);
   }
 
   destroy() {
+    Services.prefs.removeObserver(PREFERENCE_NAME, this.onOrderUpdated);
     this.onMouseUp();
   }
 
   onMouseDown(e) {
     if (!e.target.classList.contains("devtools-tab")) {
       return;
     }
 
     this.dragStartX = e.pageX;
     this.dragTarget = e.target;
     this.previousPageX = e.pageX;
     this.toolboxContainerElement = this.dragTarget.closest("#toolbox-container");
     this.toolboxTabsElement = this.dragTarget.closest(".toolbox-tabs");
+    this.isOrderUpdated = false;
 
     this.dragTarget.ownerDocument.addEventListener("mousemove", this.onMouseMove);
     this.dragTarget.ownerDocument.addEventListener("mouseout", this.onMouseOut);
     this.dragTarget.ownerDocument.addEventListener("mouseup", this.onMouseUp);
 
     this.toolboxContainerElement.classList.add("tabs-reordering");
   }
 
@@ -60,16 +69,18 @@ class ToolboxTabsOrderManager {
         if (isDragTargetPreviousSibling) {
           tabsElement.insertBefore(this.dragTarget, tabElement.nextSibling);
         } else {
           tabsElement.insertBefore(this.dragTarget, tabElement);
         }
 
         const xAfter = this.dragTarget.offsetLeft;
         this.dragStartX += xAfter - xBefore;
+
+        this.isOrderUpdated = true;
         break;
       }
     }
 
     let distance = e.pageX - this.dragStartX;
 
     if ((!this.dragTarget.previousSibling && distance < 0) ||
         (!this.dragTarget.nextSibling && distance > 0)) {
@@ -92,21 +103,53 @@ class ToolboxTabsOrderManager {
   onMouseUp() {
     if (!this.dragTarget) {
       // The case in here has two type:
       // 1. Although destroy method was called, it was not during reordering.
       // 2. Although mouse event occur, destroy method was called during reordering.
       return;
     }
 
+    if (this.isOrderUpdated) {
+      const ids =
+        [...this.toolboxTabsElement.querySelectorAll(".devtools-tab")]
+          .map(tabElement => tabElement.dataset.id);
+      const pref = ids.join(",");
+      Services.prefs.setCharPref(PREFERENCE_NAME, pref);
+    }
+
     this.dragTarget.ownerDocument.removeEventListener("mousemove", this.onMouseMove);
     this.dragTarget.ownerDocument.removeEventListener("mouseout", this.onMouseOut);
     this.dragTarget.ownerDocument.removeEventListener("mouseup", this.onMouseUp);
 
     this.toolboxContainerElement.classList.remove("tabs-reordering");
     this.dragTarget.style.left = null;
     this.dragTarget = null;
     this.toolboxContainerElement = null;
     this.toolboxTabsElement = null;
   }
 }
 
-module.exports = ToolboxTabsOrderManager;
+function sortPanelDefinitions(definitions) {
+  const pref = Services.prefs.getCharPref(PREFERENCE_NAME, "");
+
+  if (!pref) {
+    definitions.sort(definition => {
+      return -1 * (definition.ordinal == undefined || definition.ordinal < 0
+        ? Number.MAX_VALUE
+        : definition.ordinal
+      );
+    });
+  }
+
+  const toolIds = pref.split(",");
+
+  return definitions.sort((a, b) => {
+    let orderA = toolIds.indexOf(a.id);
+    let orderB = toolIds.indexOf(b.id);
+    orderA = orderA < 0 ? Number.MAX_VALUE : orderA;
+    orderB = orderB < 0 ? Number.MAX_VALUE : orderB;
+    return orderA - orderB;
+  });
+}
+
+module.exports.ToolboxTabsOrderManager = ToolboxTabsOrderManager;
+module.exports.sortPanelDefinitions = sortPanelDefinitions;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -71,16 +71,18 @@ loader.lazyRequireGetter(this, "viewSour
 loader.lazyRequireGetter(this, "StyleSheetsFront",
   "devtools/shared/fronts/stylesheets", true);
 loader.lazyRequireGetter(this, "buildHarLog",
   "devtools/client/netmonitor/src/har/har-builder-utils", true);
 loader.lazyRequireGetter(this, "getKnownDeviceFront",
   "devtools/shared/fronts/device", true);
 loader.lazyRequireGetter(this, "NetMonitorAPI",
   "devtools/client/netmonitor/src/api", true);
+loader.lazyRequireGetter(this, "sortPanelDefinitions",
+  "devtools/client/framework/toolbox-tabs-order-manager", true);
 
 loader.lazyGetter(this, "domNodeConstants", () => {
   return require("devtools/shared/dom-node-constants");
 });
 
 loader.lazyGetter(this, "registerHarOverlay", () => {
   return require("devtools/client/netmonitor/src/har/toolbox-overlay").register;
 });
@@ -142,16 +144,17 @@ function Toolbox(target, selectedTool, h
   this._applyServiceWorkersTestingSettings =
     this._applyServiceWorkersTestingSettings.bind(this);
   this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
   this._onFocus = this._onFocus.bind(this);
   this._onBrowserMessage = this._onBrowserMessage.bind(this);
   this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
   this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
   this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
+  this._onTabsOrderUpdated = this._onTabsOrderUpdated.bind(this);
   this._onToolbarFocus = this._onToolbarFocus.bind(this);
   this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
   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);
@@ -258,23 +261,18 @@ Toolbox.prototype = {
     }
   },
 
   /**
    * Combines the built-in panel definitions and the additional tool definitions that
    * can be set by add-ons.
    */
   _combineAndSortPanelDefinitions() {
-    const definitions = [...this._panelDefinitions, ...this.getVisibleAdditionalTools()];
-    definitions.sort(definition => {
-      return -1 * (definition.ordinal == undefined || definition.ordinal < 0
-        ? MAX_ORDINAL
-        : definition.ordinal
-      );
-    });
+    let definitions = [...this._panelDefinitions, ...this.getVisibleAdditionalTools()];
+    definitions = sortPanelDefinitions(definitions);
     this.component.setPanelDefinitions(definitions);
   },
 
   lastUsedToolId: null,
 
   /**
    * Returns a *copy* of the _toolPanels collection.
    *
@@ -1145,17 +1143,18 @@ Toolbox.prototype = {
     const element = this.React.createElement(this.ToolboxController, {
       L10N,
       currentToolId: this.currentToolId,
       selectTool: this.selectTool,
       toggleSplitConsole: this.toggleSplitConsole,
       toggleNoAutohide: this.toggleNoAutohide,
       closeToolbox: this.destroy,
       focusButton: this._onToolbarFocus,
-      toolbox: this
+      toolbox: this,
+      onTabsOrderUpdated: this._onTabsOrderUpdated,
     });
 
     this.component = this.ReactDOM.render(element, this._componentMount);
   },
 
   /**
    * Reset tabindex attributes across all focusable elements inside the toolbar.
    * Only have one element with tabindex=0 at a time to make sure that tabbing
@@ -1911,16 +1910,20 @@ Toolbox.prototype = {
     if (originalTarget.nodeType !== 1 ||
         originalTarget.baseURI === webconsoleURL) {
       return;
     }
 
     this._lastFocusedElement = originalTarget;
   },
 
+  _onTabsOrderUpdated: function() {
+    this._combineAndSortPanelDefinitions();
+  },
+
   /**
    * Opens the split console.
    *
    * @returns {Promise} a promise that resolves once the tool has been
    *          loaded and focused.
    */
   openSplitConsole: function() {
     this._splitConsole = true;
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -24,16 +24,17 @@ pref("devtools.toolbox.footer.height", 2
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.previousHost", "side");
 pref("devtools.toolbox.selectedTool", "webconsole");
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 pref("devtools.toolbox.splitconsoleEnabled", false);
 pref("devtools.toolbox.splitconsoleHeight", 100);
+pref("devtools.toolbox.tabsOrder", "");
 
 // Toolbox Button preferences
 pref("devtools.command-button-pick.enabled", true);
 pref("devtools.command-button-frames.enabled", true);
 pref("devtools.command-button-splitconsole.enabled", true);
 pref("devtools.command-button-paintflashing.enabled", false);
 pref("devtools.command-button-scratchpad.enabled", false);
 pref("devtools.command-button-responsive.enabled", true);