diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index 8cf0da126783..607fc5831cf1 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -216,11 +216,16 @@ panelview[mainview] > .panel-header {
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
}
+.tabbrowser-tab[tab-grouping],
.tabbrowser-tab[tabdrop-samewindow],
#tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]):not([multiselected]) {
transition: transform 200ms var(--animation-easing-function);
}
+.tabbrowser-tab[tab-grouping][multiselected]:not([selected]) {
+ z-index: 2;
+}
+
/* The next 3 rules allow dragging tabs slightly outside of the tabstrip
* to make it easier to drag tabs. */
#TabsToolbar[movingtab] {
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index 92901e6c7c09..7d0df6f54faf 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -586,6 +586,7 @@
+
+
+
+ -1; i--) {
+ let movingTab = selectedTabs[i];
+ insertAtPos = newIndex(movingTab, insertAtPos);
+
+ if (animate) {
+ movingTab.groupingTabsData = {};
+ addAnimationData(movingTab, insertAtPos, "left");
+ } else {
+ gBrowser.moveTabTo(movingTab, insertAtPos);
+ }
+ insertAtPos--;
+ }
+
+ // Animate right selected tabs
+
+ insertAtPos = draggedTabPos + 1;
+ for (let i = selectedTabs.indexOf(tab) + 1; i < selectedTabs.length; i++) {
+ let movingTab = selectedTabs[i];
+ insertAtPos = newIndex(movingTab, insertAtPos);
+
+ if (animate) {
+ movingTab.groupingTabsData = {};
+ addAnimationData(movingTab, insertAtPos, "right");
+ } else {
+ gBrowser.moveTabTo(movingTab, insertAtPos);
+ }
+ insertAtPos++;
+ }
+
+ // Slide the relevant tabs to their new position.
+ let rtl = Services.locale.isAppLocaleRTL ? -1 : 1;
+ for (let t of this._getVisibleTabs()) {
+ if (t.groupingTabsData && t.groupingTabsData.translateX) {
+ let translateX = rtl * t.groupingTabsData.translateX;
+ t.style.transform = "translateX(" + translateX + "px)";
+ }
+ }
+
+ function newIndex(aTab, index) {
+ // Don't allow mixing pinned and unpinned tabs.
+ if (aTab.pinned) {
+ return Math.min(index, gBrowser._numPinnedTabs - 1);
+ }
+ return Math.max(index, gBrowser._numPinnedTabs);
+ }
+
+ function addAnimationData(movingTab, movingTabNewIndex, side) {
+ let movingTabOldIndex = movingTab._tPos;
+
+ if (movingTabOldIndex == movingTabNewIndex) {
+ // movingTab is already at the right position
+ // and thus don't need to be animated.
+ return;
+ }
+
+ let movingTabWidth = movingTab.boxObject.width;
+ let shift = (movingTabNewIndex - movingTabOldIndex) * movingTabWidth;
+
+ movingTab.groupingTabsData.animate = true;
+ movingTab.setAttribute("tab-grouping", "true");
+
+ movingTab.groupingTabsData.translateX = shift;
+
+ let onTransitionEnd = transitionendEvent => {
+ if (transitionendEvent.propertyName != "transform" ||
+ transitionendEvent.originalTarget != movingTab) {
+ return;
+ }
+ movingTab.removeEventListener("transitionend", onTransitionEnd);
+ movingTab.groupingTabsData.newIndex = movingTabNewIndex;
+ movingTab.groupingTabsData.animate = false;
+ };
+
+ movingTab.addEventListener("transitionend", onTransitionEnd);
+
+ // Add animation data for tabs between movingTab (selected
+ // tab moving towards the dragged tab) and draggedTab.
+ // Those tabs in the middle should move in
+ // the opposite direction of movingTab.
+
+ let lowerIndex = Math.min(movingTabOldIndex, draggedTabPos);
+ let higherIndex = Math.max(movingTabOldIndex, draggedTabPos);
+
+ for (let i = lowerIndex + 1; i < higherIndex; i++) {
+ let middleTab = gBrowser.visibleTabs[i];
+
+ if (middleTab.pinned != movingTab.pinned) {
+ // Don't mix pinned and unpinned tabs
+ break;
+ }
+
+ if (middleTab.multiselected) {
+ // Skip because this selected tab should
+ // be shifted towards the dragged Tab.
+ continue;
+ }
+
+ if (!middleTab.groupingTabsData || !middleTab.groupingTabsData.translateX) {
+ middleTab.groupingTabsData = { translateX: 0};
+ }
+ if (side == "left") {
+ middleTab.groupingTabsData.translateX -= movingTabWidth;
+ } else {
+ middleTab.groupingTabsData.translateX += movingTabWidth;
+ }
+
+ middleTab.setAttribute("tab-grouping", "true");
+ }
+ }
+ ]]>
+
+
+
+
+ -1; i--) {
+ let movingTab = selectedTabs[i];
+ if (movingTab.groupingTabsData.newIndex) {
+ gBrowser.moveTabTo(movingTab, movingTab.groupingTabsData.newIndex);
+ }
+ }
+
+ // Moving right tabs
+ for (let i = tabIndex + 1; i < selectedTabs.length; i++) {
+ let movingTab = selectedTabs[i];
+ if (movingTab.groupingTabsData.newIndex) {
+ gBrowser.moveTabTo(movingTab, movingTab.groupingTabsData.newIndex);
+ }
+ }
+
+ for (let t of this._getVisibleTabs()) {
+ t.style.transform = "";
+ t.removeAttribute("tab-grouping");
+ delete t.groupingTabsData;
+ }
+ ]]>
+
+
+
+
+
+
-1; i--) {
- let movingTab = selectedTabs[i];
- gBrowser.moveTabTo(movingTab, insertAtPos);
- insertAtPos--;
- }
-
- // Move right selected tabs
- insertAtPos = draggedTabPos + 1;
- for (let i = selectedTabs.indexOf(tab) + 1; i < selectedTabs.length; i++) {
- let movingTab = selectedTabs[i];
- gBrowser.moveTabTo(movingTab, insertAtPos);
- insertAtPos++;
- }
+ this._groupSelectedTabs(tab);
}
// Create a canvas to which we capture the current tab.
@@ -1424,11 +1581,21 @@
arrowScrollbox.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll, true);
}
- if (effects == "move" &&
- this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
+ let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if ((effects == "move" || effects == "copy") &&
+ this == draggedTab.parentNode) {
ind.collapsed = true;
- this._animateTabMove(event);
- return;
+
+ if (!this._isGroupTabsAnimationOver()) {
+ // Wait for grouping tabs animation to finish
+ return;
+ }
+ this._finishGroupSelectedTabs(draggedTab);
+
+ if (effects == "move") {
+ this._animateTabMove(event);
+ return;
+ }
}
this._finishAnimateTabMove();
@@ -1496,6 +1663,7 @@
if (!draggedTab)
return;
movingTabs = draggedTab._dragData.movingTabs;
+ draggedTab.parentNode._finishGroupSelectedTabs(draggedTab);
}
this._tabDropIndicator.collapsed = true;
@@ -1636,6 +1804,7 @@
if (draggedTab.hasAttribute("tabdrop-samewindow"))
return;
+ this._finishGroupSelectedTabs(draggedTab);
this._finishAnimateTabMove();
if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
diff --git a/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js b/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js
index 59c360bcd73b..eed35aee0bb3 100644
--- a/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js
@@ -2,10 +2,14 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
+const PREF_ANIMATION = "toolkit.cosmeticAnimations.enabled";
add_task(async function setPref() {
await SpecialPowers.pushPrefEnv({
- set: [[PREF_MULTISELECT_TABS, true]],
+ set: [
+ [PREF_MULTISELECT_TABS, true],
+ [PREF_ANIMATION, false],
+ ],
});
});