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:
@@ -51,6 +51,7 @@
|
|||||||
orient="horizontal"
|
orient="horizontal"
|
||||||
stopwatchid="FX_TAB_CLICK_MS">
|
stopwatchid="FX_TAB_CLICK_MS">
|
||||||
<hbox class="tab-drop-indicator" hidden="true"/>
|
<hbox class="tab-drop-indicator" hidden="true"/>
|
||||||
|
<html:span id="tab-drag-empty-feedback"/>
|
||||||
# If the name (tabbrowser-arrowscrollbox) or structure of this changes
|
# If the name (tabbrowser-arrowscrollbox) or structure of this changes
|
||||||
# significantly, there is an optimization in
|
# significantly, there is an optimization in
|
||||||
# DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered based
|
# DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered based
|
||||||
|
|||||||
@@ -5578,7 +5578,7 @@ var SessionStoreInternal = {
|
|||||||
for (let i = 0; i < initialTabs.length; i++) {
|
for (let i = 0; i < initialTabs.length; i++) {
|
||||||
tabbrowser.unpinTab(initialTabs[i]);
|
tabbrowser.unpinTab(initialTabs[i]);
|
||||||
tabbrowser.moveTabTo(initialTabs[i], endPosition, {
|
tabbrowser.moveTabTo(initialTabs[i], endPosition, {
|
||||||
forceStandaloneTab: true,
|
forceUngrouped: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,21 @@
|
|||||||
const DIRECTION_FORWARD = 1;
|
const DIRECTION_FORWARD = 1;
|
||||||
const DIRECTION_BACKWARD = -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
|
* Updates the User Context UI indicators if the browser is in a non-default context
|
||||||
*/
|
*/
|
||||||
@@ -819,7 +834,7 @@
|
|||||||
this.verticalPinnedTabsContainer.appendChild(aTab)
|
this.verticalPinnedTabsContainer.appendChild(aTab)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.moveTabTo(aTab, this.pinnedTabCount, { forceStandaloneTab: true });
|
this.moveTabTo(aTab, this.pinnedTabCount, { forceUngrouped: true });
|
||||||
}
|
}
|
||||||
aTab.setAttribute("pinned", "true");
|
aTab.setAttribute("pinned", "true");
|
||||||
this._updateTabBarForPinnedTabs();
|
this._updateTabBarForPinnedTabs();
|
||||||
@@ -841,7 +856,7 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.moveTabTo(aTab, this.pinnedTabCount - 1, {
|
this.moveTabTo(aTab, this.pinnedTabCount - 1, {
|
||||||
forceStandaloneTab: true,
|
forceUngrouped: true,
|
||||||
});
|
});
|
||||||
aTab.removeAttribute("pinned");
|
aTab.removeAttribute("pinned");
|
||||||
}
|
}
|
||||||
@@ -5800,30 +5815,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MozTabbrowserTab} aTab
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} aTab
|
||||||
|
* The tab or tab group to move. Also accepts a tab group label as a
|
||||||
|
* stand-in for its group.
|
||||||
* @param {number} aIndex
|
* @param {number} aIndex
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
* @param {boolean} [options.forceStandaloneTab=false]
|
* @param {boolean} [options.forceUngrouped=false]
|
||||||
* Force `aTab` to move into position as a standalone tab, overriding
|
* Force `aTab` to move into position as a standalone tab, overriding
|
||||||
* any possibility of entering a tab group. For example, setting `true`
|
* any possibility of entering a tab group. For example, setting `true`
|
||||||
* ensures that a pinned tab will not accidentally be placed inside of
|
* ensures that a pinned tab will not accidentally be placed inside of
|
||||||
* a tab group, since pinned tabs are presently not allowed in tab groups.
|
* a tab group, since pinned tabs are presently not allowed in tab groups.
|
||||||
* @returns {void}
|
|
||||||
*/
|
*/
|
||||||
moveTabTo(aTab, aIndex, { forceStandaloneTab = false } = {}) {
|
moveTabTo(aTab, aIndex, { forceUngrouped = false } = {}) {
|
||||||
// Don't allow mixing pinned and unpinned tabs.
|
if (isTab(aTab)) {
|
||||||
if (aTab.pinned) {
|
// Don't allow mixing pinned and unpinned tabs.
|
||||||
aIndex = Math.min(aIndex, this.pinnedTabCount - 1);
|
if (aTab.pinned) {
|
||||||
|
aIndex = Math.min(aIndex, this.pinnedTabCount - 1);
|
||||||
|
} else {
|
||||||
|
aIndex = Math.max(aIndex, this.pinnedTabCount);
|
||||||
|
}
|
||||||
|
if (aTab._tPos == aIndex && !(aTab.group && forceUngrouped)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
aIndex = Math.max(aIndex, this.pinnedTabCount);
|
forceUngrouped = true;
|
||||||
}
|
if (isTabGroupLabel(aTab)) {
|
||||||
if (aTab._tPos == aIndex && !(aTab.group && forceStandaloneTab)) {
|
aTab = aTab.group;
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#handleTabMove(aTab, () => {
|
this.#handleTabMove(aTab, () => {
|
||||||
let neighbor = this.tabs[aIndex];
|
let neighbor = this.tabs[aIndex];
|
||||||
if (forceStandaloneTab && neighbor.group) {
|
if (forceUngrouped && neighbor.group) {
|
||||||
neighbor = neighbor.group;
|
neighbor = neighbor.group;
|
||||||
}
|
}
|
||||||
if (neighbor && aIndex > aTab._tPos) {
|
if (neighbor && aIndex > aTab._tPos) {
|
||||||
@@ -5835,7 +5858,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MozTabbrowserTab} tab
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} tab
|
||||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
||||||
*/
|
*/
|
||||||
moveTabBefore(tab, targetElement) {
|
moveTabBefore(tab, targetElement) {
|
||||||
@@ -5843,7 +5866,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MozTabbrowserTab[]} tabs
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup[]} tabs
|
||||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
||||||
*/
|
*/
|
||||||
moveTabsBefore(tabs, targetElement) {
|
moveTabsBefore(tabs, targetElement) {
|
||||||
@@ -5851,7 +5874,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MozTabbrowserTab} tab
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} tab
|
||||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
||||||
*/
|
*/
|
||||||
moveTabAfter(tab, targetElement) {
|
moveTabAfter(tab, targetElement) {
|
||||||
@@ -5859,7 +5882,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MozTabbrowserTab[]} tabs
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup[]} tabs
|
||||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
||||||
*/
|
*/
|
||||||
moveTabsAfter(tabs, targetElement) {
|
moveTabsAfter(tabs, targetElement) {
|
||||||
@@ -5867,17 +5890,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MozTabbrowserTab} tab
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} tab
|
||||||
|
* The tab or tab group to move. Also accepts a tab group label as a
|
||||||
|
* stand-in for its group.
|
||||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
* @param {MozTabbrowserTab|MozTabbrowserTabGroup} targetElement
|
||||||
* @param {boolean} moveBefore
|
* @param {boolean} moveBefore
|
||||||
*/
|
*/
|
||||||
#moveTabNextTo(tab, targetElement, moveBefore = false) {
|
#moveTabNextTo(tab, targetElement, moveBefore = false) {
|
||||||
|
if (isTabGroupLabel(tab)) {
|
||||||
|
tab = tab.group;
|
||||||
|
if (targetElement?.group) {
|
||||||
|
targetElement = targetElement.group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let getContainer = () => {
|
let getContainer = () => {
|
||||||
if (tab.pinned && this.tabContainer.verticalMode) {
|
if (tab.pinned && this.tabContainer.verticalMode) {
|
||||||
return this.tabContainer.verticalPinnedTabsContainer;
|
return this.tabContainer.verticalPinnedTabsContainer;
|
||||||
}
|
}
|
||||||
return this.tabContainer;
|
return this.tabContainer;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.#handleTabMove(tab, () => {
|
this.#handleTabMove(tab, () => {
|
||||||
if (moveBefore) {
|
if (moveBefore) {
|
||||||
getContainer().insertBefore(tab, targetElement);
|
getContainer().insertBefore(tab, targetElement);
|
||||||
@@ -5922,7 +5955,7 @@
|
|||||||
*/
|
*/
|
||||||
#handleTabMove(aTab, moveActionCallback) {
|
#handleTabMove(aTab, moveActionCallback) {
|
||||||
let wasFocused = document.activeElement == this.selectedTab;
|
let wasFocused = document.activeElement == this.selectedTab;
|
||||||
let oldPosition = aTab._tPos;
|
let oldPosition = isTab(aTab) && aTab.elementIndex;
|
||||||
|
|
||||||
moveActionCallback();
|
moveActionCallback();
|
||||||
|
|
||||||
@@ -5945,12 +5978,11 @@
|
|||||||
}
|
}
|
||||||
// Pinning/unpinning vertical tabs, and moving tabs into tab groups, both bypass moveTabTo.
|
// Pinning/unpinning vertical tabs, and moving tabs into tab groups, both bypass moveTabTo.
|
||||||
// We still want to check whether its worth dispatching an event.
|
// We still want to check whether its worth dispatching an event.
|
||||||
if (oldPosition == aTab._tPos) {
|
if (isTab(aTab) && oldPosition != aTab.elementIndex) {
|
||||||
return;
|
let evt = document.createEvent("UIEvents");
|
||||||
|
evt.initUIEvent("TabMove", true, false, window, oldPosition);
|
||||||
|
aTab.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
var evt = document.createEvent("UIEvents");
|
|
||||||
evt.initUIEvent("TabMove", true, false, window, oldPosition);
|
|
||||||
aTab.dispatchEvent(evt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -6075,11 +6107,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
moveTabToStart(aTab = this.selectedTab) {
|
moveTabToStart(aTab = this.selectedTab) {
|
||||||
this.moveTabTo(aTab, 0, { forceStandaloneTab: true });
|
this.moveTabTo(aTab, 0, { forceUngrouped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
moveTabToEnd(aTab = this.selectedTab) {
|
moveTabToEnd(aTab = this.selectedTab) {
|
||||||
this.moveTabTo(aTab, this.tabs.length - 1, { forceStandaloneTab: true });
|
this.moveTabTo(aTab, this.tabs.length - 1, { forceUngrouped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,18 +57,21 @@
|
|||||||
this.initializeAttributeInheritance();
|
this.initializeAttributeInheritance();
|
||||||
|
|
||||||
this.#labelElement = this.querySelector(".tab-group-label");
|
this.#labelElement = this.querySelector(".tab-group-label");
|
||||||
|
// Mirroring MozTabbrowserTab
|
||||||
|
this.#labelElement.container = gBrowser.tabContainer;
|
||||||
|
this.#labelElement.group = this;
|
||||||
|
|
||||||
this.#labelElement.addEventListener("click", this);
|
this.#labelElement.addEventListener("click", this);
|
||||||
|
|
||||||
this.#updateLabelAriaAttributes();
|
|
||||||
this.#updateCollapsedAriaAttributes();
|
|
||||||
|
|
||||||
this.addEventListener("TabSelect", this);
|
|
||||||
|
|
||||||
this.#labelElement.addEventListener("contextmenu", e => {
|
this.#labelElement.addEventListener("contextmenu", e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
gBrowser.tabGroupMenu.openEditModal(this);
|
gBrowser.tabGroupMenu.openEditModal(this);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.#updateLabelAriaAttributes();
|
||||||
|
this.#updateCollapsedAriaAttributes();
|
||||||
|
|
||||||
|
this.addEventListener("TabSelect", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
@@ -174,6 +177,12 @@
|
|||||||
if (!!val == this.collapsed) {
|
if (!!val == this.collapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (val) {
|
||||||
|
for (let tab of this.tabs) {
|
||||||
|
// Unlock tab sizes.
|
||||||
|
tab.style.maxWidth = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
this.toggleAttribute("collapsed", val);
|
this.toggleAttribute("collapsed", val);
|
||||||
this.#updateCollapsedAriaAttributes();
|
this.#updateCollapsedAriaAttributes();
|
||||||
const eventName = val ? "TabGroupCollapse" : "TabGroupExpand";
|
const eventName = val ? "TabGroupCollapse" : "TabGroupExpand";
|
||||||
@@ -203,7 +212,6 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.#labelElement?.setAttribute("aria-label", tabGroupName);
|
this.#labelElement?.setAttribute("aria-label", tabGroupName);
|
||||||
this.#labelElement.group = this;
|
|
||||||
this.#labelElement?.setAttribute("aria-description", tabGroupDescription);
|
this.#labelElement?.setAttribute("aria-description", tabGroupDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -682,8 +682,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
on_dragstart(event) {
|
on_dragstart(event) {
|
||||||
|
if (this._isCustomizing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tab = this._getDragTargetTab(event);
|
var tab = this._getDragTargetTab(event);
|
||||||
if (!tab || this._isCustomizing) {
|
if (!tab) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,34 +715,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dataTransferOrderedTabs;
|
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 selectedTabs = gBrowser.selectedTabs;
|
||||||
let otherSelectedTabs = selectedTabs.filter(
|
let otherSelectedTabs = selectedTabs.filter(
|
||||||
selectedTab => selectedTab != tab
|
selectedTab => selectedTab != tab
|
||||||
);
|
);
|
||||||
dataTransferOrderedTabs = [tab].concat(otherSelectedTabs);
|
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;
|
let dt = event.dataTransfer;
|
||||||
for (let i = 0; i < dataTransferOrderedTabs.length; i++) {
|
for (let i = 0; i < dataTransferOrderedTabs.length; i++) {
|
||||||
let dtTab = dataTransferOrderedTabs[i];
|
let dtTab = dataTransferOrderedTabs[i];
|
||||||
|
|
||||||
dt.mozSetDataAt(TAB_DROP_TYPE, dtTab, 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,
|
// 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
|
// otherwise trying to detach the tab by dropping it on the desktop
|
||||||
// may result in an "internet shortcut"
|
// may result in an "internet shortcut"
|
||||||
dt.mozSetDataAt(
|
dt.mozSetDataAt(
|
||||||
"text/x-moz-text-internal",
|
"text/x-moz-text-internal",
|
||||||
dtBrowser.currentURI.spec,
|
dtBrowser.currentURI.spec,
|
||||||
i
|
i
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the cursor to an arrow during tab drags.
|
// Set the cursor to an arrow during tab drags.
|
||||||
@@ -748,8 +754,20 @@
|
|||||||
// node to deliver the `dragend` event. See bug 1345473.
|
// node to deliver the `dragend` event. See bug 1345473.
|
||||||
dt.addElement(tab);
|
dt.addElement(tab);
|
||||||
|
|
||||||
|
let expandedTabGroups;
|
||||||
if (tab.multiselected) {
|
if (tab.multiselected) {
|
||||||
this.#moveTogetherSelectedTabs(tab);
|
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.
|
// Create a canvas to which we capture the current tab.
|
||||||
@@ -772,8 +790,10 @@
|
|||||||
canvas.height = 90 * scale;
|
canvas.height = 90 * scale;
|
||||||
let toDrag = canvas;
|
let toDrag = canvas;
|
||||||
let dragImageOffset = -16;
|
let dragImageOffset = -16;
|
||||||
let browser = tab.linkedBrowser;
|
let browser = isTab(tab) && tab.linkedBrowser;
|
||||||
if (gMultiProcessBrowser) {
|
if (isTabGroupLabel(tab)) {
|
||||||
|
toDrag = document.getElementById("tab-drag-empty-feedback");
|
||||||
|
} else if (gMultiProcessBrowser) {
|
||||||
var context = canvas.getContext("2d");
|
var context = canvas.getContext("2d");
|
||||||
context.fillStyle = "white";
|
context.fillStyle = "white";
|
||||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
@@ -851,6 +871,7 @@
|
|||||||
),
|
),
|
||||||
fromTabList,
|
fromTabList,
|
||||||
tabGroupCreationColor: gBrowser.tabGroupMenu.nextUnusedColor,
|
tabGroupCreationColor: gBrowser.tabGroupMenu.nextUnusedColor,
|
||||||
|
expandedTabGroups,
|
||||||
};
|
};
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -897,7 +918,7 @@
|
|||||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||||
if (
|
if (
|
||||||
(effects == "move" || effects == "copy") &&
|
(effects == "move" || effects == "copy") &&
|
||||||
this == draggedTab.container &&
|
document == draggedTab.ownerDocument &&
|
||||||
!draggedTab._dragData.fromTabList
|
!draggedTab._dragData.fromTabList
|
||||||
) {
|
) {
|
||||||
ind.hidden = true;
|
ind.hidden = true;
|
||||||
@@ -1117,12 +1138,16 @@
|
|||||||
|
|
||||||
if (shouldTranslate) {
|
if (shouldTranslate) {
|
||||||
let translationPromises = [];
|
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 => {
|
let translationPromise = new Promise(resolve => {
|
||||||
tab.toggleAttribute("tabdrop-samewindow", true);
|
item.toggleAttribute("tabdrop-samewindow", true);
|
||||||
tab.style.transform = `translate(${newTranslateX}px, ${newTranslateY}px)`;
|
item.style.transform = `translate(${newTranslateX}px, ${newTranslateY}px)`;
|
||||||
let postTransitionCleanup = () => {
|
let postTransitionCleanup = () => {
|
||||||
tab.removeAttribute("tabdrop-samewindow");
|
item.removeAttribute("tabdrop-samewindow");
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
if (gReduceMotion) {
|
if (gReduceMotion) {
|
||||||
@@ -1131,15 +1156,15 @@
|
|||||||
let onTransitionEnd = transitionendEvent => {
|
let onTransitionEnd = transitionendEvent => {
|
||||||
if (
|
if (
|
||||||
transitionendEvent.propertyName != "transform" ||
|
transitionendEvent.propertyName != "transform" ||
|
||||||
transitionendEvent.originalTarget != tab
|
transitionendEvent.originalTarget != item
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tab.removeEventListener("transitionend", onTransitionEnd);
|
item.removeEventListener("transitionend", onTransitionEnd);
|
||||||
|
|
||||||
postTransitionCleanup();
|
postTransitionCleanup();
|
||||||
};
|
};
|
||||||
tab.addEventListener("transitionend", onTransitionEnd);
|
item.addEventListener("transitionend", onTransitionEnd);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
translationPromises.push(translationPromise);
|
translationPromises.push(translationPromise);
|
||||||
@@ -1257,6 +1282,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (draggedTab) {
|
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;
|
delete draggedTab._dragData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1861,10 +1893,12 @@
|
|||||||
selectedTab._notselectedsinceload = false;
|
selectedTab._notselectedsinceload = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#keepTabSizeLocked;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to keep the active tab's close button under the mouse cursor
|
* Try to keep the active tab's close button under the mouse cursor
|
||||||
*/
|
*/
|
||||||
_lockTabSizing(aTab, aTabWidth) {
|
_lockTabSizing(aClosingTab, aTabWidth) {
|
||||||
if (this.verticalMode) {
|
if (this.verticalMode) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1874,17 +1908,23 @@
|
|||||||
return;
|
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) {
|
if (!this._tabDefaultMaxWidth) {
|
||||||
this._tabDefaultMaxWidth = parseFloat(
|
this._tabDefaultMaxWidth = parseFloat(
|
||||||
window.getComputedStyle(aTab).maxWidth
|
window.getComputedStyle(tabs[numPinned]).maxWidth
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._lastTabClosedByMouse = true;
|
this._lastTabClosedByMouse = true;
|
||||||
this._scrollButtonWidth = window.windowUtils.getBoundsWithoutFlushing(
|
this._scrollButtonWidth = window.windowUtils.getBoundsWithoutFlushing(
|
||||||
this.arrowScrollbox._scrollButtonDown
|
this.arrowScrollbox._scrollButtonDown
|
||||||
).width;
|
).width;
|
||||||
|
if (aTabWidth === undefined) {
|
||||||
|
aTabWidth = window.windowUtils.getBoundsWithoutFlushing(
|
||||||
|
tabs[numPinned]
|
||||||
|
).width;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.overflowing) {
|
if (this.overflowing) {
|
||||||
// Don't need to do anything if we're in overflow mode and aren't scrolled
|
// 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
|
// 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.
|
// 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.
|
// This can't be done as easily in non-overflow mode, so we don't bother.
|
||||||
if (aTab.owner) {
|
if (aClosingTab?.owner) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._expandSpacerBy(aTabWidth);
|
this._expandSpacerBy(aTabWidth);
|
||||||
} else {
|
} /* non-overflow mode */ else {
|
||||||
// non-overflow mode
|
|
||||||
// Locking is neither in effect nor needed, so let tabs expand normally.
|
|
||||||
if (isEndTab && !this._hasTabTempMaxWidth) {
|
if (isEndTab && !this._hasTabTempMaxWidth) {
|
||||||
|
// Locking is neither in effect nor needed, so let tabs expand normally.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let numPinned = gBrowser.pinnedTabCount;
|
|
||||||
// Force tabs to stay the same width, unless we're closing the last tab,
|
// 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
|
// which case we need to let them expand just enough so that the overall
|
||||||
// tabbar width is the same.
|
// tabbar width is the same.
|
||||||
@@ -1955,6 +1993,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
_unlockTabSizing() {
|
_unlockTabSizing() {
|
||||||
|
if (this.#keepTabSizeLocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
gBrowser.removeEventListener("mousemove", this);
|
gBrowser.removeEventListener("mousemove", this);
|
||||||
window.removeEventListener("mouseout", this);
|
window.removeEventListener("mouseout", this);
|
||||||
|
|
||||||
@@ -2343,8 +2385,12 @@
|
|||||||
let lastBound = endEdge(lastTab) - lastMovingTabScreen;
|
let lastBound = endEdge(lastTab) - lastMovingTabScreen;
|
||||||
translate = Math.min(Math.max(translate, firstBound), lastBound);
|
translate = Math.min(Math.max(translate, firstBound), lastBound);
|
||||||
|
|
||||||
for (let tab of movingTabs) {
|
for (let item of movingTabs) {
|
||||||
tab.style.transform = `${translateAxis}(${translate}px)`;
|
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;
|
dragData.translatePos = translate;
|
||||||
@@ -2562,7 +2608,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gBrowser._tabGroupsEnabled && !isPinned) {
|
if (gBrowser._tabGroupsEnabled && isTab(draggedTab) && !isPinned) {
|
||||||
let dragOverGroupingThreshold = 1 - moveOverThreshold;
|
let dragOverGroupingThreshold = 1 - moveOverThreshold;
|
||||||
|
|
||||||
// When dragging tab(s) over an ungrouped tab, signal to the user
|
// When dragging tab(s) over an ungrouped tab, signal to the user
|
||||||
@@ -2902,7 +2948,10 @@
|
|||||||
}
|
}
|
||||||
// fall through
|
// fall through
|
||||||
case "mousemove":
|
case "mousemove":
|
||||||
if (document.getElementById("tabContextMenu").state != "open") {
|
if (
|
||||||
|
document.getElementById("tabContextMenu").state != "open" &&
|
||||||
|
!this.hasAttribute("movingtab")
|
||||||
|
) {
|
||||||
this._unlockTabSizing();
|
this._unlockTabSizing();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -3027,28 +3076,30 @@
|
|||||||
*/
|
*/
|
||||||
_getDragTargetTab(event, { ignoreTabSides = false } = {}) {
|
_getDragTargetTab(event, { ignoreTabSides = false } = {}) {
|
||||||
let { target } = event;
|
let { target } = event;
|
||||||
if (target.nodeType != Node.ELEMENT_NODE) {
|
while (target) {
|
||||||
target = target.parentElement;
|
if (isTab(target) || isTabGroupLabel(target)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
target = target.parentNode;
|
||||||
}
|
}
|
||||||
let tab = target?.closest("tab");
|
if (target && ignoreTabSides) {
|
||||||
if (tab && ignoreTabSides) {
|
let { width, height } = target.getBoundingClientRect();
|
||||||
let { width, height } = tab.getBoundingClientRect();
|
|
||||||
if (
|
if (
|
||||||
event.screenX < tab.screenX + width * 0.25 ||
|
event.screenX < target.screenX + width * 0.25 ||
|
||||||
event.screenX > tab.screenX + width * 0.75 ||
|
event.screenX > target.screenX + width * 0.75 ||
|
||||||
((event.screenY < tab.screenY + height * 0.25 ||
|
((event.screenY < target.screenY + height * 0.25 ||
|
||||||
event.screenY > tab.screenY + height * 0.75) &&
|
event.screenY > target.screenY + height * 0.75) &&
|
||||||
this.verticalMode)
|
this.verticalMode)
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tab;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getDropIndex(event) {
|
_getDropIndex(event) {
|
||||||
let tab = this._getDragTargetTab(event);
|
let tab = this._getDragTargetTab(event);
|
||||||
if (!tab) {
|
if (!isTab(tab)) {
|
||||||
return this.allTabs.length;
|
return this.allTabs.length;
|
||||||
}
|
}
|
||||||
let isBeforeMiddle;
|
let isBeforeMiddle;
|
||||||
@@ -3080,12 +3131,10 @@
|
|||||||
if (isMovingTabs) {
|
if (isMovingTabs) {
|
||||||
let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||||
if (
|
if (
|
||||||
XULElement.isInstance(sourceNode) &&
|
(isTab(sourceNode) || isTabGroupLabel(sourceNode)) &&
|
||||||
sourceNode.localName == "tab" &&
|
|
||||||
sourceNode.ownerGlobal.isChromeWindow &&
|
sourceNode.ownerGlobal.isChromeWindow &&
|
||||||
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") ==
|
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") ==
|
||||||
"navigator:browser" &&
|
"navigator:browser"
|
||||||
sourceNode.ownerGlobal.gBrowser.tabContainer == sourceNode.container
|
|
||||||
) {
|
) {
|
||||||
// Do not allow transfering a private tab to a non-private window
|
// Do not allow transfering a private tab to a non-private window
|
||||||
// and vice versa.
|
// and vice versa.
|
||||||
|
|||||||
@@ -218,7 +218,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tabbrowser-tabs[movingtab] &:is([selected], [multiselected]) {
|
#tabbrowser-tabs[movingtab] &:is(:active, [multiselected]) {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
|
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
|
||||||
@@ -838,7 +838,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tabbrowser-tabs[movingtab] &:is([selected],[multiselected]) {
|
#tabbrowser-tabs[movingtab] &:is(:active,[multiselected]) {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: light-dark(var(--dragover-tab-group-color), var(--dragover-tab-group-color-invert));
|
background-color: light-dark(var(--dragover-tab-group-color), var(--dragover-tab-group-color-invert));
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
@@ -901,6 +901,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-group-label-container {
|
.tab-group-label-container {
|
||||||
|
#tabbrowser-tabs[movingtab] &:active {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
|
||||||
|
}
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
#tabbrowser-tabs[movingtab] &,
|
#tabbrowser-tabs[movingtab] &,
|
||||||
&[multiselected-move-together],
|
&[multiselected-move-together],
|
||||||
@@ -997,6 +1002,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tab-drag-empty-feedback {
|
||||||
|
background: rgba(0,0,0,0.01);
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tab-group-editor-panel {
|
.tab-group-editor-panel {
|
||||||
--panel-width: 20em;
|
--panel-width: 20em;
|
||||||
--panel-padding: var(--space-large);
|
--panel-padding: var(--space-large);
|
||||||
|
|||||||
Reference in New Issue
Block a user