Bug 1908441 - Implement drag and drop of tab group into a new or other window. r=sthompson,tabbrowser-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D241004
This commit is contained in:
Dão Gottwald
2025-03-11 21:39:05 +00:00
parent a9568f4c21
commit 1642f3dfa4
3 changed files with 65 additions and 59 deletions

View File

@@ -33,21 +33,6 @@
const DIRECTION_FORWARD = 1;
const DIRECTION_BACKWARD = -1;
/**
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} element
* @returns {boolean}
* `true` if element is a `<tab>`
*/
const isTab = element => !!(element?.tagName == "tab");
/**
* @param {MozTabbrowserTab|MozTextLabel} element
* @returns {boolean}
* `true` if element is the `<label>` in a `<tab-group>`
*/
const isTabGroupLabel = element =>
!!element?.classList?.contains("tab-group-label");
/**
* Updates the User Context UI indicators if the browser is in a non-default context
*/
@@ -5690,6 +5675,10 @@
* in the current window, in which case this will do nothing.
*/
replaceTabsWithWindow(contextTab, aOptions = {}) {
if (this.isTabGroupLabel(contextTab)) {
return this.replaceTabWithWindow(contextTab, aOptions);
}
let tabs;
if (contextTab.multiselected) {
tabs = this.selectedTabs;
@@ -5796,6 +5785,24 @@
return newWindow;
}
/**
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} element
* @returns {boolean}
* `true` if element is a `<tab>`
*/
isTab(element) {
return !!(element?.tagName == "tab");
}
/**
* @param {MozTabbrowserTab|MozTextLabel} element
* @returns {boolean}
* `true` if element is the `<label>` in a `<tab-group>`
*/
isTabGroupLabel(element) {
return !!element?.classList?.contains("tab-group-label");
}
_updateTabsAfterInsert() {
for (let i = 0; i < this.tabs.length; i++) {
this.tabs[i]._tPos = i;
@@ -5833,7 +5840,7 @@
* a tab group, since pinned tabs are presently not allowed in tab groups.
*/
moveTabTo(aTab, aIndex, { forceUngrouped = false } = {}) {
if (isTab(aTab)) {
if (this.isTab(aTab)) {
// Don't allow mixing pinned and unpinned tabs.
if (aTab.pinned) {
aIndex = Math.min(aIndex, this.pinnedTabCount - 1);
@@ -5845,7 +5852,7 @@
}
} else {
forceUngrouped = true;
if (isTabGroupLabel(aTab)) {
if (this.isTabGroupLabel(aTab)) {
aTab = aTab.group;
}
}
@@ -5903,7 +5910,7 @@
* @param {boolean} moveBefore
*/
#moveTabNextTo(tab, targetElement, moveBefore = false) {
if (isTabGroupLabel(tab)) {
if (this.isTabGroupLabel(tab)) {
tab = tab.group;
if (targetElement?.group) {
targetElement = targetElement.group;
@@ -5961,7 +5968,7 @@
*/
#handleTabMove(aTab, moveActionCallback) {
let wasFocused = document.activeElement == this.selectedTab;
let oldPosition = isTab(aTab) && aTab.elementIndex;
let oldPosition = this.isTab(aTab) && aTab.elementIndex;
moveActionCallback();
@@ -5986,7 +5993,7 @@
}
// Pinning/unpinning vertical tabs, and moving tabs into tab groups, both bypass moveTabTo.
// We still want to check whether its worth dispatching an event.
if (isTab(aTab) && oldPosition != aTab.elementIndex) {
if (this.isTab(aTab) && oldPosition != aTab.elementIndex) {
let evt = document.createEvent("UIEvents");
evt.initUIEvent("TabMove", true, false, window, oldPosition);
aTab.dispatchEvent(evt);

View File

@@ -14,12 +14,8 @@
const DIRECTION_BACKWARD = -1;
const DIRECTION_FORWARD = 1;
/**
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} element
* @returns {boolean}
* `true` if element is a `<tab>`
*/
const isTab = element => !!(element?.tagName == "tab");
const isTab = element => gBrowser.isTab(element);
const isTabGroupLabel = element => gBrowser.isTabGroupLabel(element);
/**
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} element
@@ -28,14 +24,6 @@
*/
const isTabGroup = element => !!(element?.tagName == "tab-group");
/**
* @param {MozTabbrowserTab|MozTextLabel} element
* @returns {boolean}
* `true` if element is the `<label>` in a `<tab-group>`
*/
const isTabGroupLabel = element =>
!!element?.classList?.contains("tab-group-label");
class MozTabbrowserTabs extends MozElements.TabsBase {
static observedAttributes = ["orient"];
@@ -757,7 +745,10 @@
let expandedTabGroups;
if (tab.multiselected) {
this.#moveTogetherSelectedTabs(tab);
} else if (isTabGroupLabel(tab)) {
} else if (
isTabGroupLabel(tab) &&
gBrowser.visibleTabs.length > tab.group.tabs.length
) {
expandedTabGroups = gBrowser.tabGroups.filter(
group => !group.collapsed
);
@@ -1010,6 +1001,16 @@
: "translateX(" + Math.round(newMargin) + "px)";
}
#expandGroupsAfterDragDrop(dragData) {
if (dragData?.expandedTabGroups?.length) {
for (let group of dragData.expandedTabGroups) {
group.collapsed = false;
}
this.#keepTabSizeLocked = false;
this._unlockTabSizing();
}
}
// eslint-disable-next-line complexity
on_drop(event) {
var dt = event.dataTransfer;
@@ -1191,6 +1192,8 @@
moveTabs();
}
}
} else if (isTabGroupLabel(draggedTab)) {
gBrowser.adoptTabGroup(draggedTab.group, this._getDropIndex(event));
} else if (draggedTab) {
// Move the tabs into this window. To avoid multiple tab-switches in
// the original window, the selected tab should be adopted last.
@@ -1282,13 +1285,7 @@
}
if (draggedTab) {
if (draggedTab._dragData.expandedTabGroups?.length) {
for (let group of draggedTab._dragData.expandedTabGroups) {
group.collapsed = false;
}
this.#keepTabSizeLocked = false;
this._unlockTabSizing();
}
this.#expandGroupsAfterDragDrop(draggedTab._dragData);
delete draggedTab._dragData;
}
}
@@ -1306,6 +1303,7 @@
this._finishMoveTogetherSelectedTabs(draggedTab);
this._finishAnimateTabMove();
this.#expandGroupsAfterDragDrop(draggedTab._dragData);
if (
dt.mozUserCancelled ||