--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -211,24 +211,25 @@ panelview[mainview] > .panel-header {
}
%endif
#tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
position: fixed !important;
display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
}
-#tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
+#tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected],
+#tabbrowser-tabs[movingtab] > .tabbrowser-tab[multiselected] {
position: relative;
z-index: 2;
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
}
.tabbrowser-tab[tabdrop-samewindow],
-#tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
+#tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]):not([multiselected]) {
transition: transform 200ms var(--animation-easing-function);
}
/* The next 3 rules allow dragging tabs slightly outside of the tabstrip
* to make it easier to drag tabs. */
#TabsToolbar[movingtab] {
padding-bottom: 15px;
}
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -144,16 +144,18 @@ window._gBrowser = {
_removingTabs: [],
_multiSelectedTabsSet: new WeakSet(),
_lastMultiSelectedTabRef: null,
_clearMultiSelectionLocked: false,
+ _clearMultiSelectionLockedOnce: false,
+
/**
* Tab close requests are ignored if the window is closing anyway,
* e.g. when holding Ctrl+W.
*/
_windowIsClosing: false,
/**
* This defines a proxy which allows us to access browsers by
@@ -3689,16 +3691,20 @@ window._gBrowser = {
}
aTab.removeAttribute("multiselected");
this.tabContainer._setPositionalAttributes();
this._multiSelectedTabsSet.delete(aTab);
},
clearMultiSelectedTabs(updatePositionalAttributes) {
if (this._clearMultiSelectionLocked) {
+ if (this._clearMultiSelectionLockedOnce) {
+ this._clearMultiSelectionLockedOnce = false;
+ this._clearMultiSelectionLocked = false;
+ }
return;
}
let selectedTabs = this.selectedTabs;
if (selectedTabs.length < 2) {
return;
}
@@ -3707,16 +3713,21 @@ window._gBrowser = {
}
this._multiSelectedTabsSet = new WeakSet();
this._lastMultiSelectedTabRef = null;
if (updatePositionalAttributes) {
this.tabContainer._setPositionalAttributes();
}
},
+ lockClearMultiSelectionOnce() {
+ this._clearMultiSelectionLockedOnce = true;
+ this._clearMultiSelectionLocked = true;
+ },
+
/**
* Remove the active tab from the multiselection if it's the only one left there.
*/
updateActiveTabMultiSelectState() {
if (this.selectedTabs.length == 1) {
this.clearMultiSelectedTabs();
}
},
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -552,74 +552,93 @@
<method name="_animateTabMove">
<parameter name="event"/>
<body><![CDATA[
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
if (this.getAttribute("movingtab") != "true") {
this.setAttribute("movingtab", "true");
this.parentNode.setAttribute("movingtab", "true");
- this.selectedItem = draggedTab;
+ if (!draggedTab.multiselected)
+ this.selectedItem = draggedTab;
}
if (!("animLastScreenX" in draggedTab._dragData))
draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
let screenX = event.screenX;
if (screenX == draggedTab._dragData.animLastScreenX)
return;
+ // Direction of the mouse movement.
+ let ltrMove = screenX > draggedTab._dragData.animLastScreenX;
+
draggedTab._dragData.animLastScreenX = screenX;
let rtl = (window.getComputedStyle(this).direction == "rtl");
let pinned = draggedTab.pinned;
let numPinned = gBrowser._numPinnedTabs;
let tabs = this._getVisibleTabs()
.slice(pinned ? 0 : numPinned,
pinned ? numPinned : undefined);
+ let movingTabs = draggedTab._dragData.movingTabs;
if (rtl) {
tabs.reverse();
+ // Copy moving tabs array to avoid infinite reversing.
+ movingTabs = [...movingTabs].reverse();
}
let tabWidth = draggedTab.getBoundingClientRect().width;
+ let shiftWidth = tabWidth * movingTabs.length;
draggedTab._dragData.tabWidth = tabWidth;
// Move the dragged tab based on the mouse position.
let leftTab = tabs[0];
let rightTab = tabs[tabs.length - 1];
- let tabScreenX = draggedTab.boxObject.screenX;
+ let rightMovingTabScreenX = movingTabs[movingTabs.length - 1].boxObject.screenX;
+ let leftMovingTabScreenX = movingTabs[0].boxObject.screenX;
let translateX = screenX - draggedTab._dragData.screenX;
if (!pinned) {
translateX += this.arrowScrollbox._scrollbox.scrollLeft - draggedTab._dragData.scrollX;
}
- let leftBound = leftTab.boxObject.screenX - tabScreenX;
+ let leftBound = leftTab.boxObject.screenX - leftMovingTabScreenX;
let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
- (tabScreenX + tabWidth);
- translateX = Math.max(translateX, leftBound);
- translateX = Math.min(translateX, rightBound);
- draggedTab.style.transform = "translateX(" + translateX + "px)";
+ (rightMovingTabScreenX + tabWidth);
+ translateX = Math.min(Math.max(translateX, leftBound), rightBound);
+
+ for (let tab of movingTabs) {
+ tab.style.transform = "translateX(" + translateX + "px)";
+ }
+
draggedTab._dragData.translateX = translateX;
// Determine what tab we're dragging over.
- // * Point of reference is the center of the dragged tab. If that
+ // * Single tab dragging: Point of reference is the center of the dragged tab. If that
// point touches a background tab, the dragged tab would take that
// tab's position when dropped.
+ // * Multiple tabs dragging: All dragged tabs are one "giant" tab with two
+ // points of reference (center of tabs on the extremities). When
+ // mouse is moving from left to right, the right reference gets activated,
+ // otherwise the left reference will be used. Everything else works the same
+ // as single tab dragging.
// * We're doing a binary search in order to reduce the amount of
// tabs we need to check.
- let tabCenter = tabScreenX + translateX + tabWidth / 2;
+ tabs = tabs.filter(t => !movingTabs.includes(t) || t == draggedTab);
+ let leftTabCenter = leftMovingTabScreenX + translateX + tabWidth / 2;
+ let rightTabCenter = rightMovingTabScreenX + translateX + tabWidth / 2;
+ let tabCenter = ltrMove ? rightTabCenter : leftTabCenter;
let newIndex = -1;
let oldIndex = "animDropIndex" in draggedTab._dragData ?
- draggedTab._dragData.animDropIndex : draggedTab._tPos;
+ draggedTab._dragData.animDropIndex : movingTabs[0]._tPos;
let low = 0;
let high = tabs.length - 1;
while (low <= high) {
let mid = Math.floor((low + high) / 2);
- if (tabs[mid] == draggedTab &&
- ++mid > high)
+ if (tabs[mid] == draggedTab && ++mid > high)
break;
let boxObject = tabs[mid].boxObject;
screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
if (screenX > tabCenter) {
high = mid - 1;
} else if (screenX + boxObject.width < tabCenter) {
low = mid + 1;
} else {
@@ -640,19 +659,19 @@
if (tab != draggedTab) {
let shift = getTabShift(tab, newIndex);
tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
}
}
function getTabShift(tab, dropIndex) {
if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
- return rtl ? -tabWidth : tabWidth;
+ return (rtl ? -shiftWidth : shiftWidth);
if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
- return rtl ? tabWidth : -tabWidth;
+ return (rtl ? shiftWidth : -shiftWidth);
return 0;
}
]]></body>
</method>
<method name="_finishAnimateTabMove">
<body><![CDATA[
if (this.getAttribute("movingtab") != "true") {
@@ -1163,16 +1182,39 @@
// Set the cursor to an arrow during tab drags.
dt.mozCursor = "default";
// Set the tab as the source of the drag, which ensures we have a stable
// node to deliver the `dragend` event. See bug 1345473.
dt.addElement(tab);
+ // Regroup all selected tabs around the dragged tab
+ // for multiple tabs dragging
+ if (tab.multiselected) {
+ let selectedTabs = gBrowser.selectedTabs;
+ let draggedTabPos = tab._tPos;
+
+ // Move left selected tabs
+ let insertAtPos = draggedTabPos - 1;
+ for (let i = selectedTabs.indexOf(tab) - 1; i > -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++;
+ }
+ }
+
// Create a canvas to which we capture the current tab.
// Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
// canvas size (in CSS pixels) to the window's backing resolution in order
// to get a full-resolution drag image for use on HiDPI displays.
let windowUtils = window.windowUtils;
let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
let canvas = this._dndCanvas;
if (!canvas) {
@@ -1236,17 +1278,19 @@
function clientX(ele) {
return ele.getBoundingClientRect().left;
}
let tabOffsetX = clientX(tab) - clientX(this);
tab._dragData = {
offsetX: event.screenX - window.screenX - tabOffsetX,
offsetY: event.screenY - window.screenY,
scrollX: this.arrowScrollbox._scrollbox.scrollLeft,
- screenX: event.screenX
+ screenX: event.screenX,
+ movingTabs: (tab.multiselected ? gBrowser.selectedTabs : [tab])
+ .filter(t => t.pinned == tab.pinned)
};
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
var effects = this._getDropEffectForTabDrag(event);
@@ -1339,21 +1383,23 @@
ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
ind.style.marginInlineStart = (-ind.clientWidth) + "px";
]]></handler>
<handler event="drop"><![CDATA[
var dt = event.dataTransfer;
var dropEffect = dt.dropEffect;
var draggedTab;
+ let movingTabs;
if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) { // tab copy or move
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// not our drop then
if (!draggedTab)
return;
+ movingTabs = draggedTab._dragData.movingTabs;
}
this._tabDropIndicator.collapsed = true;
event.stopPropagation();
if (draggedTab && dropEffect == "copy") {
// copy the dropped tab (wherever it's from)
let newIndex = this._getDropIndex(event, false);
let newTab = gBrowser.duplicateTab(draggedTab);
@@ -1369,44 +1415,55 @@
if (oldTranslateX > 0 && translateOffset > tabWidth / 2) {
newTranslateX += tabWidth;
} else if (oldTranslateX < 0 && -translateOffset > tabWidth / 2) {
newTranslateX -= tabWidth;
}
let dropIndex = "animDropIndex" in draggedTab._dragData &&
draggedTab._dragData.animDropIndex;
- if (dropIndex && dropIndex > draggedTab._tPos)
+ let incrementDropIndex = true;
+ if (dropIndex && dropIndex > movingTabs[0]._tPos) {
dropIndex--;
+ incrementDropIndex = false;
+ }
let animate = gBrowser.animationsEnabled;
if (oldTranslateX && oldTranslateX != newTranslateX && animate) {
- draggedTab.setAttribute("tabdrop-samewindow", "true");
- draggedTab.style.transform = "translateX(" + newTranslateX + "px)";
- let onTransitionEnd = transitionendEvent => {
- if (transitionendEvent.propertyName != "transform" ||
- transitionendEvent.originalTarget != draggedTab) {
- return;
- }
- draggedTab.removeEventListener("transitionend", onTransitionEnd);
+ for (let tab of movingTabs) {
+ tab.setAttribute("tabdrop-samewindow", "true");
+ tab.style.transform = "translateX(" + newTranslateX + "px)";
+ let onTransitionEnd = transitionendEvent => {
+ if (transitionendEvent.propertyName != "transform" ||
+ transitionendEvent.originalTarget != tab) {
+ return;
+ }
+ tab.removeEventListener("transitionend", onTransitionEnd);
+
+ tab.removeAttribute("tabdrop-samewindow");
- draggedTab.removeAttribute("tabdrop-samewindow");
+ this._finishAnimateTabMove();
+ if (dropIndex !== false) {
+ gBrowser.moveTabTo(tab, dropIndex);
+ if (incrementDropIndex)
+ dropIndex++;
+ }
- this._finishAnimateTabMove();
- if (dropIndex !== false) {
- gBrowser.moveTabTo(draggedTab, dropIndex);
- }
-
- gBrowser.syncThrobberAnimations(draggedTab);
- };
- draggedTab.addEventListener("transitionend", onTransitionEnd);
+ gBrowser.syncThrobberAnimations(tab);
+ };
+ tab.addEventListener("transitionend", onTransitionEnd);
+ }
} else {
this._finishAnimateTabMove();
if (dropIndex !== false) {
- gBrowser.moveTabTo(draggedTab, dropIndex);
+ for (let tab of movingTabs) {
+ gBrowser.moveTabTo(tab, dropIndex);
+ if (incrementDropIndex)
+ dropIndex++;
+ }
}
}
} else if (draggedTab) {
let newIndex = this._getDropIndex(event, false);
gBrowser.adoptTab(draggedTab, newIndex, true);
} else {
// Pass true to disallow dropping javascript: or data: urls
let links;
@@ -1968,16 +2025,20 @@
<handler event="dragstart"><![CDATA[
if (this.mOverCloseButton) {
event.stopPropagation();
}
]]></handler>
<handler event="mousedown" phase="capturing">
<![CDATA[
+ if (event.button == 0 && !this.selected && this.multiselected) {
+ gBrowser.lockClearMultiSelectionOnce();
+ }
+
let tabContainer = this.parentNode;
if (tabContainer._closeTabByDblclick &&
event.button == 0 &&
event.detail == 1) {
this._selectedOnFirstMouseDown = this.selected;
}
if (this.selected) {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -405,18 +405,16 @@ support-files =
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_storagePressure_notification.js]
skip-if = verify
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_close_dependent_window.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tabDrop.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_tabReorder.js]
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_detach_restore.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_drag_drop_perwindow.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_dragdrop.js]
skip-if = debug || (os == 'linux') || (os == 'mac') # Bug 1312436, Bug 1388973
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_tab_dragdrop2.js]
--- a/browser/base/content/test/tabs/browser.ini
+++ b/browser/base/content/test/tabs/browser.ini
@@ -25,16 +25,17 @@ support-files =
[browser_multiselect_tabs_close_other_tabs.js]
[browser_multiselect_tabs_close_using_shortcuts.js]
[browser_multiselect_tabs_close.js]
[browser_multiselect_tabs_move_to_new_window_contextmenu.js]
[browser_multiselect_tabs_mute_unmute.js]
[browser_multiselect_tabs_pin_unpin.js]
[browser_multiselect_tabs_positional_attrs.js]
[browser_multiselect_tabs_reload.js]
+[browser_multiselect_tabs_reorder.js]
[browser_multiselect_tabs_using_Ctrl.js]
[browser_multiselect_tabs_using_selectedTabs.js]
[browser_multiselect_tabs_using_Shift_and_Ctrl.js]
[browser_multiselect_tabs_using_Shift.js]
[browser_navigatePinnedTab.js]
[browser_new_file_whitelisted_http_tab.js]
skip-if = !e10s # Test only relevant for e10s.
[browser_new_tab_insert_position.js]
@@ -53,16 +54,17 @@ skip-if = !e10s # Pref and test only rel
[browser_pinnedTabs.js]
[browser_positional_attributes.js]
skip-if = (verify && (os == 'win' || os == 'mac'))
[browser_preloadedBrowser_zoom.js]
[browser_reload_deleted_file.js]
skip-if = (debug && os == 'mac') || (debug && os == 'linux' && bits == 64) #Bug 1421183, disabled on Linux/OSX for leaked windows
[browser_tabCloseProbes.js]
[browser_tabReorder_overflow.js]
+[browser_tabReorder.js]
[browser_tabSpinnerProbe.js]
skip-if = !e10s # Tab spinner is e10s only.
[browser_tabSwitchPrintPreview.js]
skip-if = os == 'mac'
[browser_tabswitch_updatecommands.js]
[browser_viewsource_of_data_URI_in_file_process.js]
[browser_visibleTabs_bookmarkAllTabs.js]
[browser_visibleTabs_contextMenu.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_reorder.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+add_task(async function() {
+ let tab0 = gBrowser.selectedTab;
+ let tab1 = await addTab();
+ let tab2 = await addTab();
+ let tab3 = await addTab();
+ let tab4 = await addTab();
+ let tab5 = await addTab();
+ let tabs = [tab0, tab1, tab2, tab3, tab4, tab5];
+
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await triggerClickOn(tab3, { ctrlKey: true });
+ await triggerClickOn(tab5, { ctrlKey: true });
+
+ is(gBrowser.selectedTab, tab1, "Tab1 is active");
+ is(gBrowser.selectedTabs.length, 3, "Three selected tabs");
+
+ for (let i of [1, 3, 5]) {
+ ok(tabs[i].multiselected, "Tab" + i + " is multiselected");
+ }
+ for (let i of [0, 2, 4]) {
+ ok(!tabs[i].multiselected, "Tab" + i + " is not multiselected");
+ }
+ for (let i of [0, 1, 2, 3, 4, 5]) {
+ is(tabs[i]._tPos, i, "Tab" + i + " position is :" + i);
+ }
+
+ await dragAndDrop(tab3, tab4, false);
+
+ is(gBrowser.selectedTab, tab3, "Dragged tab (tab3) is now active");
+ is(gBrowser.selectedTabs.length, 3, "Three selected tabs");
+
+ for (let i of [1, 3, 5]) {
+ ok(tabs[i].multiselected, "Tab" + i + " is still multiselected");
+ }
+ for (let i of [0, 2, 4]) {
+ ok(!tabs[i].multiselected, "Tab" + i + " is still not multiselected");
+ }
+
+ is(tab0._tPos, 0, "Tab0 position (0) doesn't change");
+
+ // Multiselected tabs gets grouped at the start of the slide.
+ is(tab1._tPos, tab3._tPos - 1, "Tab1 is located right at the left of the dragged tab (tab3)");
+ is(tab5._tPos, tab3._tPos + 1, "Tab5 is located right at the right of the dragged tab (tab3)");
+ is(tab3._tPos, 4, "Dragged tab (tab3) position is 4");
+
+ is(tab4._tPos, 2, "Drag target (tab4) has shifted to position 2");
+
+ for (let tab of tabs.filter(t => t != tab0))
+ BrowserTestUtils.removeTab(tab);
+});
+
rename from browser/base/content/test/general/browser_tabReorder.js
rename to browser/base/content/test/tabs/browser_tabReorder.js
--- a/browser/base/content/test/general/browser_tabReorder.js
+++ b/browser/base/content/test/tabs/browser_tabReorder.js
@@ -13,33 +13,16 @@ add_task(async function() {
}
});
is(gBrowser.tabs.length, initialTabsLength + 3, "new tabs are opened");
is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct");
is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
- async function dragAndDrop(tab1, tab2, copy) {
- let rect = tab2.getBoundingClientRect();
- let event = {
- ctrlKey: copy,
- altKey: copy,
- clientX: rect.left + rect.width / 2 + 10,
- clientY: rect.top + rect.height / 2,
- };
-
- let originalTPos = tab1._tPos;
- EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
- if (!copy) {
- await BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
- "Waiting for tab position to be updated");
- }
- }
-
await dragAndDrop(newTab1, newTab2, false);
is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
await dragAndDrop(newTab2, newTab1, true);
is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
--- a/browser/base/content/test/tabs/browser_tabReorder_overflow.js
+++ b/browser/base/content/test/tabs/browser_tabReorder_overflow.js
@@ -7,21 +7,17 @@ requestLongerTimeout(2);
add_task(async function() {
let initialTabsLength = gBrowser.tabs.length;
let arrowScrollbox = gBrowser.tabContainer.arrowScrollbox;
let tabs = gBrowser.tabs;
let tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
- let rect = ele => ele.getBoundingClientRect();
- let width = ele => rect(ele).width;
- let height = ele => rect(ele).height;
- let left = ele => rect(ele).left;
- let top = ele => rect(ele).top;
+ let width = ele => ele.getBoundingClientRect().width;
let tabCountForOverflow = Math.ceil(width(arrowScrollbox) / tabMinWidth);
let newTab1 = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:robots", {skipAnimation: true});
let newTab2 = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:about", {skipAnimation: true});
let newTab3 = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:config", {skipAnimation: true});
while (tabs.length < tabCountForOverflow) {
@@ -34,26 +30,14 @@ add_task(async function() {
}
});
is(gBrowser.tabs.length, tabCountForOverflow, "new tabs are opened");
is(gBrowser.tabs[initialTabsLength], newTab1, "newTab1 position is correct");
is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
- async function dragAndDrop(tab1, tab2) {
- let event = {
- clientX: left(tab2) + width(tab2) / 2 + 10,
- clientY: top(tab2) + height(tab2) / 2,
- };
-
- let originalTPos = tab1._tPos;
- EventUtils.synthesizeDrop(tab1, tab2, null, "move", window, window, event);
- await BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
- "Waiting for tab position to be updated");
- }
-
- await dragAndDrop(newTab1, newTab2);
+ await dragAndDrop(newTab1, newTab2, false);
is(gBrowser.tabs.length, tabCountForOverflow, "tabs are still there");
is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
});
--- a/browser/base/content/test/tabs/head.js
+++ b/browser/base/content/test/tabs/head.js
@@ -152,8 +152,25 @@ async function test_mute_tab(tab, icon,
// the media element's playing state.
let isAudioPlaying = await is_audio_playing(tab);
if (isAudioPlaying) {
await wait_for_tab_playing_event(tab, !expectMuted);
}
return mutedPromise;
}
+
+async function dragAndDrop(tab1, tab2, copy) {
+ let rect = tab2.getBoundingClientRect();
+ let event = {
+ ctrlKey: copy,
+ altKey: copy,
+ clientX: rect.left + rect.width / 2 + 10,
+ clientY: rect.top + rect.height / 2,
+ };
+
+ let originalTPos = tab1._tPos;
+ EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
+ if (!copy) {
+ await BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
+ "Waiting for tab position to be updated");
+ }
+}
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -680,17 +680,17 @@
:root[tabsintitlebar]:not([extradragspace]) #toolbar-menubar[autohide=true] + #TabsToolbar > #tabbrowser-tabs > .tabbrowser-tab::after,
%else
:root[tabsintitlebar]:not([extradragspace]) .tabbrowser-tab::after,
%endif
/* Show full height tab separators on hover and multiselection. */
.tabbrowser-tab:hover::after,
#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab[beforehovered]::after,
.tabbrowser-tab[multiselected]::after,
-.tabbrowser-tab[before-multiselected]::after {
+#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab[before-multiselected]::after {
margin-top: var(--tabs-top-border-width);
margin-bottom: 0;
}
/* Show full height tab separators on selected tabs. */
#tabbrowser-tabs:not([movingtab]) > .tabbrowser-tab[beforeselected-visible]::after,
#tabbrowser-tabs[movingtab] > .tabbrowser-tab[visuallyselected]::before,
.tabbrowser-tab[visuallyselected]::after {