Bug 1908439 - Drag and drop for moving a group within the window. r=dwalker,tabbrowser-reviewers,sessionstore-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D239494
This commit is contained in:
@@ -682,8 +682,12 @@
|
||||
}
|
||||
|
||||
on_dragstart(event) {
|
||||
if (this._isCustomizing) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tab = this._getDragTargetTab(event);
|
||||
if (!tab || this._isCustomizing) {
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -711,34 +715,36 @@
|
||||
}
|
||||
|
||||
let dataTransferOrderedTabs;
|
||||
if (!fromTabList) {
|
||||
if (fromTabList || isTabGroupLabel(tab)) {
|
||||
// Dragging a group label or an item in the all tabs menu doesn't
|
||||
// change the currently selected tabs, and it's not possible to select
|
||||
// multiple tabs from the list, thus handle only the dragged tab in
|
||||
// this case.
|
||||
dataTransferOrderedTabs = [tab];
|
||||
} else {
|
||||
let selectedTabs = gBrowser.selectedTabs;
|
||||
let otherSelectedTabs = selectedTabs.filter(
|
||||
selectedTab => selectedTab != tab
|
||||
);
|
||||
dataTransferOrderedTabs = [tab].concat(otherSelectedTabs);
|
||||
} else {
|
||||
// Dragging an item in the tabs list doesn't change the currently
|
||||
// selected tabs, and it's not possible to select multiple tabs from
|
||||
// the list, thus handle only the dragged tab in this case.
|
||||
dataTransferOrderedTabs = [tab];
|
||||
}
|
||||
|
||||
let dt = event.dataTransfer;
|
||||
for (let i = 0; i < dataTransferOrderedTabs.length; i++) {
|
||||
let dtTab = dataTransferOrderedTabs[i];
|
||||
|
||||
dt.mozSetDataAt(TAB_DROP_TYPE, dtTab, i);
|
||||
let dtBrowser = dtTab.linkedBrowser;
|
||||
if (isTab(dtTab)) {
|
||||
let dtBrowser = dtTab.linkedBrowser;
|
||||
|
||||
// We must not set text/x-moz-url or text/plain data here,
|
||||
// otherwise trying to detach the tab by dropping it on the desktop
|
||||
// may result in an "internet shortcut"
|
||||
dt.mozSetDataAt(
|
||||
"text/x-moz-text-internal",
|
||||
dtBrowser.currentURI.spec,
|
||||
i
|
||||
);
|
||||
// We must not set text/x-moz-url or text/plain data here,
|
||||
// otherwise trying to detach the tab by dropping it on the desktop
|
||||
// may result in an "internet shortcut"
|
||||
dt.mozSetDataAt(
|
||||
"text/x-moz-text-internal",
|
||||
dtBrowser.currentURI.spec,
|
||||
i
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the cursor to an arrow during tab drags.
|
||||
@@ -748,8 +754,20 @@
|
||||
// node to deliver the `dragend` event. See bug 1345473.
|
||||
dt.addElement(tab);
|
||||
|
||||
let expandedTabGroups;
|
||||
if (tab.multiselected) {
|
||||
this.#moveTogetherSelectedTabs(tab);
|
||||
} else if (isTabGroupLabel(tab)) {
|
||||
expandedTabGroups = gBrowser.tabGroups.filter(
|
||||
group => !group.collapsed
|
||||
);
|
||||
if (expandedTabGroups.length) {
|
||||
this._lockTabSizing();
|
||||
this.#keepTabSizeLocked = true;
|
||||
}
|
||||
for (let group of expandedTabGroups) {
|
||||
group.collapsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a canvas to which we capture the current tab.
|
||||
@@ -772,8 +790,10 @@
|
||||
canvas.height = 90 * scale;
|
||||
let toDrag = canvas;
|
||||
let dragImageOffset = -16;
|
||||
let browser = tab.linkedBrowser;
|
||||
if (gMultiProcessBrowser) {
|
||||
let browser = isTab(tab) && tab.linkedBrowser;
|
||||
if (isTabGroupLabel(tab)) {
|
||||
toDrag = document.getElementById("tab-drag-empty-feedback");
|
||||
} else if (gMultiProcessBrowser) {
|
||||
var context = canvas.getContext("2d");
|
||||
context.fillStyle = "white";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
@@ -851,6 +871,7 @@
|
||||
),
|
||||
fromTabList,
|
||||
tabGroupCreationColor: gBrowser.tabGroupMenu.nextUnusedColor,
|
||||
expandedTabGroups,
|
||||
};
|
||||
|
||||
event.stopPropagation();
|
||||
@@ -897,7 +918,7 @@
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
if (
|
||||
(effects == "move" || effects == "copy") &&
|
||||
this == draggedTab.container &&
|
||||
document == draggedTab.ownerDocument &&
|
||||
!draggedTab._dragData.fromTabList
|
||||
) {
|
||||
ind.hidden = true;
|
||||
@@ -1117,12 +1138,16 @@
|
||||
|
||||
if (shouldTranslate) {
|
||||
let translationPromises = [];
|
||||
for (let tab of movingTabs) {
|
||||
for (let item of movingTabs) {
|
||||
if (isTabGroupLabel(item)) {
|
||||
// Shift the `.tab-group-label-container` to shift the label element.
|
||||
item = item.parentElement;
|
||||
}
|
||||
let translationPromise = new Promise(resolve => {
|
||||
tab.toggleAttribute("tabdrop-samewindow", true);
|
||||
tab.style.transform = `translate(${newTranslateX}px, ${newTranslateY}px)`;
|
||||
item.toggleAttribute("tabdrop-samewindow", true);
|
||||
item.style.transform = `translate(${newTranslateX}px, ${newTranslateY}px)`;
|
||||
let postTransitionCleanup = () => {
|
||||
tab.removeAttribute("tabdrop-samewindow");
|
||||
item.removeAttribute("tabdrop-samewindow");
|
||||
resolve();
|
||||
};
|
||||
if (gReduceMotion) {
|
||||
@@ -1131,15 +1156,15 @@
|
||||
let onTransitionEnd = transitionendEvent => {
|
||||
if (
|
||||
transitionendEvent.propertyName != "transform" ||
|
||||
transitionendEvent.originalTarget != tab
|
||||
transitionendEvent.originalTarget != item
|
||||
) {
|
||||
return;
|
||||
}
|
||||
tab.removeEventListener("transitionend", onTransitionEnd);
|
||||
item.removeEventListener("transitionend", onTransitionEnd);
|
||||
|
||||
postTransitionCleanup();
|
||||
};
|
||||
tab.addEventListener("transitionend", onTransitionEnd);
|
||||
item.addEventListener("transitionend", onTransitionEnd);
|
||||
}
|
||||
});
|
||||
translationPromises.push(translationPromise);
|
||||
@@ -1257,6 +1282,13 @@
|
||||
}
|
||||
|
||||
if (draggedTab) {
|
||||
if (draggedTab._dragData.expandedTabGroups?.length) {
|
||||
for (let group of draggedTab._dragData.expandedTabGroups) {
|
||||
group.collapsed = false;
|
||||
}
|
||||
this.#keepTabSizeLocked = true;
|
||||
this._unlockTabSizing();
|
||||
}
|
||||
delete draggedTab._dragData;
|
||||
}
|
||||
}
|
||||
@@ -1861,10 +1893,12 @@
|
||||
selectedTab._notselectedsinceload = false;
|
||||
}
|
||||
|
||||
#keepTabSizeLocked;
|
||||
|
||||
/**
|
||||
* Try to keep the active tab's close button under the mouse cursor
|
||||
*/
|
||||
_lockTabSizing(aTab, aTabWidth) {
|
||||
_lockTabSizing(aClosingTab, aTabWidth) {
|
||||
if (this.verticalMode) {
|
||||
return;
|
||||
}
|
||||
@@ -1874,17 +1908,23 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var isEndTab = aTab._tPos > tabs.at(-1)._tPos;
|
||||
let numPinned = gBrowser.pinnedTabCount;
|
||||
let isEndTab = aClosingTab && aClosingTab._tPos > tabs.at(-1)._tPos;
|
||||
|
||||
if (!this._tabDefaultMaxWidth) {
|
||||
this._tabDefaultMaxWidth = parseFloat(
|
||||
window.getComputedStyle(aTab).maxWidth
|
||||
window.getComputedStyle(tabs[numPinned]).maxWidth
|
||||
);
|
||||
}
|
||||
this._lastTabClosedByMouse = true;
|
||||
this._scrollButtonWidth = window.windowUtils.getBoundsWithoutFlushing(
|
||||
this.arrowScrollbox._scrollButtonDown
|
||||
).width;
|
||||
if (aTabWidth === undefined) {
|
||||
aTabWidth = window.windowUtils.getBoundsWithoutFlushing(
|
||||
tabs[numPinned]
|
||||
).width;
|
||||
}
|
||||
|
||||
if (this.overflowing) {
|
||||
// Don't need to do anything if we're in overflow mode and aren't scrolled
|
||||
@@ -1895,17 +1935,15 @@
|
||||
// If the tab has an owner that will become the active tab, the owner will
|
||||
// be to the left of it, so we actually want the left tab to slide over.
|
||||
// This can't be done as easily in non-overflow mode, so we don't bother.
|
||||
if (aTab.owner) {
|
||||
if (aClosingTab?.owner) {
|
||||
return;
|
||||
}
|
||||
this._expandSpacerBy(aTabWidth);
|
||||
} else {
|
||||
// non-overflow mode
|
||||
// Locking is neither in effect nor needed, so let tabs expand normally.
|
||||
} /* non-overflow mode */ else {
|
||||
if (isEndTab && !this._hasTabTempMaxWidth) {
|
||||
// Locking is neither in effect nor needed, so let tabs expand normally.
|
||||
return;
|
||||
}
|
||||
let numPinned = gBrowser.pinnedTabCount;
|
||||
// Force tabs to stay the same width, unless we're closing the last tab,
|
||||
// which case we need to let them expand just enough so that the overall
|
||||
// tabbar width is the same.
|
||||
@@ -1955,6 +1993,10 @@
|
||||
}
|
||||
|
||||
_unlockTabSizing() {
|
||||
if (this.#keepTabSizeLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
gBrowser.removeEventListener("mousemove", this);
|
||||
window.removeEventListener("mouseout", this);
|
||||
|
||||
@@ -2343,8 +2385,12 @@
|
||||
let lastBound = endEdge(lastTab) - lastMovingTabScreen;
|
||||
translate = Math.min(Math.max(translate, firstBound), lastBound);
|
||||
|
||||
for (let tab of movingTabs) {
|
||||
tab.style.transform = `${translateAxis}(${translate}px)`;
|
||||
for (let item of movingTabs) {
|
||||
if (isTabGroupLabel(item)) {
|
||||
// Shift the `.tab-group-label-container` to shift the label element.
|
||||
item = item.parentElement;
|
||||
}
|
||||
item.style.transform = `${translateAxis}(${translate}px)`;
|
||||
}
|
||||
|
||||
dragData.translatePos = translate;
|
||||
@@ -2562,7 +2608,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (gBrowser._tabGroupsEnabled && !isPinned) {
|
||||
if (gBrowser._tabGroupsEnabled && isTab(draggedTab) && !isPinned) {
|
||||
let dragOverGroupingThreshold = 1 - moveOverThreshold;
|
||||
|
||||
// When dragging tab(s) over an ungrouped tab, signal to the user
|
||||
@@ -2902,7 +2948,10 @@
|
||||
}
|
||||
// fall through
|
||||
case "mousemove":
|
||||
if (document.getElementById("tabContextMenu").state != "open") {
|
||||
if (
|
||||
document.getElementById("tabContextMenu").state != "open" &&
|
||||
!this.hasAttribute("movingtab")
|
||||
) {
|
||||
this._unlockTabSizing();
|
||||
}
|
||||
break;
|
||||
@@ -3027,28 +3076,30 @@
|
||||
*/
|
||||
_getDragTargetTab(event, { ignoreTabSides = false } = {}) {
|
||||
let { target } = event;
|
||||
if (target.nodeType != Node.ELEMENT_NODE) {
|
||||
target = target.parentElement;
|
||||
while (target) {
|
||||
if (isTab(target) || isTabGroupLabel(target)) {
|
||||
break;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
let tab = target?.closest("tab");
|
||||
if (tab && ignoreTabSides) {
|
||||
let { width, height } = tab.getBoundingClientRect();
|
||||
if (target && ignoreTabSides) {
|
||||
let { width, height } = target.getBoundingClientRect();
|
||||
if (
|
||||
event.screenX < tab.screenX + width * 0.25 ||
|
||||
event.screenX > tab.screenX + width * 0.75 ||
|
||||
((event.screenY < tab.screenY + height * 0.25 ||
|
||||
event.screenY > tab.screenY + height * 0.75) &&
|
||||
event.screenX < target.screenX + width * 0.25 ||
|
||||
event.screenX > target.screenX + width * 0.75 ||
|
||||
((event.screenY < target.screenY + height * 0.25 ||
|
||||
event.screenY > target.screenY + height * 0.75) &&
|
||||
this.verticalMode)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return tab;
|
||||
return target;
|
||||
}
|
||||
|
||||
_getDropIndex(event) {
|
||||
let tab = this._getDragTargetTab(event);
|
||||
if (!tab) {
|
||||
if (!isTab(tab)) {
|
||||
return this.allTabs.length;
|
||||
}
|
||||
let isBeforeMiddle;
|
||||
@@ -3080,12 +3131,10 @@
|
||||
if (isMovingTabs) {
|
||||
let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
if (
|
||||
XULElement.isInstance(sourceNode) &&
|
||||
sourceNode.localName == "tab" &&
|
||||
(isTab(sourceNode) || isTabGroupLabel(sourceNode)) &&
|
||||
sourceNode.ownerGlobal.isChromeWindow &&
|
||||
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") ==
|
||||
"navigator:browser" &&
|
||||
sourceNode.ownerGlobal.gBrowser.tabContainer == sourceNode.container
|
||||
"navigator:browser"
|
||||
) {
|
||||
// Do not allow transfering a private tab to a non-private window
|
||||
// and vice versa.
|
||||
|
||||
Reference in New Issue
Block a user