forked from mirrors/gecko-dev
		
	 7f9229d43d
			
		
	
	
		7f9229d43d
		
	
	
	
	
		
			
			This will help transition to ES Modules as this symbol is exposed to them.
$ sed -ie "/require(.Services.)/d" $(git grep -l 'require("Services")' devtools/)
$ sed -ie "/loader.lazyRequireGetter(this, .Services./d" $(git grep -l 'loader.lazyRequireGetter(this, "Services"' devtools/)
+ the edition of builtin-modules.js + eslintrc.js
+ manual eslint fixes
+ removal of devtools-services in the debugger, except for jest tests
Differential Revision: https://phabricator.services.mozilla.com/D156401
		
	
			
		
			
				
	
	
		
			284 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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 { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 | |
| const { gDevTools } = require("devtools/client/framework/devtools");
 | |
| const Telemetry = require("devtools/client/shared/telemetry");
 | |
| const TABS_REORDERED_SCALAR = "devtools.toolbox.tabs_reordered";
 | |
| const PREFERENCE_NAME = "devtools.toolbox.tabsOrder";
 | |
| 
 | |
| /**
 | |
|  * Manage the order of devtools tabs.
 | |
|  */
 | |
| class ToolboxTabsOrderManager {
 | |
|   constructor(toolbox, onOrderUpdated, panelDefinitions) {
 | |
|     this.toolbox = toolbox;
 | |
|     this.onOrderUpdated = onOrderUpdated;
 | |
|     this.currentPanelDefinitions = panelDefinitions || [];
 | |
| 
 | |
|     this.onMouseDown = this.onMouseDown.bind(this);
 | |
|     this.onMouseMove = this.onMouseMove.bind(this);
 | |
|     this.onMouseUp = this.onMouseUp.bind(this);
 | |
| 
 | |
|     Services.prefs.addObserver(PREFERENCE_NAME, this.onOrderUpdated);
 | |
| 
 | |
|     this.telemetry = new Telemetry();
 | |
|   }
 | |
| 
 | |
|   async destroy() {
 | |
|     Services.prefs.removeObserver(PREFERENCE_NAME, this.onOrderUpdated);
 | |
| 
 | |
|     // Call mouseUp() to clear the state to prepare for in case a dragging was in progress
 | |
|     // when the destroy() was called.
 | |
|     await this.onMouseUp();
 | |
|   }
 | |
| 
 | |
|   insertBefore(target) {
 | |
|     const xBefore = this.dragTarget.offsetLeft;
 | |
|     this.toolboxTabsElement.insertBefore(this.dragTarget, target);
 | |
|     const xAfter = this.dragTarget.offsetLeft;
 | |
|     this.dragStartX += xAfter - xBefore;
 | |
|     this.isOrderUpdated = true;
 | |
|   }
 | |
| 
 | |
|   isFirstTab(tabElement) {
 | |
|     return !tabElement.previousSibling;
 | |
|   }
 | |
| 
 | |
|   isLastTab(tabElement) {
 | |
|     return (
 | |
|       !tabElement.nextSibling ||
 | |
|       tabElement.nextSibling.id === "tools-chevron-menu-button"
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   isRTL() {
 | |
|     return this.toolbox.direction === "rtl";
 | |
|   }
 | |
| 
 | |
|   async saveOrderPreference() {
 | |
|     const tabs = [...this.toolboxTabsElement.querySelectorAll(".devtools-tab")];
 | |
|     const tabIds = tabs.map(tab => tab.dataset.extensionId || tab.dataset.id);
 | |
|     // Concat the overflowed tabs id since they are not contained in visible tabs.
 | |
|     // The overflowed tabs cannot be reordered so we just append the id from current
 | |
|     // panel definitions on their order.
 | |
|     const overflowedTabIds = this.currentPanelDefinitions
 | |
|       .filter(definition => !tabs.some(tab => tab.dataset.id === definition.id))
 | |
|       .map(definition => definition.extensionId || definition.id);
 | |
|     const currentTabIds = tabIds.concat(overflowedTabIds);
 | |
|     const dragTargetId =
 | |
|       this.dragTarget.dataset.extensionId || this.dragTarget.dataset.id;
 | |
|     const prefIds = getTabsOrderFromPreference();
 | |
|     const absoluteIds = toAbsoluteOrder(prefIds, currentTabIds, dragTargetId);
 | |
| 
 | |
|     // Remove panel id which is not in panel definitions and addons list.
 | |
|     const extensions = await AddonManager.getAllAddons();
 | |
|     const definitions = gDevTools.getToolDefinitionArray();
 | |
|     const result = absoluteIds.filter(
 | |
|       id =>
 | |
|         definitions.find(d => id === (d.extensionId || d.id)) ||
 | |
|         extensions.find(e => id === e.id)
 | |
|     );
 | |
| 
 | |
|     Services.prefs.setCharPref(PREFERENCE_NAME, result.join(","));
 | |
|   }
 | |
| 
 | |
|   setCurrentPanelDefinitions(currentPanelDefinitions) {
 | |
|     this.currentPanelDefinitions = currentPanelDefinitions;
 | |
|   }
 | |
| 
 | |
|   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.eventTarget = this.dragTarget.ownerGlobal.top;
 | |
| 
 | |
|     this.eventTarget.addEventListener("mousemove", this.onMouseMove);
 | |
|     this.eventTarget.addEventListener("mouseup", this.onMouseUp);
 | |
| 
 | |
|     this.toolboxContainerElement.classList.add("tabs-reordering");
 | |
|   }
 | |
| 
 | |
|   onMouseMove(e) {
 | |
|     const diffPageX = e.pageX - this.previousPageX;
 | |
|     let dragTargetCenterX =
 | |
|       this.dragTarget.offsetLeft + diffPageX + this.dragTarget.clientWidth / 2;
 | |
|     let isDragTargetPreviousSibling = false;
 | |
| 
 | |
|     const tabElements = this.toolboxTabsElement.querySelectorAll(
 | |
|       ".devtools-tab"
 | |
|     );
 | |
| 
 | |
|     // Calculate the minimum and maximum X-offset that can be valid for the drag target.
 | |
|     const firstElement = tabElements[0];
 | |
|     const firstElementCenterX =
 | |
|       firstElement.offsetLeft + firstElement.clientWidth / 2;
 | |
|     const lastElement = tabElements[tabElements.length - 1];
 | |
|     const lastElementCenterX =
 | |
|       lastElement.offsetLeft + lastElement.clientWidth / 2;
 | |
|     const max = Math.max(firstElementCenterX, lastElementCenterX);
 | |
|     const min = Math.min(firstElementCenterX, lastElementCenterX);
 | |
| 
 | |
|     // Normalize the target center X so to remain between the first and last tab.
 | |
|     dragTargetCenterX = Math.min(max, dragTargetCenterX);
 | |
|     dragTargetCenterX = Math.max(min, dragTargetCenterX);
 | |
| 
 | |
|     for (const tabElement of tabElements) {
 | |
|       if (tabElement === this.dragTarget) {
 | |
|         isDragTargetPreviousSibling = true;
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // Is the dragTarget near the center of the other tab?
 | |
|       const anotherCenterX = tabElement.offsetLeft + tabElement.clientWidth / 2;
 | |
|       const distanceWithDragTarget = Math.abs(
 | |
|         dragTargetCenterX - anotherCenterX
 | |
|       );
 | |
|       const isReplaceable = distanceWithDragTarget < tabElement.clientWidth / 3;
 | |
| 
 | |
|       if (isReplaceable) {
 | |
|         const replaceableElement = isDragTargetPreviousSibling
 | |
|           ? tabElement.nextSibling
 | |
|           : tabElement;
 | |
|         this.insertBefore(replaceableElement);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let distance = e.pageX - this.dragStartX;
 | |
| 
 | |
|     // To accomodate for RTL locales, we cannot rely on the first/last element of the
 | |
|     // NodeList. We cannot have negative distances for the leftmost tab, and we cannot
 | |
|     // have positive distances for the rightmost tab.
 | |
|     const isFirstTab = this.isFirstTab(this.dragTarget);
 | |
|     const isLastTab = this.isLastTab(this.dragTarget);
 | |
|     const isLeftmostTab = this.isRTL() ? isLastTab : isFirstTab;
 | |
|     const isRightmostTab = this.isRTL() ? isFirstTab : isLastTab;
 | |
| 
 | |
|     if ((isLeftmostTab && distance < 0) || (isRightmostTab && distance > 0)) {
 | |
|       // If the drag target is already edge of the tabs and the mouse will make the
 | |
|       // element to move to same direction more, keep the position.
 | |
|       distance = 0;
 | |
|     }
 | |
| 
 | |
|     this.dragTarget.style.left = `${distance}px`;
 | |
|     this.previousPageX = e.pageX;
 | |
|   }
 | |
| 
 | |
|   async 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) {
 | |
|       await this.saveOrderPreference();
 | |
| 
 | |
|       // Log which tabs reordered. The question we want to answer is:
 | |
|       // "How frequently are the tabs re-ordered, also which tabs get re-ordered?"
 | |
|       const toolId =
 | |
|         this.dragTarget.dataset.extensionId || this.dragTarget.dataset.id;
 | |
|       this.telemetry.keyedScalarAdd(TABS_REORDERED_SCALAR, toolId, 1);
 | |
|     }
 | |
| 
 | |
|     this.eventTarget.removeEventListener("mousemove", this.onMouseMove);
 | |
|     this.eventTarget.removeEventListener("mouseup", this.onMouseUp);
 | |
| 
 | |
|     this.toolboxContainerElement.classList.remove("tabs-reordering");
 | |
|     this.dragTarget.style.left = null;
 | |
|     this.dragTarget = null;
 | |
|     this.toolboxContainerElement = null;
 | |
|     this.toolboxTabsElement = null;
 | |
|     this.eventTarget = null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| function getTabsOrderFromPreference() {
 | |
|   const pref = Services.prefs.getCharPref(PREFERENCE_NAME, "");
 | |
|   return pref ? pref.split(",") : [];
 | |
| }
 | |
| 
 | |
| function sortPanelDefinitions(definitions) {
 | |
|   const toolIds = getTabsOrderFromPreference();
 | |
| 
 | |
|   return definitions.sort((a, b) => {
 | |
|     let orderA = toolIds.indexOf(a.extensionId || a.id);
 | |
|     let orderB = toolIds.indexOf(b.extensionId || b.id);
 | |
|     orderA = orderA < 0 ? Number.MAX_VALUE : orderA;
 | |
|     orderB = orderB < 0 ? Number.MAX_VALUE : orderB;
 | |
|     return orderA - orderB;
 | |
|   });
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * This function returns absolute tab ids that were merged the both ids that are in
 | |
|  * preference and tabs.
 | |
|  * Some tabs added with add-ons etc show/hide depending on conditions.
 | |
|  * However, all of tabs that include hidden tab always keep the relationship with
 | |
|  * left side tab, except in case the left tab was target of dragging. If the left
 | |
|  * tab has been moved, it keeps its relationship with the tab next to it.
 | |
|  *
 | |
|  * Case 1: Drag a tab to left
 | |
|  *   currentTabIds: [T1, T2, T3, T4, T5]
 | |
|  *   prefIds      : [T1, T2, T3, E1(hidden), T4, T5]
 | |
|  *   drag T4      : [T1, T2, T4, T3, T5]
 | |
|  *   result       : [T1, T2, T4, T3, E1, T5]
 | |
|  *
 | |
|  * Case 2: Drag a tab to right
 | |
|  *   currentTabIds: [T1, T2, T3, T4, T5]
 | |
|  *   prefIds      : [T1, T2, T3, E1(hidden), T4, T5]
 | |
|  *   drag T2      : [T1, T3, T4, T2, T5]
 | |
|  *   result       : [T1, T3, E1, T4, T2, T5]
 | |
|  *
 | |
|  * Case 3: Hidden tab was left end and drag a tab to left end
 | |
|  *   currentTabIds: [T1, T2, T3, T4, T5]
 | |
|  *   prefIds      : [E1(hidden), T1, T2, T3, T4, T5]
 | |
|  *   drag T4      : [T4, T1, T2, T3, T5]
 | |
|  *   result       : [E1, T4, T1, T2, T3, T5]
 | |
|  *
 | |
|  * Case 4: Hidden tab was right end and drag a tab to right end
 | |
|  *   currentTabIds: [T1, T2, T3, T4, T5]
 | |
|  *   prefIds      : [T1, T2, T3, T4, T5, E1(hidden)]
 | |
|  *   drag T1      : [T2, T3, T4, T5, T1]
 | |
|  *   result       : [T2, T3, T4, T5, E1, T1]
 | |
|  *
 | |
|  * @param Array - prefIds: id array of preference
 | |
|  * @param Array - currentTabIds: id array of appearanced tabs
 | |
|  * @param String - dragTargetId: id of dragged target
 | |
|  * @return Array
 | |
|  */
 | |
| function toAbsoluteOrder(prefIds, currentTabIds, dragTargetId) {
 | |
|   currentTabIds = [...currentTabIds];
 | |
|   let indexAtCurrentTabs = 0;
 | |
| 
 | |
|   for (const prefId of prefIds) {
 | |
|     if (prefId === dragTargetId) {
 | |
|       // do nothing
 | |
|     } else if (currentTabIds.includes(prefId)) {
 | |
|       indexAtCurrentTabs = currentTabIds.indexOf(prefId) + 1;
 | |
|     } else {
 | |
|       currentTabIds.splice(indexAtCurrentTabs, 0, prefId);
 | |
|       indexAtCurrentTabs += 1;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return currentTabIds;
 | |
| }
 | |
| 
 | |
| module.exports.ToolboxTabsOrderManager = ToolboxTabsOrderManager;
 | |
| module.exports.sortPanelDefinitions = sortPanelDefinitions;
 | |
| module.exports.toAbsoluteOrder = toAbsoluteOrder;
 |