Bug 1226272 - Part 1: Make devtools tab draggable and reorderable. r?
MozReview-Commit-ID: 3fdHXX8Tljn
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -22,14 +22,15 @@ DevToolsModules(
'sidebar.js',
'source-map-url-service.js',
'target-from-url.js',
'target.js',
'toolbox-highlighter-utils.js',
'toolbox-host-manager.js',
'toolbox-hosts.js',
'toolbox-options.js',
+ 'toolbox-tabs-order-manager.js',
'toolbox.js',
'ToolboxProcess.jsm',
)
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Developer Tools: Framework')
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/toolbox-tabs-order-manager.js
@@ -0,0 +1,101 @@
+/* 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";
+
+/**
+ * Manage the order of devtools tabs.
+ */
+class ToolboxTabsOrderManager {
+ constructor(tabsElement) {
+ this.tabsElement = tabsElement;
+
+ this.onMouseDown = this.onMouseDown.bind(this);
+ this.onMouseMove = this.onMouseMove.bind(this);
+ this.onMouseOut = this.onMouseOut.bind(this);
+ this.onMouseUp = this.onMouseUp.bind(this);
+
+ let order = 0;
+
+ for (const tabElement of this.tabsElement.childNodes) {
+ tabElement.style.order = order;
+ order++;
+ }
+
+ this.tabsElement.addEventListener("mousedown", this.onMouseDown);
+ }
+
+ destroy() {
+ this.tabsElement.removeEventListener("mousedown", this.onMouseDown);
+ this.tabsElement = null;
+ }
+
+ onMouseDown(e) {
+ if (!e.target.classList.contains("devtools-tab")) {
+ return;
+ }
+
+ this.dragStartX = e.pageX;
+ this.dragTarget = e.target;
+ this.maxOrder = -Number.MAX_VALUE;
+ this.minOrder = Number.MAX_VALUE;
+
+ for (const tabElement of this.tabsElement.childNodes) {
+ const order = parseInt(tabElement.style.order, 10);
+ this.maxOrder = Math.max(this.maxOrder, order);
+ this.minOrder = Math.min(this.minOrder, order);
+ }
+
+ this.tabsElement.ownerDocument.addEventListener("mousemove", this.onMouseMove);
+ this.tabsElement.ownerDocument.addEventListener("mouseout", this.onMouseOut);
+ this.tabsElement.ownerDocument.addEventListener("mouseup", this.onMouseUp);
+ }
+
+ onMouseMove(e) {
+ for (const tabElement of this.tabsElement.childNodes) {
+ if (tabElement === this.dragTarget) {
+ continue;
+ }
+
+ const bounds = tabElement.getBoundingClientRect();
+ const sx = bounds.left + bounds.width * 0.2;
+
+ if (sx < e.pageX && e.pageX < sx + bounds.width * 0.6) {
+ const order = this.dragTarget.style.order;
+ this.dragTarget.style.order = tabElement.style.order;
+ tabElement.style.order = order;
+ this.dragStartX += tabElement.style.order < this.dragTarget.style.order
+ ? tabElement.clientWidth : -tabElement.clientWidth;
+ break;
+ }
+ }
+
+ let distance = e.pageX - this.dragStartX;
+
+ if ((this.dragTarget.style.order == this.minOrder && distance < 0) ||
+ (this.dragTarget.style.order == this.maxOrder && distance > 0)) {
+ distance = 0;
+ }
+
+ this.dragTarget.style.left = `${ distance }px`;
+ }
+
+ onMouseOut(e) {
+ if (e.pageX <= 0 || this.tabsElement.ownerDocument.width <= e.pageX ||
+ e.pageY <= 0 || this.tabsElement.ownerDocument.height <= e.pageY) {
+ this.onMouseUp();
+ }
+ }
+
+ onMouseUp() {
+ this.dragTarget.style.left = "unset";
+ this.dragTarget = null;
+
+ this.tabsElement.ownerDocument.removeEventListener("mousemove", this.onMouseMove);
+ this.tabsElement.ownerDocument.removeEventListener("mouseout", this.onMouseOut);
+ this.tabsElement.ownerDocument.removeEventListener("mouseup", this.onMouseUp);
+ }
+}
+
+exports.ToolboxTabsOrderManager = ToolboxTabsOrderManager;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -68,16 +68,18 @@ loader.lazyRequireGetter(this, "HUDServi
loader.lazyRequireGetter(this, "viewSource",
"devtools/client/shared/view-source");
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, "ToolboxTabsOrderManager",
+ "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;
});
@@ -557,16 +559,19 @@ Toolbox.prototype = {
// If in testing environment, wait for performance connection to finish,
// so we don't have to explicitly wait for this in tests; ideally, all tests
// will handle this on their own, but each have their own tear down function.
if (flags.testing) {
await performanceFrontConnection;
}
+ this._tabsOrderManager =
+ new ToolboxTabsOrderManager(this.doc.querySelector(".toolbox-tabs"));
+
this.emit("ready");
this._isOpenDeferred.resolve();
}.bind(this))().catch(console.error);
},
/**
* loading React modules when needed (to avoid performance penalties
* during Firefox start up time).
@@ -2662,16 +2667,20 @@ Toolbox.prototype = {
this._updateTextBoxMenuItems, true);
this.textBoxContextMenuPopup = null;
}
if (this._componentMount) {
this._componentMount.removeEventListener("keypress", this._onToolbarArrowKeypress);
this.ReactDOM.unmountComponentAtNode(this._componentMount);
this._componentMount = null;
}
+ if (this._tabsOrderManager) {
+ this._tabsOrderManager.destroy();
+ this._tabsOrderManager = null;
+ }
let outstanding = [];
for (let [id, panel] of this._toolPanels) {
try {
gDevTools.emit(id + "-destroy", this, panel);
this.emit(id + "-destroy", panel);
outstanding.push(panel.destroy());