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], + ], }); });