chore: Drag and drop refactor, p=#11723

* feat: Full cross-window workspace syncing, b=no-bug, c=workspaces

* feat: Also change icons and labels if the tab is pending, b=no-bug, c=tabs, workspaces

* feat: Dont session duplicate the tabs, b=no-bug, c=workspaces

* feat: Properly handle tab moves, b=no-bug, c=workspaces

* feat: Start on new session restore, b=no-bug, c=no-component

* Discard changes to prefs/browser.yaml

* feat: Start doing out own session restore, b=no-bug, c=folders, tabs

* feat: Stop using pinned manager and use zen session sidebar, b=no-bug, c=common, folders, tabs, workspaces

* feat: Dont restore windows that are already initialized, b=no-bug, c=no-component

* chore: Update patches to ff 145, b=no-bug, c=no-component

* Discard changes to src/browser/components/sessionstore/SessionStore-sys-mjs.patch

* Discard changes to src/browser/components/tabbrowser/content/tab-js.patch

* Discard changes to src/browser/components/tabbrowser/content/tabbrowser-js.patch

* Discard changes to src/zen/tabs/ZenPinnedTabsStorage.mjs

* feat: Run session saver before opening a new winodw, b=no-bug, c=tabs

* feat: Clone the previous state, b=no-bug, c=no-component

* feat: Move window sync to its own JS module, b=no-bug, c=workspaces

* feat: Run session saver before opening a new window, b=no-bug, c=no-component

* feat: Full cross-window workspace syncing, b=no-bug, c=workspaces

* feat: Also change icons and labels if the tab is pending, b=no-bug, c=tabs, workspaces

* feat: Dont session duplicate the tabs, b=no-bug, c=workspaces

* feat: Start on new session restore, b=no-bug, c=no-component

* feat: Properly handle tab moves, b=no-bug, c=workspaces

* Discard changes to prefs/browser.yaml

* feat: Start doing out own session restore, b=no-bug, c=folders, tabs

* feat: Stop using pinned manager and use zen session sidebar, b=no-bug, c=common, folders, tabs, workspaces

* feat: Dont restore windows that are already initialized, b=no-bug, c=no-component

* chore: Update patches to ff 145, b=no-bug, c=no-component

* Discard changes to src/browser/components/sessionstore/SessionStore-sys-mjs.patch

* Discard changes to src/browser/components/tabbrowser/content/tab-js.patch

* Discard changes to src/browser/components/tabbrowser/content/tabbrowser-js.patch

* Discard changes to src/zen/tabs/ZenPinnedTabsStorage.mjs

* feat: Run session saver before opening a new winodw, b=no-bug, c=tabs

* feat: Clone the previous state, b=no-bug, c=no-component

* feat: Move window sync to its own JS module, b=no-bug, c=workspaces

* feat: Run session saver before opening a new window, b=no-bug, c=no-component

* feat: Start making use of IDs instead of sync identifiers, b=no-bug, c=folders

* feat: Listen to new tab opens for new sync system, b=no-bug, c=common, folders, tabs

* feat: Listen for more tab events and properly sync them, b=no-bug, c=common, folders, tabs

* feat: Start moving browser views to the selected windows, b=no-bug, c=no-component

* chore: Remove extra patch, b=no-bug, c=no-component

* feat: Leave a screenshot of the page behind when switching windows or tabs, b=no-bug, c=common

* feat: Run session saves right before writing and quiting, b=no-bug, c=common

* fix: Fixed going back to a different window not allowing to type on inputs, b=no-bug, c=no-component

* feat: Start syncing folders as well, b=no-bug, c=folders

* Discard changes to src/browser/components/tabbrowser/content/tab-js.patch

* chore: Update patches to ff 146, b=no-bug, c=no-component

* feat: Early support for unsynced windoiws, b=no-bug, c=workspaces

* fix: Move back active views when closing a window, b=no-bug, c=no-component

* feat: Stop rendering sub-layers when swaping browsers, b=no-bug, c=common

* feat: Improved support for unsynced windows support, b=no-bug, c=workspaces, folders

* feat: Implemented 'Move To...' Button for unsynced windows, b=no-bug, c=workspaces, common

* feat: Make sure to properly flush all windows when making a new one and fix removing progress listeners, b=no-bug, c=workspaces

* feat: Make sure to not lose any tabs when opening from a private window, b=no-bug, c=workspaces

* feat: Allow unload to run instantly and fix closing windows on mac, b=no-bug, c=no-component

* feat: Make sure to always initialize an empty state with the sidebar object, b=no-bug, c=workspaces

* chore: Small fixes and QA checks, b=no-bug, c=tabs, workspaces

* fix: Fixed tab labels not changing on unfocused windows, b=no-bug, c=no-component

* feat: Fixed closing windows on macos not returning to the original views, b=no-bug, c=no-component

* chore: Turn off debug flags by default, b=no-bug, c=no-component

* feat: Start implementing old pinned tab behaviour we used to have, b=no-bug, c=common, tabs

* feat: Unsynced windows should always be allowed to change labels, b=no-bug, c=welcome

* feat: Make sure we wait long enough before we initialize workspaces, b=no-bug, c=workspaces

* feat: Dont mix remoteness when changing browser views and restore window spaces, b=no-bug, c=common, folders, workspaces

* test: Fixed tests for the pinned tabs manager, b=no-bug, c=tabs, folders, tests, welcome

* feat: Added partial support for split views, b=no-bug, c=split-view

* chore: Finished basic support for split views, b=no-bug, c=folders, split-view

* feat: Always make sure to save the last closed window to the sidebar object, b=no-bug, c=no-component

* feat: Implement workspace sync store into the session file, b=closes #10857, c=common, tabs, tests, workspaces

* feat: New drag and drop system, b=no-bug, c=tabs, common, folders

* feat: Add support for drag-and-dropping tabs into groups, b=no-bug, c=common, folders, tabs, workspaces

* feat: Add a default value for the workspace cache, b=no-bug, c=workspaces

* fix: Default assign an array instead of an object to the workspaces cache, b=no-bug, c=workspaces

* feat: Take into consideration win data may not have allocated spaces, b=no-bug, c=workspaces

* feat: Always make sure we are assigning the correct space ID, b=no-bug, c=workspaces

* feat: Make sure to initialize windows even if there are no tabs, b=no-bug, c=no-component

* feat: Improved drag and drop simulation, b=no-bug, c=common, tabs

* feat: Implement opacity changes to the drag image, b=no-bug, c=tabs, common, split-view

* feat: Support for drag and dropping outside the window, b=no-bug, c=split-view

* feat: Added transitions when reordering, b=no-bug, c=split-view, tabs

* feat: Started working on drag and dropping to essentials, b=no-bug, c=no-component

* Discard changes to locales/en-US/browser/browser/zen-workspaces.ftl

* Discard changes to prefs/zen/view.yaml

* Discard changes to prefs/zen/zen.yaml

* Discard changes to src/browser/base/content/zen-assets.inc.xhtml

* Discard changes to src/browser/base/content/zen-assets.jar.inc.mn

* Discard changes to src/browser/base/content/zen-panels/popups.inc

* Discard changes to src/browser/base/content/zen-preloaded.inc.xhtml

* Discard changes to src/browser/components/places/content/editBookmark-js.patch

* Discard changes to src/browser/components/sessionstore/SessionStore-sys-mjs.patch

* Discard changes to src/browser/components/sessionstore/TabState-sys-mjs.patch

* Discard changes to src/browser/components/tabbrowser/content/tab-js.patch

* Discard changes to src/browser/components/urlbar/UrlbarProviderPlaces-sys-mjs.patch

* Discard changes to src/zen/ZenComponents.manifest

* Discard changes to src/browser/components/tabbrowser/content/tabbrowser-js.patch

* feat: Finish migration, b=no-bug, c=no-component

* feat: Add support for multi tabs dragging, b=no-bug, c=tabs

* feat: Added support for essential tabs, b=no-bug, c=split-view, tabs, workspaces

* feat: Added support to switch space when holding on the side of the sidebar, b=no-bug, c=common, split-view, workspaces

* Discard changes to src/browser/components/tabbrowser/content/tabbrowser-js.patch

* chore: Fixed merge conflicts, b=no-bug, c=no-component

* feat: Added support for split views, b=no-bug, c=split-view

---------

Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
This commit is contained in:
mr. m
2025-12-28 18:26:52 +01:00
committed by GitHub
parent b66b05dcf6
commit 335c024e6d
26 changed files with 1755 additions and 608 deletions

View File

@@ -40,12 +40,6 @@
- name: zen.view.window.scheme
value: 2
- name: zen.view.drag-and-drop.move-over-threshold
value: 70
- name: zen.view.drag-and-drop.edge-zone-threshold
value: 25
- name: zen.view.context-menu.refresh
value: '@IS_TWILIGHT@'

View File

@@ -20,6 +20,15 @@
- name: zen.tabs.close-window-with-empty
value: true
- name: zen.tabs.use-legacy-drag-and-drop
value: false
- name: zen.tabs.folder-dragover-threshold-percent
value: 20 # Percentage of folder height to trigger dragover
- name: zen.tabs.dnd-switch-space-delay
value: 1000 # milliseconds
- name: zen.ctrlTab.show-pending-tabs
value: false

View File

@@ -4,6 +4,7 @@
#include ../../../zen/common/jar.inc.mn
#include ../../../zen/compact-mode/jar.inc.mn
#include ../../../zen/drag-and-drop/jar.inc.mn
#include ../../../zen/split-view/jar.inc.mn
#include ../../../zen/mods/jar.inc.mn
#include ../../../zen/workspaces/jar.inc.mn

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/drag-and-drop.js b/browser/components/tabbrowser/content/drag-and-drop.js
index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9548dce83 100644
index 97b931c3c7385a52d20204369fcf6d6999053687..6a136cf14d0bc081507a05f298f12ac7a7914601 100644
--- a/browser/components/tabbrowser/content/drag-and-drop.js
+++ b/browser/components/tabbrowser/content/drag-and-drop.js
@@ -32,6 +32,9 @@
@@ -12,26 +12,33 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
if (isTab(element)) {
return element;
}
@@ -112,6 +115,10 @@
@@ -112,6 +115,9 @@
}
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (draggedTab && dropEffect === "move") {
+ gZenPinnedTabManager.applyDragoverClass(event, draggedTab);
+ gZenViewSplitter.onBrowserDragEndToSplit(event);
+ }
if (
(dropEffect == "move" || dropEffect == "copy") &&
document == draggedTab.ownerDocument &&
@@ -266,6 +273,18 @@
@@ -130,10 +136,6 @@
// Pinned tabs in expanded vertical mode are on a grid format and require
// different logic to drag and drop.
- if (this._isContainerVerticalPinnedGrid(draggedTab)) {
- this._animateExpandedPinnedTabMove(event);
- return;
- }
this._animateTabMove(event);
return;
}
@@ -266,6 +268,15 @@
this._tabDropIndicator.hidden = true;
event.stopPropagation();
+ if (draggedTab?.hasAttribute("zen-has-splitted")) {
+ draggedTab.removeAttribute("zen-has-splitted");
+ draggedTab._visuallySelected = false;
+ }
+ if (draggedTab && dropEffect == "move") {
+ this.handle_drop_transition?.(draggedTab._dragData.dropElement, draggedTab, movingTabs, draggedTab._dragData.dropBefore);
+ let moved = gZenPinnedTabManager.moveToAnotherTabContainerIfNecessary(event, movingTabs);
+
+ if (moved) {
@@ -42,7 +49,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
if (draggedTab && dropEffect == "copy") {
let duplicatedDraggedTab;
let duplicatedTabs = [];
@@ -291,8 +310,9 @@
@@ -291,8 +302,9 @@
let translateOffsetY = oldTranslateY % tabHeight;
let newTranslateX = oldTranslateX - translateOffsetX;
let newTranslateY = oldTranslateY - translateOffsetY;
@@ -54,7 +61,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
if (this._isContainerVerticalPinnedGrid(draggedTab)) {
// Update both translate axis for pinned vertical expanded tabs
@@ -308,8 +328,8 @@
@@ -308,8 +320,8 @@
}
} else {
let tabs = this._tabbrowserTabs.ariaFocusableItems.slice(
@@ -65,7 +72,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
);
let size = this._tabbrowserTabs.verticalMode ? "height" : "width";
let screenAxis = this._tabbrowserTabs.verticalMode
@@ -362,11 +382,13 @@
@@ -362,11 +374,13 @@
this._dragToPinPromoCard,
];
let shouldPin =
@@ -79,7 +86,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
isTab(draggedTab) &&
draggedTab.pinned &&
this._tabbrowserTabs.arrowScrollbox.contains(event.target);
@@ -384,6 +406,7 @@
@@ -384,6 +398,7 @@
(oldTranslateY && oldTranslateY != newTranslateY);
} else if (this._tabbrowserTabs.verticalMode) {
shouldTranslate &&= oldTranslateY && oldTranslateY != newTranslateY;
@@ -87,7 +94,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
} else {
shouldTranslate &&= oldTranslateX && oldTranslateX != newTranslateX;
}
@@ -440,7 +463,7 @@
@@ -440,7 +455,7 @@
item.removeAttribute("tabdrop-samewindow");
resolve();
};
@@ -96,7 +103,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
postTransitionCleanup();
} else {
let onTransitionEnd = transitionendEvent => {
@@ -581,6 +604,7 @@
@@ -581,6 +596,7 @@
let nextItem = this._tabbrowserTabs.ariaFocusableItems[newIndex];
let tabGroup = isTab(nextItem) && nextItem.group;
@@ -104,7 +111,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
gBrowser.loadTabs(urls, {
inBackground,
replace,
@@ -618,7 +642,16 @@
@@ -618,7 +634,16 @@
this._expandGroupOnDrop(draggedTab);
}
this._resetTabsAfterDrop(draggedTab.ownerDocument);
@@ -122,7 +129,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
if (
dt.mozUserCancelled ||
dt.dropEffect != "none" ||
@@ -822,7 +855,10 @@
@@ -822,7 +847,10 @@
_getDragTarget(event, { ignoreSides = false } = {}) {
let { target } = event;
while (target) {
@@ -134,7 +141,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
break;
}
target = target.parentNode;
@@ -839,14 +875,17 @@
@@ -839,14 +867,17 @@
return null;
}
}
@@ -154,7 +161,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
!this._tabbrowserTabs.expandOnHover
);
}
@@ -877,7 +916,8 @@
@@ -877,7 +908,8 @@
isTabGroupLabel(draggedTab) &&
draggedTab._dragData?.expandGroupOnDrop
) {
@@ -164,19 +171,23 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
}
}
@@ -942,10 +982,7 @@
if (this._isContainerVerticalPinnedGrid(tab)) {
// In expanded vertical mode, the max number of pinned tabs per row is dynamic
// Set this before adjusting dragged tab's position
- let pinnedTabs = this._tabbrowserTabs.visibleTabs.slice(
- 0,
- gBrowser.pinnedTabCount
- );
+ let pinnedTabs = this._tabbrowserTabs.ariaFocusableItems.slice(0, gBrowser._numZenEssentials);
let tabsPerRow = 0;
let position = RTL_UI
? window.windowUtils.getBoundsWithoutFlushing(
@@ -1112,7 +1149,7 @@
@@ -1055,7 +1087,6 @@
// using updateDragImage. On Linux, we can use a panel.
if (platform == "win" || platform == "macosx") {
captureListener = function () {
- dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
};
} else {
// Create a panel to use it in setDragImage
@@ -1093,7 +1124,6 @@
);
dragImageOffset = dragImageOffset * scale;
}
- dt.setDragImage(toDrag, dragImageOffset, dragImageOffset);
// _dragData.offsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
@@ -1112,7 +1142,7 @@
let dropEffect = this.getDropEffectForTabDrag(event);
let isMovingInTabStrip = !fromTabList && dropEffect == "move";
let collapseTabGroupDuringDrag =
@@ -185,7 +196,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
tab._dragData = {
offsetX: this._tabbrowserTabs.verticalMode
@@ -1122,7 +1159,7 @@
@@ -1122,7 +1152,7 @@
? event.screenY - window.screenY - tabOffset
: event.screenY - window.screenY,
scrollPos:
@@ -194,7 +205,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
? this._tabbrowserTabs.pinnedTabsContainer.scrollPosition
: this._tabbrowserTabs.arrowScrollbox.scrollPosition,
screenX: event.screenX,
@@ -1149,6 +1186,7 @@
@@ -1149,6 +1179,7 @@
if (collapseTabGroupDuringDrag) {
tab.group.collapsed = true;
@@ -202,224 +213,15 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
}
}
}
@@ -1173,6 +1211,16 @@
@@ -1173,6 +1204,7 @@
if (tabStripItemElement.hasAttribute("dragtarget")) {
return;
}
+ let { movingTabs: zenMovingTabs } = tab._dragData;
+ for (let movingTab of zenMovingTabs.slice(zenMovingTabs.findIndex(t => t._tPos == tab._tPos))) {
+ if (isTabGroupLabel(tab)) {
+ movingTab = movingTab.parentElement;
+ }
+ // "dragtarget" contains the following rules which must only be set AFTER the above
+ // elements have been adjusted. {z-index: 3 !important, position: absolute !important}
+ movingTab.setAttribute("zen-dragtarget", "");
+ }
+ return;
let isPinned = tab.pinned;
let numPinned = gBrowser.pinnedTabCount;
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
@@ -1624,10 +1672,7 @@
return;
}
- let tabs = this._tabbrowserTabs.visibleTabs.slice(
- 0,
- gBrowser.pinnedTabCount
- );
+ let tabs = this._tabbrowserTabs.ariaFocusableItems.slice(0, gBrowser._numZenEssentials);
let directionX = screenX > dragData.animLastScreenX;
let directionY = screenY > dragData.animLastScreenY;
@@ -1636,6 +1681,8 @@
let { width: tabWidth, height: tabHeight } =
draggedTab.getBoundingClientRect();
+ tabWidth += 4; // Add 4px to account for the gap
+ tabHeight += 4;
let shiftSizeX = tabWidth * movingTabs.length;
let shiftSizeY = tabHeight;
dragData.tabWidth = tabWidth;
@@ -1672,8 +1719,8 @@
let lastBoundX =
lastTabInRow.screenX +
lastTabInRow.getBoundingClientRect().width -
- (lastMovingTabScreenX + tabWidth);
- let lastBoundY = periphery.screenY - (lastMovingTabScreenY + tabHeight);
+ (lastMovingTabScreenX + tabWidth) + 4;
+ let lastBoundY = lastTab.screenY - lastMovingTabScreenY;
translateX = Math.min(Math.max(translateX, firstBoundX), lastBoundX);
translateY = Math.min(Math.max(translateY, firstBoundY), lastBoundY);
@@ -1833,13 +1880,18 @@
this._clearDragOverGroupingTimer();
this.#clearPinnedDropIndicatorTimer();
- let isPinned = draggedTab.pinned;
- let numPinned = gBrowser.pinnedTabCount;
+ let isPinned = draggedTab?.group ? draggedTab.group.pinned : draggedTab.pinned;
+ let numPinned = gBrowser._numVisiblePinTabsWithoutCollapsed;
+ let essential = draggedTab.hasAttribute("zen-essential");
+ const isDraggingFolder = isTabGroupLabel(draggedTab) && draggedTab.group?.isZenFolder;
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
let tabs = allTabs.slice(
- isPinned ? 0 : numPinned,
- isPinned ? numPinned : undefined
+ (isPinned && essential) ? 0 : gBrowser._numZenEssentials,
+ isPinned ? (essential ? gBrowser._numZenEssentials : (isDraggingFolder ? numPinned : undefined)) : undefined
);
+ if (draggedTab.group?.hasAttribute("split-view-group")) {
+ draggedTab = draggedTab.group.labelElement;
+ }
if (this._rtlMode) {
tabs.reverse();
@@ -1854,7 +1906,7 @@
let translateAxis = this._tabbrowserTabs.verticalMode
? "translateY"
: "translateX";
- let { width: tabWidth, height: tabHeight } = bounds(draggedTab);
+ let { width: tabWidth, height: tabHeight } = bounds(draggedTab.group?.hasAttribute("split-view-group") ? draggedTab.group : draggedTab);
let tabSize = this._tabbrowserTabs.verticalMode ? tabHeight : tabWidth;
let translateX = event.screenX - dragData.screenX;
let translateY = event.screenY - dragData.screenY;
@@ -1870,6 +1922,12 @@
);
let lastMovingTab = movingTabs.at(-1);
let firstMovingTab = movingTabs[0];
+ if (lastMovingTab.group?.hasAttribute("split-view-group")) {
+ lastMovingTab = lastMovingTab.group;
+ }
+ if (firstMovingTab.group?.hasAttribute("split-view-group")) {
+ firstMovingTab = firstMovingTab.group;
+ }
let endEdge = ele => ele[screenAxis] + bounds(ele)[size];
let lastMovingTabScreen = endEdge(lastMovingTab);
let firstMovingTabScreen = firstMovingTab[screenAxis];
@@ -1884,6 +1942,13 @@
let endBound = this._rtlMode
? endEdge(this._tabbrowserTabs) - lastMovingTabScreen
: periphery[screenAxis] - 1 - lastMovingTabScreen;
+ {
+ let firstTab = tabs.at(this._rtlMode ? -1 : 0);
+ let lastTab = tabs.at(this._rtlMode ? 0 : -1);
+ startBound = firstTab[screenAxis] - firstMovingTabScreen;
+ endBound = endEdge(lastTab) - lastMovingTabScreen;
+ endBound = gZenPinnedTabManager.getLastTabBound(endBound, lastTab, isDraggingFolder);
+ }
translate = Math.min(Math.max(translate, startBound), endBound);
// Center the tab under the cursor if the tab is not under the cursor while dragging
@@ -2075,6 +2140,8 @@
};
let dropElement = getOverlappedElement();
+ if (dropElement?.hasAttribute("split-view-group")) dropElement = dropElement.labelElement;
+ gZenPinnedTabManager.animateSeparatorMove(movingTabs, dropElement, isPinned, event);
let newDropElementIndex;
if (dropElement) {
@@ -2157,7 +2224,7 @@
? Services.prefs.getIntPref(
"browser.tabs.dragDrop.moveOverThresholdPercent"
) / 100
- : 0.5;
+ : Services.prefs.getIntPref('zen.view.drag-and-drop.move-over-threshold') / 100;
moveOverThreshold = Math.min(1, Math.max(0, moveOverThreshold));
let shouldMoveOver = overlapPercent > moveOverThreshold;
if (logicalForward && shouldMoveOver) {
@@ -2190,6 +2257,7 @@
// If dragging a group over another group, don't make it look like it is
// possible to drop the dragged group inside the other group.
if (
+ false &&
isTabGroupLabel(draggedTab) &&
dropElement?.group &&
(!dropElement.group.collapsed ||
@@ -2216,20 +2284,13 @@
let isOutOfBounds = isPinned
? dropElement.elementIndex >= numPinned
: dropElement.elementIndex < numPinned;
- if (isOutOfBounds) {
- // Drop after last pinned tab
- dropElement = this._tabbrowserTabs.ariaFocusableItems[numPinned - 1];
- dropBefore = false;
- }
}
- if (
- gBrowser._tabGroupsEnabled &&
- isTab(draggedTab) &&
- !isPinned &&
- (!numPinned || newDropElementIndex >= numPinned)
- ) {
+ if (isTab(draggedTab) || isTabGroupLabel(draggedTab)) {
let dragOverGroupingThreshold = 1 - moveOverThreshold;
+ if (draggedTab && !dropElement?.group) {
+ gZenFolders.highlightGroupOnDragOver(null);
+ }
let groupingDelay = Services.prefs.getIntPref(
"browser.tabs.dragDrop.createGroup.delayMS"
);
@@ -2237,6 +2298,7 @@
// When dragging tab(s) over an ungrouped tab, signal to the user
// that dropping the tab(s) will create a new tab group.
let shouldCreateGroupOnDrop =
+ false &&
!movingTabsSet.has(dropElement) &&
isTab(dropElement) &&
!dropElement?.group &&
@@ -2245,6 +2307,7 @@
// When dragging tab(s) over a collapsed tab group label, signal to the
// user that dropping the tab(s) will add them to the group.
let shouldDropIntoCollapsedTabGroup =
+ false &&
isTabGroupLabel(dropElement) &&
dropElement.group.collapsed &&
overlapPercent > dragOverGroupingThreshold;
@@ -2302,6 +2365,14 @@
dropElement = dropElementGroup.tabs[0];
dropBefore = true;
}
+ ({ dropElement, colorCode, dropBefore } = gZenFolders.handleDragOverTabGroupLabel(
+ dropElement,
+ draggedTab,
+ overlapPercent,
+ movingTabs,
+ dropBefore,
+ colorCode
+ ));
}
this._setDragOverGroupColor(colorCode);
this._tabbrowserTabs.toggleAttribute(
@@ -2324,10 +2395,11 @@
dragData.dropBefore = dropBefore;
dragData.animDropElementIndex = newDropElementIndex;
+ gZenFolders.setFolderIndentation(movingTabs, dropElement);
// Shift background tabs to leave a gap where the dragged tab
// would currently be dropped.
for (let item of tabs) {
- if (item == draggedTab) {
+ if (item == draggedTab || (item.group?.hasAttribute("split-view-group") && item.group == draggedTab.group)) {
continue;
}
@@ -2417,11 +2489,13 @@
}
finishAnimateTabMove() {
+ gZenPinnedTabManager.onDragFinish();
if (!this.#isMovingTab()) {
return;
}
this.#setMovingTabMode(false);
+ gZenFolders.highlightGroupOnDragOver(null);
for (let item of this._tabbrowserTabs.ariaFocusableItems) {
this._resetGroupTarget(item);
@@ -2457,7 +2531,7 @@
@@ -2457,7 +2489,7 @@
tab.style.left = "";
tab.style.top = "";
tab.style.maxWidth = "";
@@ -428,7 +230,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
}
for (let label of draggedTabDocument.getElementsByClassName(
"tab-group-label-container"
@@ -2467,7 +2541,7 @@
@@ -2467,7 +2499,7 @@
label.style.left = "";
label.style.top = "";
label.style.maxWidth = "";

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d797de7be 100644
index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..00a9810cc894b6a21adb78b70a15049cc1db3edf 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -386,6 +386,7 @@
@@ -87,7 +87,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
tab.linkedPanel = uniqueId;
this._selectedTab = tab;
this._selectedBrowser = browser;
@@ -898,13 +951,17 @@
@@ -898,13 +951,18 @@
}
this.showTab(aTab);
@@ -100,17 +100,21 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
);
// If periphery is null, append to end
- this.pinnedTabsContainer.insertBefore(aTab, periphery);
+ this.tabContainer.tabDragAndDrop.handle_drop_transition(this.tabs[this.pinnedTabCount - 1], aTab, [aTab], false);
+ aTab.hasAttribute("zen-essential") ? gZenWorkspaces.getEssentialsSection(aTab).appendChild(aTab) : this.pinnedTabsContainer.insertBefore(aTab, this.pinnedTabsContainer.lastChild)
});
+ }
aTab.setAttribute("pinned", "true");
this._updateTabBarForPinnedTabs();
@@ -917,11 +974,15 @@
@@ -917,11 +975,18 @@
}
this.#handleTabMove(aTab, () => {
+ const handled = gZenFolders.handleTabUnpin(aTab);
+ if (!handled) {
+ this.tabContainer.tabDragAndDrop.handle_drop_transition(this.tabs[this.pinnedTabCount + 1 /* empty + extra */], aTab, [aTab], true);
+ }
+
// we remove this attribute first, so that allTabs represents
// the moving of a tab from the pinned tabs container
@@ -123,7 +127,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
});
aTab.style.marginInlineStart = "";
@@ -1098,6 +1159,9 @@
@@ -1098,6 +1163,9 @@
let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"];
@@ -133,7 +137,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (
aIconURL &&
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
@@ -1107,6 +1171,9 @@
@@ -1107,6 +1175,9 @@
);
return;
}
@@ -143,7 +147,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
let browser = this.getBrowserForTab(aTab);
browser.mIconURL = aIconURL;
@@ -1379,7 +1446,6 @@
@@ -1379,7 +1450,6 @@
// Preview mode should not reset the owner
if (!this._previewMode && !oldTab.selected) {
@@ -151,7 +155,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
let lastRelatedTab = this._lastRelatedTabMap.get(oldTab);
@@ -1470,6 +1536,7 @@
@@ -1470,6 +1540,7 @@
if (!this._previewMode) {
newTab.recordTimeFromUnloadToReload();
newTab.updateLastAccessed();
@@ -159,7 +163,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
oldTab.updateLastAccessed();
// if this is the foreground window, update the last-seen timestamps.
if (this.ownerGlobal == BrowserWindowTracker.getTopWindow()) {
@@ -1622,6 +1689,9 @@
@@ -1622,6 +1693,9 @@
}
let activeEl = document.activeElement;
@@ -169,7 +173,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// If focus is on the old tab, move it to the new tab.
if (activeEl == oldTab) {
newTab.focus();
@@ -1945,6 +2015,11 @@
@@ -1945,6 +2019,11 @@
}
_setTabLabel(aTab, aLabel, { beforeTabOpen, isContentTitle, isURL } = {}) {
@@ -181,7 +185,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (!aLabel || aLabel.includes("about:reader?")) {
return false;
}
@@ -2053,7 +2128,7 @@
@@ -2053,7 +2132,7 @@
newIndex = this.selectedTab._tPos + 1;
}
@@ -190,7 +194,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (this.isTabGroupLabel(targetTab)) {
throw new Error(
"Replacing a tab group label with a tab is not supported"
@@ -2328,6 +2403,7 @@
@@ -2328,6 +2407,7 @@
uriIsAboutBlank,
userContextId,
skipLoad,
@@ -198,7 +202,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} = {}) {
let b = document.createXULElement("browser");
// Use the JSM global to create the permanentKey, so that if the
@@ -2401,8 +2477,7 @@
@@ -2401,8 +2481,7 @@
// we use a different attribute name for this?
b.setAttribute("name", name);
}
@@ -208,7 +212,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
b.setAttribute("transparent", "true");
}
@@ -2567,7 +2642,7 @@
@@ -2567,7 +2646,7 @@
let panel = this.getPanel(browser);
let uniqueId = this._generateUniquePanelID();
@@ -217,7 +221,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
aTab.linkedPanel = uniqueId;
// Inject the <browser> into the DOM if necessary.
@@ -2626,8 +2701,8 @@
@@ -2626,8 +2705,8 @@
// If we transitioned from one browser to two browsers, we need to set
// hasSiblings=false on both the existing browser and the new browser.
if (this.tabs.length == 2) {
@@ -228,7 +232,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} else {
aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1;
}
@@ -2814,7 +2889,6 @@
@@ -2814,7 +2893,6 @@
this.selectedTab = this.addTrustedTab(BROWSER_NEW_TAB_URL, {
tabIndex: tab._tPos + 1,
userContextId: tab.userContextId,
@@ -236,7 +240,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
focusUrlBar: true,
});
resolve(this.selectedBrowser);
@@ -2923,6 +2997,9 @@
@@ -2923,6 +3001,9 @@
schemelessInput,
hasValidUserGestureActivation = false,
textDirectiveUserActivation = false,
@@ -246,7 +250,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} = {}
) {
// all callers of addTab that pass a params object need to pass
@@ -2933,10 +3010,17 @@
@@ -2933,10 +3014,17 @@
);
}
@@ -264,7 +268,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// If we're opening a foreground tab, set the owner by default.
ownerTab ??= inBackground ? null : this.selectedTab;
@@ -2944,6 +3028,7 @@
@@ -2944,6 +3032,7 @@
if (this.selectedTab.owner) {
this.selectedTab.owner = null;
}
@@ -272,7 +276,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// Find the tab that opened this one, if any. This is used for
// determining positioning, and inherited attributes such as the
@@ -2996,6 +3081,21 @@
@@ -2996,6 +3085,21 @@
noInitialLabel,
skipBackgroundNotify,
});
@@ -294,7 +298,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (insertTab) {
// Insert the tab into the tab container in the correct position.
this.#insertTabAtIndex(t, {
@@ -3004,6 +3104,7 @@
@@ -3004,6 +3108,7 @@
ownerTab,
openerTab,
pinned,
@@ -302,7 +306,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
bulkOrderedOpen,
tabGroup: tabGroup ?? openerTab?.group,
});
@@ -3022,6 +3123,7 @@
@@ -3022,6 +3127,7 @@
openWindowInfo,
skipLoad,
triggeringRemoteType,
@@ -310,7 +314,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}));
if (focusUrlBar) {
@@ -3146,6 +3248,12 @@
@@ -3146,6 +3252,12 @@
}
}
@@ -323,7 +327,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// Additionally send pinned tab events
if (pinned) {
this.#notifyPinnedStatus(t);
@@ -3349,10 +3457,10 @@
@@ -3349,10 +3461,10 @@
isAdoptingGroup = false,
isUserTriggered = false,
telemetryUserCreateSource = "unknown",
@@ -335,7 +339,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
if (!color) {
@@ -3373,9 +3481,14 @@
@@ -3373,9 +3485,14 @@
label,
isAdoptingGroup
);
@@ -352,7 +356,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
);
group.addTabs(tabs);
@@ -3496,7 +3609,7 @@
@@ -3496,7 +3613,7 @@
}
this.#handleTabMove(tab, () =>
@@ -361,7 +365,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
);
}
@@ -3698,6 +3811,7 @@
@@ -3698,6 +3815,7 @@
openWindowInfo,
skipLoad,
triggeringRemoteType,
@@ -369,7 +373,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
) {
// If we don't have a preferred remote type (or it is `NOT_REMOTE`), and
@@ -3767,6 +3881,7 @@
@@ -3767,6 +3885,7 @@
openWindowInfo,
name,
skipLoad,
@@ -377,7 +381,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
});
}
@@ -3955,7 +4070,7 @@
@@ -3955,7 +4074,7 @@
// Add a new tab if needed.
if (!tab) {
let createLazyBrowser =
@@ -386,7 +390,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
let url = "about:blank";
if (tabData.entries?.length) {
@@ -3992,8 +4107,10 @@
@@ -3992,8 +4111,10 @@
insertTab: false,
skipLoad: true,
preferredRemoteType,
@@ -398,7 +402,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (select) {
tabToSelect = tab;
}
@@ -4005,7 +4122,8 @@
@@ -4005,7 +4126,8 @@
this.pinTab(tab);
// Then ensure all the tab open/pinning information is sent.
this._fireTabOpen(tab, {});
@@ -408,7 +412,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
let { groupId } = tabData;
const tabGroup = tabGroupWorkingData.get(groupId);
// if a tab refers to a tab group we don't know, skip any group
@@ -4019,7 +4137,10 @@
@@ -4019,7 +4141,10 @@
tabGroup.stateData.id,
tabGroup.stateData.color,
tabGroup.stateData.collapsed,
@@ -420,7 +424,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
);
tabsFragment.appendChild(tabGroup.node);
}
@@ -4064,9 +4185,23 @@
@@ -4064,9 +4189,23 @@
// to remove the old selected tab.
if (tabToSelect) {
let leftoverTab = this.selectedTab;
@@ -436,15 +440,15 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
+ gZenWorkspaces._initialTab._shouldRemove = true;
+ }
+ }
}
+ }
+ else {
+ gZenWorkspaces._tabToRemoveForEmpty = this.selectedTab;
+ }
}
+ this._hasAlreadyInitializedZenSessionStore = true;
if (tabs.length > 1 || !tabs[0].selected) {
this._updateTabsAfterInsert();
@@ -4257,11 +4392,14 @@
@@ -4257,11 +4396,14 @@
if (ownerTab) {
tab.owner = ownerTab;
}
@@ -460,7 +464,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (
!bulkOrderedOpen &&
((openerTab &&
@@ -4273,7 +4411,7 @@
@@ -4273,7 +4415,7 @@
let lastRelatedTab =
openerTab && this._lastRelatedTabMap.get(openerTab);
let previousTab = lastRelatedTab || openerTab || this.selectedTab;
@@ -469,7 +473,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
tabGroup = previousTab.group;
}
if (
@@ -4284,7 +4422,7 @@
@@ -4284,7 +4426,7 @@
) {
elementIndex = Infinity;
} else if (previousTab.visible) {
@@ -478,7 +482,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} else if (previousTab == FirefoxViewHandler.tab) {
elementIndex = 0;
}
@@ -4312,14 +4450,14 @@
@@ -4312,14 +4454,14 @@
}
// Ensure index is within bounds.
if (tab.pinned) {
@@ -497,7 +501,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (pinned && !itemAfter?.pinned) {
itemAfter = null;
@@ -4330,7 +4468,7 @@
@@ -4330,7 +4472,7 @@
this.tabContainer._invalidateCachedTabs();
@@ -506,7 +510,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (this.isTab(itemAfter) && itemAfter.group == tabGroup) {
// Place at the front of, or between tabs in, the same tab group
this.tabContainer.insertBefore(tab, itemAfter);
@@ -4358,7 +4496,11 @@
@@ -4358,7 +4500,11 @@
const tabContainer = pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
@@ -518,7 +522,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
this._updateTabsAfterInsert();
@@ -4366,6 +4508,7 @@
@@ -4366,6 +4512,7 @@
if (pinned) {
this._updateTabBarForPinnedTabs();
}
@@ -526,7 +530,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
TabBarVisibility.update();
}
@@ -4916,6 +5059,7 @@
@@ -4916,6 +5063,7 @@
telemetrySource,
} = {}
) {
@@ -534,7 +538,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window.
if (
@@ -5005,6 +5149,7 @@
@@ -5005,6 +5153,7 @@
if (lastToClose) {
this.removeTab(lastToClose, aParams);
}
@@ -542,7 +546,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} catch (e) {
console.error(e);
}
@@ -5043,6 +5188,12 @@
@@ -5043,6 +5192,12 @@
aTab._closeTimeNoAnimTimerId = Glean.browserTabclose.timeNoAnim.start();
}
@@ -555,7 +559,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate && aTab.closing) {
@@ -5057,6 +5208,9 @@
@@ -5057,6 +5212,9 @@
// state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
let isLastTab = this.#isLastTabInWindow(aTab);
@@ -565,7 +569,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (
!this._beginRemoveTab(aTab, {
closeWindowFastpath: true,
@@ -5105,7 +5259,13 @@
@@ -5105,7 +5263,13 @@
// We're not animating, so we can cancel the animation stopwatch.
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
aTab._closeTimeAnimTimerId = null;
@@ -580,7 +584,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
return;
}
@@ -5239,7 +5399,7 @@
@@ -5239,7 +5403,7 @@
closeWindowWithLastTab != null
? closeWindowWithLastTab
: !window.toolbar.visible ||
@@ -589,7 +593,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here,
@@ -5263,6 +5423,7 @@
@@ -5263,6 +5427,7 @@
newTab = true;
}
@@ -597,7 +601,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -5303,13 +5464,7 @@
@@ -5303,13 +5468,7 @@
aTab._mouseleave();
if (newTab) {
@@ -612,7 +616,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} else {
TabBarVisibility.update();
}
@@ -5442,6 +5597,7 @@
@@ -5442,6 +5601,7 @@
this.tabs[i]._tPos = i;
}
@@ -620,7 +624,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (!this._windowIsClosing) {
// update tab close buttons state
this.tabContainer._updateCloseButtons();
@@ -5663,6 +5819,7 @@
@@ -5663,6 +5823,7 @@
}
let excludeTabs = new Set(aExcludeTabs);
@@ -628,7 +632,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor.
@@ -5675,13 +5832,13 @@
@@ -5675,13 +5836,13 @@
!excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) {
@@ -644,7 +648,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
);
let tab = this.tabContainer.findNextTab(aTab, {
@@ -5697,7 +5854,7 @@
@@ -5697,7 +5858,7 @@
}
if (tab) {
@@ -653,7 +657,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
// If no qualifying visible tab was found, see if there is a tab in
@@ -5718,7 +5875,7 @@
@@ -5718,7 +5879,7 @@
});
}
@@ -662,7 +666,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
_blurTab(aTab) {
@@ -5729,7 +5886,7 @@
@@ -5729,7 +5890,7 @@
* @returns {boolean}
* False if swapping isn't permitted, true otherwise.
*/
@@ -671,7 +675,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// Do not allow transfering a private tab to a non-private window
// and vice versa.
if (
@@ -5783,6 +5940,7 @@
@@ -5783,6 +5944,7 @@
// fire the beforeunload event in the process. Close the other
// window if this was its last tab.
if (
@@ -679,7 +683,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
!remoteBrowser._beginRemoveTab(aOtherTab, {
adoptedByTab: aOurTab,
closeWindowWithLastTab: true,
@@ -5794,7 +5952,7 @@
@@ -5794,7 +5956,7 @@
// If this is the last tab of the window, hide the window
// immediately without animation before the docshell swap, to avoid
// about:blank being painted.
@@ -688,7 +692,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (closeWindow) {
let win = aOtherTab.ownerGlobal;
win.windowUtils.suppressAnimation(true);
@@ -5918,11 +6076,13 @@
@@ -5918,11 +6080,13 @@
}
// Finish tearing down the tab that's going away.
@@ -702,7 +706,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
this.setTabTitle(aOurTab);
@@ -6124,10 +6284,10 @@
@@ -6124,10 +6288,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
}
@@ -715,7 +719,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
aTab.selected ||
aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -6185,7 +6345,8 @@
@@ -6185,7 +6349,8 @@
*
* @param {MozTabbrowserTab|MozTabbrowserTabGroup|MozTabbrowserTabGroup.labelElement} aTab
*/
@@ -725,7 +729,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (this.tabs.length == 1) {
return null;
}
@@ -6209,12 +6370,14 @@
@@ -6209,12 +6374,14 @@
}
// tell a new window to take the "dropped" tab
@@ -741,7 +745,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
/**
@@ -6319,7 +6482,7 @@
@@ -6319,7 +6486,7 @@
* `true` if element is a `<tab-group>`
*/
isTabGroup(element) {
@@ -750,7 +754,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
}
/**
@@ -6404,8 +6567,8 @@
@@ -6404,8 +6571,8 @@
}
// Don't allow mixing pinned and unpinned tabs.
@@ -761,7 +765,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount);
}
@@ -6431,10 +6594,16 @@
@@ -6431,10 +6598,16 @@
this.#handleTabMove(
element,
() => {
@@ -780,7 +784,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
if (neighbor && this.isTab(element) && tabIndex > element._tPos) {
neighbor.after(element);
} else {
@@ -6492,23 +6661,28 @@
@@ -6492,23 +6665,28 @@
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group;
@@ -815,7 +819,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} else if (!element.pinned && targetElement && targetElement.pinned) {
// If the caller asks to move an unpinned element next to a pinned
// tab, move the unpinned element to be the first unpinned element
@@ -6521,14 +6695,34 @@
@@ -6521,14 +6699,34 @@
// move the tab group right before the first unpinned tab.
// 4. Moving a tab group and the first unpinned tab is grouped:
// move the tab group right before the first unpinned tab's tab group.
@@ -851,7 +855,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
element.pinned
? this.tabContainer.pinnedTabsContainer
: this.tabContainer;
@@ -6537,7 +6731,7 @@
@@ -6537,7 +6735,7 @@
element,
() => {
if (moveBefore) {
@@ -860,7 +864,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
} else if (targetElement) {
targetElement.after(element);
} else {
@@ -6607,10 +6801,10 @@
@@ -6607,10 +6805,10 @@
* @param {TabMetricsContext} [metricsContext]
*/
moveTabToGroup(aTab, aGroup, metricsContext) {
@@ -873,7 +877,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
return;
}
if (aTab.group && aTab.group.id === aGroup.id) {
@@ -6656,6 +6850,7 @@
@@ -6656,6 +6854,7 @@
let state = {
tabIndex: tab._tPos,
@@ -881,7 +885,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
};
if (tab.visible) {
state.elementIndex = tab.elementIndex;
@@ -6682,7 +6877,7 @@
@@ -6682,7 +6881,7 @@
let changedTabGroup =
previousTabState.tabGroupId != currentTabState.tabGroupId;
@@ -890,7 +894,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
tab.dispatchEvent(
new CustomEvent("TabMove", {
bubbles: true,
@@ -6723,6 +6918,10 @@
@@ -6723,6 +6922,10 @@
moveActionCallback();
@@ -901,17 +905,16 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
// Clear tabs cache after moving nodes because the order of tabs may have
// changed.
this.tabContainer._invalidateCachedTabs();
@@ -6815,6 +7014,9 @@
@@ -6815,6 +7018,8 @@
params.userContextId = aTab.getAttribute("usercontextid");
}
let newTab = this.addWebTab("about:blank", params);
+ newTab._zenContentsVisible = true;
+ newTab.zenStaticLabel = aTab.zenStaticLabel;
+ newTab.zenStaticIcon = aTab.zenStaticIcon;
let newBrowser = this.getBrowserForTab(newTab);
aTab.container.tabDragAndDrop.finishAnimateTabMove();
@@ -7623,7 +7825,7 @@
@@ -7623,7 +7828,7 @@
// preventDefault(). It will still raise the window if appropriate.
break;
}
@@ -920,7 +923,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
window.focus();
aEvent.preventDefault();
break;
@@ -7640,7 +7842,6 @@
@@ -7640,7 +7845,6 @@
}
case "TabGroupCollapse":
aEvent.target.tabs.forEach(tab => {
@@ -928,7 +931,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
});
break;
case "TabGroupCreateByUser":
@@ -8589,6 +8790,7 @@
@@ -8589,6 +8793,7 @@
aWebProgress.isTopLevel
) {
this.mTab.setAttribute("busy", "true");
@@ -936,7 +939,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected;
}
@@ -8670,6 +8872,7 @@
@@ -8670,6 +8875,7 @@
// known defaults. Note we use the original URL since about:newtab
// redirects to a prerendered page.
const shouldRemoveFavicon =
@@ -944,7 +947,7 @@ index 42027bfa55eab8ea9298a7d425f2ded45188f7f3..b01c89f2e0d3b44dddaa01a20b4da13d
!this.mBrowser.mIconURL &&
!ignoreBlank &&
!(originalLocation.spec in FAVICON_DEFAULTS);
@@ -9623,7 +9826,7 @@ var TabContextMenu = {
@@ -9623,7 +9829,7 @@ var TabContextMenu = {
);
contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected;

View File

@@ -1,7 +1,16 @@
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..04144081560f1678dc9673736ef2bd9d9ca3f478 100644
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..6d5ae983446bc778f3075d79f8ff14748dd7756f 100644
--- a/browser/components/tabbrowser/content/tabs.js
+++ b/browser/components/tabbrowser/content/tabs.js
@@ -235,7 +235,7 @@
true
)
? new window.TabStacking(this)
- : new window.TabDragAndDrop(this);
+ : Services.prefs.getBoolPref("zen.tabs.use-legacy-drag-and-drop") ? new window.TabDragAndDrop(this) : new window.ZenDragAndDrop(this);
this.tabDragAndDrop.init();
}
@@ -436,7 +436,7 @@
// and we're not hitting the scroll buttons.
if (

View File

@@ -0,0 +1,47 @@
diff --git a/widget/cocoa/nsDragService.mm b/widget/cocoa/nsDragService.mm
index f1614b823a859ff8fbc74982f205bb1f2ef29beb..897c24846a97c132babe3ad79da12ebfcec90484 100644
--- a/widget/cocoa/nsDragService.mm
+++ b/widget/cocoa/nsDragService.mm
@@ -23,6 +23,7 @@
#include "mozilla/PresShell.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/nsZenDragAndDrop.h"
#include "nsIContent.h"
#include "nsCocoaUtils.h"
#include "mozilla/gfx/2D.h"
@@ -148,6 +149,10 @@
bitsPerPixel:32];
uint8_t* dest = [imageRep bitmapData];
+ auto drag_translucency = DRAG_TRANSLUCENCY;
+ if (auto zenDragAndDrop = zen::nsZenDragAndDrop::GetZenDragAndDropInstance()) {
+ drag_translucency = zenDragAndDrop->GetDragImageOpacity();
+ }
for (uint32_t i = 0; i < height; ++i) {
uint8_t* src = map.mData + i * map.mStride;
for (uint32_t j = 0; j < width; ++j) {
@@ -155,15 +160,15 @@
// is premultipled here. Also, Quartz likes RGBA, so do that translation
// as well.
#ifdef IS_BIG_ENDIAN
- dest[0] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
- dest[1] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
- dest[2] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
- dest[3] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
+ dest[0] = uint8_t(src[1] * drag_translucency);
+ dest[1] = uint8_t(src[2] * drag_translucency);
+ dest[2] = uint8_t(src[3] * drag_translucency);
+ dest[3] = uint8_t(src[0] * drag_translucency);
#else
- dest[0] = uint8_t(src[2] * DRAG_TRANSLUCENCY);
- dest[1] = uint8_t(src[1] * DRAG_TRANSLUCENCY);
- dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
- dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
+ dest[0] = uint8_t(src[2] * drag_translucency);
+ dest[1] = uint8_t(src[1] * drag_translucency);
+ dest[2] = uint8_t(src[0] * drag_translucency);
+ dest[3] = uint8_t(src[3] * drag_translucency);
#endif
src += 4;
dest += 4;

View File

@@ -11,4 +11,6 @@
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenMods.mjs", { global: "current" });
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs", { global: "current" });
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenSessionStore.mjs", { global: "current" });
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenDragAndDrop.js", this);
}

View File

@@ -1223,6 +1223,7 @@ window.gZenVerticalTabsManager = {
// Always move the splitter next to the sidebar
const splitter = document.getElementById('zen-sidebar-splitter');
splitter.addEventListener('dragover', gBrowser.tabContainer);
this.navigatorToolbox.after(splitter);
window.dispatchEvent(new Event('resize'));
if (!isCompactMode) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
Classes = [
{
'cid': '{f8714110-1fb1-4129-abad-887a64e4085e}',
'interfaces': ['nsIZenDragAndDrop'],
'contract_ids': ['@mozilla.org/zen/drag-and-drop;1'],
'type': 'zen::nsZenDragAndDrop',
'headers': ['mozilla/nsZenDragAndDrop.h'],
'processes': ProcessSelector.MAIN_PROCESS_ONLY,
},
]

View File

@@ -0,0 +1,5 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
content/browser/zen-components/ZenDragAndDrop.js (../../zen/drag-and-drop/ZenDragAndDrop.js)

View File

@@ -0,0 +1,31 @@
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
"nsIZenDragAndDrop.idl",
]
EXPORTS.mozilla += [
"nsZenDragAndDrop.h",
]
SOURCES += [
"nsZenDragAndDrop.cpp",
]
XPCOM_MANIFESTS += [
"components.conf",
]
include("/ipc/chromium/chromium-config.mozbuild")
LOCAL_INCLUDES += [
"/dom/base",
"/layout/base",
"/widget",
]
FINAL_LIBRARY = "xul"
XPIDL_MODULE = "zen_dnd"

View File

@@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
/**
* @brief Interface for Zen's drag and drop functionality.
*/
[scriptable, uuid(f8714110-1fb1-4129-abad-887a64e4085e)]
interface nsIZenDragAndDrop : nsISupports {
/**
* @brief Indicate that a drag operation has started. Note
* that this should only be called for zen's drag and drop
* operations for the tabs.
* @param opacity The opacity of the drag image.
*/
void onDragStart(in float opacity);
/**
* @brief Indicate that a drag operation has ended.
*/
void onDragEnd();
};

View File

@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsZenDragAndDrop.h"
#include "nsBaseDragService.h"
namespace zen {
namespace {
static constexpr auto kZenDefaultDragImageOpacity =
#if defined(MOZ_WIDGET_GTK)
// For GTK, the default is 0.5 (DRAG_IMAGE_ALPHA_LEVEL) to match
// the native behavior. Make sure its synced with the following variable:
// https://searchfox.org/firefox-main/rev/14c08f0368ead8bfdddec62f43e0bb5c8fd61289/widget/gtk/nsDragService.cpp#75
0.5f;
#else
// For other platforms, the default is whatever the value of DRAG_TRANSLUCENCY
// is, defined in nsBaseDragService.h
DRAG_TRANSLUCENCY;
#endif
} // namespace: <empty>
// Use the macro to inject all of the definitions for nsISupports.
NS_IMPL_ISUPPORTS(nsZenDragAndDrop, nsIZenDragAndDrop)
nsZenDragAndDrop::nsZenDragAndDrop() {
(void)this->OnDragEnd();
}
auto nsZenDragAndDrop::GetZenDragAndDropInstance() -> nsCOMPtr<nsZenDragAndDrop> {
return do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID);
}
NS_IMETHODIMP
nsZenDragAndDrop::OnDragStart(float opacity) {
mDragImageOpacity = opacity;
return NS_OK;
}
NS_IMETHODIMP
nsZenDragAndDrop::OnDragEnd() {
mDragImageOpacity = kZenDefaultDragImageOpacity;
return NS_OK;
}
} // namespace: zen

View File

@@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ZenDragAndDrop_h__
#define mozilla_ZenDragAndDrop_h__
#include "nsIZenDragAndDrop.h"
#include "nsCOMPtr.h"
#define ZEN_BOOSTS_BACKEND_CONTRACTID "@mozilla.org/zen/drag-and-drop;1"
namespace zen {
/**
* @brief Implementation of the nsIZenDragAndDrop interface.
* When we want to do a drag and drop operation, web standards
* don't really allow much customization of the drag image.
* This class allows Zen to have more control over the drag
* and drop operations for the tabs.
*/
class nsZenDragAndDrop final : public nsIZenDragAndDrop {
NS_DECL_ISUPPORTS
NS_DECL_NSIZENDRAGANDDROP
public:
explicit nsZenDragAndDrop();
auto GetDragImageOpacity() const { return mDragImageOpacity; }
/**
* @brief Get the singleton instance of nsZenDragAndDrop. There may be occasions
* where it won't be available (e.g. on the content process), so this may return
* nullptr.
* @return nsZenDragAndDrop* The singleton instance, or nullptr if not available
*/
static auto GetZenDragAndDropInstance() -> nsCOMPtr<nsZenDragAndDrop>;
private:
~nsZenDragAndDrop() = default;
float mDragImageOpacity{};
};
} // namespace zen
#endif

View File

@@ -6,7 +6,7 @@ export class nsZenFolder extends MozTabbrowserTabGroup {
#initialized = false;
static markup = `
<hbox class="tab-group-label-container" pack="center">
<hbox class="tab-group-label-container zen-drop-target" pack="center">
<html:div class="tab-group-folder-icon"/>
<label class="tab-group-label" role="button"/>
<image class="tab-reset-button reset-icon" role="button" keyNav="false" data-l10n-id="zen-folders-unload-all-tooltip"/>

View File

@@ -33,8 +33,6 @@ function formatRelativeTime(timestamp) {
class nsZenFolders extends nsZenDOMOperatedFeature {
#ZEN_MAX_SUBFOLDERS = Services.prefs.getIntPref('zen.folders.max-subfolders', 5);
#ZEN_EDGE_ZONE_THRESHOLD =
Services.prefs.getIntPref('zen.view.drag-and-drop.edge-zone-threshold', 25) / 100;
#popup = null;
#popupTimer = null;
@@ -1065,18 +1063,16 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
* @param {Array<MozTabbrowserTab>|null} movingTabs The tabs being moved.
*/
highlightGroupOnDragOver(folder, movingTabs) {
if (folder === this.#lastHighlightedGroup) return;
if (folder === this.#lastHighlightedGroup) return true;
const tab = movingTabs ? movingTabs[0] : null;
if (this.#lastHighlightedGroup && this.#lastHighlightedGroup !== folder) {
this.#lastHighlightedGroup.removeAttribute('selected');
if (this.#lastHighlightedGroup.collapsed) {
this.updateFolderIcon(this.#lastHighlightedGroup, 'close');
}
this.#lastHighlightedGroup = null;
}
if (
folder &&
folder?.isZenFolder &&
(!folder.hasAttribute('split-view-group') || !folder.hasAttribute('selected')) &&
folder !== tab?.group &&
!(
@@ -1084,13 +1080,13 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
movingTabs?.some((t) => gBrowser.isTabGroupLabel(t))
)
) {
folder.setAttribute('selected', 'true');
folder.style.transform = '';
if (folder.collapsed) {
this.updateFolderIcon(folder, 'open');
}
this.#lastHighlightedGroup = folder;
return true;
}
return false;
}
/**
@@ -1103,54 +1099,6 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
}
/**
* Handles the dragover logic when dragging a tab or tab group label over another tab group label.
* This function determines where the dragged item should be visually dropped (before/after the group, or inside it)
* and updates related styling and highlighting.
*
* @param {MozTabbrowserTabGroupLabel} currentDropElement The tab group label currently being dragged over.
* @param {MozTabbrowserTab|MozTabbrowserTabGroupLabel} draggedTab The tab or tab group label being dragged.
* @param {number} overlapPercent The percentage of overlap between the dragged item and the drop target.
* @param {Array<MozTabbrowserTab>} movingTabs An array of tabs that are currently being dragged together.
* @param {boolean} currentDropBefore Indicates if the current drop position is before the middle of the drop element.
* @param {string|undefined} currentColorCode The current color code for dragover highlighting.
* @returns {{dropElement: MozTabbrowserTabGroup|MozTabbrowserTab|MozTabbrowserTabGroupLabel, colorCode: string|undefined, dropBefore: boolean}}
* An object containing the updated drop element, color code for highlighting, and drop position.
*/
handleDragOverTabGroupLabel(
currentDropElement,
draggedTab,
overlapPercent,
movingTabs,
currentDropBefore,
currentColorCode
) {
let dropElement = currentDropElement;
let dropBefore = currentDropBefore;
let colorCode = currentColorCode;
const dropElementGroup = dropElement?.isZenFolder ? dropElement : dropElement?.group;
const isSplitGroup = dropElement?.group?.hasAttribute('split-view-group');
let firstGroupElem = dropElementGroup?.groupStartElement.nextElementSibling;
if (gBrowser.isTabGroup(firstGroupElem)) firstGroupElem = firstGroupElem.labelElement;
const isInMiddleZone =
overlapPercent >= this.#ZEN_EDGE_ZONE_THRESHOLD &&
overlapPercent <= 1 - this.#ZEN_EDGE_ZONE_THRESHOLD;
const shouldDropInside = isInMiddleZone && !isSplitGroup;
if (shouldDropInside) {
dropElement = firstGroupElem;
dropBefore = true;
this.highlightGroupOnDragOver(dropElementGroup, movingTabs);
} else {
colorCode = undefined;
this.highlightGroupOnDragOver(null);
}
return { dropElement, colorCode, dropBefore };
}
#normalizeGroupItems(items) {
return items
.filter((item) => !item.hasAttribute('zen-empty-tab'))

View File

@@ -8,6 +8,7 @@ EXTRA_PP_COMPONENTS += [
DIRS += [
"common",
"drag-and-drop",
"glance",
"mods",
"tests",

View File

@@ -464,6 +464,12 @@ class nsZenWindowSync {
}
const relativeTab = this.#getItemFromWindow(aWindow, originalSibling.id);
if (relativeTab) {
gBrowser.tabContainer.tabDragAndDrop.handle_drop_transition(
relativeTab,
aTargetItem,
[aTargetItem],
false
);
relativeTab.after(aTargetItem);
}
});

View File

@@ -228,9 +228,26 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
}
#getDragImageForSplit(tab) {
const element = window.MozXULElement.parseXULToFragment(
`
<vbox id="zen-split-view-drag-image">
<image />
<label />
</vbox>
`
).querySelector('#zen-split-view-drag-image');
const image = element.querySelector('image');
const label = element.querySelector('label');
image.src = tab.getAttribute('image');
label.textContent = tab.label;
document.documentElement.appendChild(element);
this._dndElement = element;
return element;
}
onBrowserDragOverToSplit(event) {
if (this.fakeBrowser) {
this.onBrowserDragEndToSplit(event);
return;
}
var dt = event.dataTransfer;
@@ -239,23 +256,20 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
// tab copy or move
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// not our drop then
if (!draggedTab || gBrowser.selectedTab.hasAttribute('zen-empty-tab')) {
if (!gBrowser.isTab(draggedTab) || gBrowser.selectedTab.hasAttribute('zen-empty-tab')) {
return;
}
gBrowser.tabContainer.tabDragAndDrop.finishMoveTogetherSelectedTabs(draggedTab);
}
if (
!draggedTab ||
this._canDrop ||
this._hasAnimated ||
this.fakeBrowser ||
!this._lastOpenedTab ||
(this._lastOpenedTab &&
this._lastOpenedTab.getAttribute('zen-workspace-id') !==
draggedTab.getAttribute('zen-workspace-id') &&
!this._lastOpenedTab.hasAttribute('zen-essential')) ||
draggedTab === this._lastOpenedTab
(this._lastOpenedTab.getAttribute('zen-workspace-id') !==
draggedTab.getAttribute('zen-workspace-id') &&
!this._lastOpenedTab.hasAttribute('zen-essential'))
) {
this._lastOpenedTab = gBrowser.selectedTab;
}
if (!draggedTab || this._canDrop || this._hasAnimated || this.fakeBrowser) {
return;
}
if (draggedTab.splitView) {
@@ -285,19 +299,25 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
return;
}
dt.mozCursor = 'default';
if (!this._dndElement) {
const originalDNDArgs = gBrowser.tabContainer.tabDragAndDrop.originalDragImageArgs;
requestAnimationFrame(() => {
dt.updateDragImage(
this.#getDragImageForSplit(draggedTab),
originalDNDArgs[1],
originalDNDArgs[2]
);
});
gBrowser.tabContainer.tabDragAndDrop.clearDragOverVisuals();
}
const oldTab = this._lastOpenedTab;
this._canDrop = true;
Services.zen.playHapticFeedback();
{
this._draggingTab = draggedTab;
gBrowser.selectedTab = oldTab;
this._hasAnimated = true;
this.tabBrowserPanel.setAttribute('dragging-split', 'true');
for (const tab of gBrowser.tabs) {
tab.style.removeProperty('transform');
if (tab.group) {
tab.group.style.removeProperty('transform');
}
}
// Add a min width to all the browser elements to prevent them from resizing
const panelsWidth = gBrowser.tabbox.getBoundingClientRect().width;
let numOfTabsToDivide = 2;
@@ -330,11 +350,6 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
this.fakeBrowser.setAttribute('has-split-view', 'true');
}
gBrowser.tabbox.appendChild(this.fakeBrowser);
this.fakeBrowser.style.setProperty(
'--zen-split-view-fake-icon',
`url(${draggedTab.getAttribute('image')})`
);
draggedTab._visuallySelected = true;
this.fakeBrowser.setAttribute('side', side);
this._finishAllAnimatingPromise = Promise.all([
gZenUIManager.motion.animate(
@@ -371,13 +386,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
]);
if (this._finishAllAnimatingPromise) {
this._finishAllAnimatingPromise.then(() => {
draggedTab.linkedBrowser.docShellIsActive = false;
draggedTab.linkedBrowser
.closest('.browserSidebarContainer')
.classList.remove('deck-selected');
if (draggedTab !== oldTab) {
draggedTab.linkedBrowser.docShellIsActive = false;
draggedTab.linkedBrowser
.closest('.browserSidebarContainer')
.classList.remove('deck-selected');
}
this.fakeBrowser.addEventListener('dragleave', this.onBrowserDragEndToSplit);
this._canDrop = true;
draggedTab._visuallySelected = true;
});
}
}
@@ -390,12 +406,11 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
const panelsRect = gBrowser.tabbox.getBoundingClientRect();
const fakeBrowserRect = this.fakeBrowser && this.fakeBrowser.getBoundingClientRect();
if (
((event.target.closest('#tabbrowser-tabbox') && event.target != this.fakeBrowser) ||
(fakeBrowserRect &&
event.clientX > fakeBrowserRect.left &&
event.clientX < fakeBrowserRect.left + fakeBrowserRect.width &&
event.clientY > fakeBrowserRect.top &&
event.clientY < fakeBrowserRect.top + fakeBrowserRect.height) ||
((fakeBrowserRect &&
event.clientX > fakeBrowserRect.left &&
event.clientX < fakeBrowserRect.left + fakeBrowserRect.width &&
event.clientY > fakeBrowserRect.top &&
event.clientY < fakeBrowserRect.top + fakeBrowserRect.height) ||
(event.screenX === 0 && event.screenY === 0)) && // It's equivalent to 0 if the event has been dropped
!cancelled
) {
@@ -415,50 +430,47 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
if (!this.fakeBrowser) {
return;
}
this.fakeBrowser.classList.add('fade-out');
const side = this.fakeBrowser.getAttribute('side');
if (this._draggingTab) this._draggingTab.setAttribute('zen-has-splitted', 'true');
this._lastOpenedTab = gBrowser.selectedTab;
this._draggingTab = null;
try {
this._canDrop = false;
Promise.all([
gZenUIManager.motion.animate(
gBrowser.tabbox,
side === 'left'
gBrowser.tabContainer.tabDragAndDrop.clearSpaceSwitchTimer();
event.dataTransfer.updateDragImage(
...gBrowser.tabContainer.tabDragAndDrop.originalDragImageArgs
);
this._canDrop = false;
Promise.all([
gZenUIManager.motion.animate(
gBrowser.tabbox,
side === 'left'
? {
paddingLeft: [`${halfWidth}px`, 0],
}
: {
paddingRight: [`${halfWidth}px`, 0],
},
{
duration: 0.1,
easing: 'ease-out',
}
),
gZenUIManager.motion.animate(
this.fakeBrowser,
{
width: [`${halfWidth - padding * 2}px`, 0],
...(side === 'left'
? {
paddingLeft: [`${halfWidth}px`, 0],
marginLeft: [`${-halfWidth}px`, 0],
}
: {
paddingRight: [`${halfWidth}px`, 0],
},
{
duration: 0.1,
easing: 'ease-out',
}
),
gZenUIManager.motion.animate(
this.fakeBrowser,
{
width: [`${halfWidth - padding * 2}px`, 0],
...(side === 'left'
? {
marginLeft: [`${-halfWidth}px`, 0],
}
: {}),
},
{
duration: 0.1,
easing: 'ease-out',
}
),
]).then(() => {
this._maybeRemoveFakeBrowser();
});
} catch {
this._canDrop = false;
: {}),
},
{
duration: 0.1,
easing: 'ease-out',
}
),
]).finally(() => {
this._maybeRemoveFakeBrowser();
}
});
}
/**
@@ -1667,11 +1679,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
_maybeRemoveFakeBrowser(select = true) {
gBrowser.tabbox.removeAttribute('style');
this.tabBrowserPanel.removeAttribute('dragging-split');
if (this._dndElement) {
this._dndElement.remove();
delete this._dndElement;
}
if (this.fakeBrowser) {
delete this._hasAnimated;
this.fakeBrowser.remove();
this.fakeBrowser = null;
if (this._draggingTab) this._draggingTab._visuallySelected = false;
if (select) {
gBrowser.selectedTab = this._draggingTab;
this._draggingTab = null;
@@ -1728,6 +1743,12 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
return false;
}
const droppedOnTab = gZenGlanceManager.getTabOrGlanceParent(gBrowser.getTabForBrowser(browser));
if (droppedOnTab === this._draggingTab) {
this.createEmptySplit(dropSide == 'right');
return true;
}
gBrowser.selectedTab = this._draggingTab;
this._draggingTab = null;
const browserContainer = draggedTab.linkedBrowser?.closest('.browserSidebarContainer');
@@ -1735,7 +1756,6 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
browserContainer.style.opacity = '0';
}
const droppedOnTab = gZenGlanceManager.getTabOrGlanceParent(gBrowser.getTabForBrowser(browser));
if (droppedOnTab && droppedOnTab !== draggedTab) {
// Calculate which side of the target browser the drop occurred
// const browserRect = browser.getBoundingClientRect();
@@ -1977,13 +1997,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
}
}
createEmptySplit() {
createEmptySplit(rightSide = true) {
const selectedTab = gBrowser.selectedTab;
const emptyTab = gZenWorkspaces._emptyTab;
let tabs = rightSide ? [selectedTab, emptyTab] : [emptyTab, selectedTab];
const data = {
tabs: [selectedTab, emptyTab],
tabs: tabs,
gridType: 'grid',
layoutTree: this.calculateLayoutTree([selectedTab, emptyTab], 'grid'),
layoutTree: this.calculateLayoutTree(tabs, 'grid'),
};
this._data.push(data);
this.activateSplitView(data);
@@ -2016,7 +2037,11 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
this.removeTabFromGroup(emptyTab, groupIndex, { forUnsplit: true });
gBrowser.selectedTab = selectedTab;
this.resetTabState(emptyTab, false);
this.splitTabs([selectedTab, newSelectedTab], 'grid', 1);
this.splitTabs(
rightSide ? [selectedTab, newSelectedTab] : [newSelectedTab, selectedTab],
'grid',
rightSide ? 1 : 0
);
} else {
cleanup();
}

View File

@@ -210,31 +210,28 @@
right: var(--zen-element-separation);
}
}
}
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 3.5rem;
pointer-events: none;
height: 3.5rem;
background: var(--zen-split-view-fake-icon);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
opacity: 0.8;
transition: opacity 0.2s;
transition-delay: 0.1s;
#zen-split-view-drag-image {
width: 200px;
height: 250px;
border-radius: 16px;
background: black;
justify-content: center;
align-items: center;
padding: 10px;
gap: 20px;
position: relative;
@starting-style {
opacity: 0;
}
& image {
width: 24px;
height: 24px;
}
&.fade-out::after {
opacity: 0;
transition-delay: 0s;
& label {
color: white;
font-size: 14px;
font-weight: bold;
text-align: center;
}
}

View File

@@ -84,12 +84,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
onTabIconChanged(tab, url = null) {
tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } }));
const iconUrl = url ?? tab.iconImage.src;
if (tab.hasAttribute('zen-essential')) {
tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`);
this.setEssentialTabIcon(tab, url);
}
}
setEssentialTabIcon(tab, url = null) {
const iconUrl = url ?? tab.getAttribute('image') ?? '';
tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`);
}
_onTabResetPinButton(event, tab) {
event.stopPropagation();
this._resetTabToStoredState(tab);
@@ -534,15 +538,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
moveToAnotherTabContainerIfNecessary(event, movingTabs) {
movingTabs = [...movingTabs];
if (!this.enabled) {
return false;
}
movingTabs = [...movingTabs];
try {
const pinnedTabsTarget =
event.target.closest('.zen-current-workspace-indicator') || this._isGoingToPinnedTabs;
const pinnedTabsTarget = event.target.closest(
':is(.zen-current-workspace-indicator, .zen-workspace-pinned-tabs-section)'
);
const essentialTabsTarget = event.target.closest('.zen-essentials-container');
const tabsTarget = !this._isGoingToPinnedTabs;
const tabsTarget = !pinnedTabsTarget;
// TODO: Solve the issue of adding a tab between two groups
// Remove group labels from the moving tabs and replace it
@@ -685,9 +690,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
removeTabContainersDragoverClass(hideIndicator = true) {
if (this._dragIndicator) {
Services.zen.playHapticFeedback();
}
this.dragIndicator.remove();
this._dragIndicator = null;
if (hideIndicator) {
@@ -695,95 +697,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
}
onDragFinish() {
for (const item of this.dragShiftableItems) {
item.style.transform = '';
}
delete this._topToNormalTabs;
for (const item of gBrowser.tabContainer.ariaFocusableItems) {
if (gBrowser.isTab(item)) {
let isVisible = true;
let parent = item.group;
while (parent) {
if (!parent.visible) {
isVisible = false;
break;
}
parent = parent.group;
}
if (!isVisible) {
continue;
}
}
const itemToAnimate =
item.group?.hasAttribute('split-view-group') || gBrowser.isTabGroupLabel(item)
? item.group
: item;
itemToAnimate.style.removeProperty('--zen-folder-indent');
}
this.removeTabContainersDragoverClass();
}
get dragShiftableItems() {
const separator = gZenWorkspaces.pinnedTabsContainer.querySelector(
'.pinned-tabs-container-separator'
);
// Make sure to always return the separator at the start of the array
return Services.prefs.getBoolPref('zen.view.show-newtab-button-top')
? [separator, gZenWorkspaces.activeWorkspaceElement.newTabButton]
: [separator];
}
animateSeparatorMove(movingTabs, dropElement, isPinned) {
let draggedTab = movingTabs[0];
if (gBrowser.isTabGroupLabel(draggedTab) && draggedTab.group.isZenFolder) {
this._isGoingToPinnedTabs = true;
return;
}
if (draggedTab?.group?.hasAttribute('split-view-group')) {
draggedTab = draggedTab.group;
}
const itemsToCheck = this.dragShiftableItems;
let translate = movingTabs[isPinned ? movingTabs.length - 1 : 0].getBoundingClientRect().top;
if (isPinned) {
const rect = draggedTab.getBoundingClientRect();
translate += rect.height;
}
const draggingTabHeight = movingTabs.reduce((acc, item) => {
return acc + window.windowUtils.getBoundsWithoutFlushing(item).height;
}, 0);
if (typeof this._topToNormalTabs === 'undefined') {
const rects = itemsToCheck.map((item) => window.windowUtils.getBoundsWithoutFlushing(item));
this._topToNormalTabs = rects[0].top + rects.at(-1).height / (isPinned ? 2 : 4);
}
let topToNormalTabs = this._topToNormalTabs;
const isGoingToPinnedTabs =
translate < topToNormalTabs && gBrowser.pinnedTabCount - gBrowser._numZenEssentials > 0;
const multiplier = isGoingToPinnedTabs !== isPinned ? (isGoingToPinnedTabs ? 1 : -1) : 0;
this._isGoingToPinnedTabs = isGoingToPinnedTabs;
if (!dropElement) {
itemsToCheck.forEach((item) => {
item.style.transform = `translateY(${draggingTabHeight * multiplier}px)`;
});
}
}
getLastTabBound(lastBound, lastTab, isDraggingFolder = false) {
if (!lastTab.pinned || isDraggingFolder) {
return lastBound;
}
const shiftedItems = this.dragShiftableItems;
let totalHeight = shiftedItems.reduce((acc, item) => {
return acc + window.windowUtils.getBoundsWithoutFlushing(item).height;
}, 0);
if (shiftedItems.length === 1) {
// Means the new tab button is not at the top or not visible
const lastTabRect = window.windowUtils.getBoundsWithoutFlushing(lastTab);
totalHeight += lastTabRect.height;
}
return lastBound + totalHeight + 6;
}
get dragIndicator() {
if (!this._dragIndicator) {
this._dragIndicator = document.createElement('div');

View File

@@ -715,7 +715,6 @@
}
& .zen-essentials-container {
will-change: transform;
justify-content: center;
grid-template-columns: 1fr !important;
padding: 0 !important;
@@ -1108,8 +1107,6 @@
}
.zen-essentials-container {
will-change: transform;
overflow: hidden;
gap: 4px;
transition:
@@ -1166,7 +1163,7 @@
}
}
.zen-essentials-container > .tabbrowser-tab,
.tabbrowser-tab[zen-essential='true'],
#zen-welcome-initial-essentials-browser-sidebar-essentials .tabbrowser-tab {
--toolbarbutton-inner-padding: 0;
max-width: unset;
@@ -1288,9 +1285,9 @@
width: calc(var(--indicator-width) - 2 * var(--zen-drag-indicator-height) - 4px);
height: var(--zen-drag-indicator-height);
transition:
top 0.1s ease-out,
left 0.1s ease-out,
width 0.1s ease-out;
top 0.05s ease-out,
left 0.05s ease-out,
width 0.05s ease-out;
&::before {
left: calc(-2 * var(--zen-drag-indicator-height));
@@ -1373,7 +1370,14 @@
}
}
.tabbrowser-tab[zen-dragtarget],
.tab-group-label-container[zen-dragtarget] {
z-index: 9 !important;
/* Drag and drop */
#zen-dragover-background {
position: absolute;
z-index: -1;
/* Extra width to cover the sidebar splitter */
width: calc(100% + var(--zen-toolbox-padding));
left: 0;
pointer-events: none;
background: var(--zen-primary-color);
}

View File

@@ -43,7 +43,7 @@ export class nsZenWorkspace extends MozXULElement {
static get markup() {
return `
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1" context="zenWorkspaceMoreActions">
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator zen-drop-target" flex="1" context="zenWorkspaceMoreActions">
<stack class="zen-current-workspace-indicator-stack">
<image class="zen-current-workspace-indicator-chevron" />
<hbox class="zen-current-workspace-indicator-icon" />

View File

@@ -1568,11 +1568,7 @@ class nsZenWorkspaces {
}
async changeWorkspace(workspace, ...args) {
if (
!this.workspaceEnabled ||
this.#inChangingWorkspace ||
gNavToolbox.hasAttribute('movingtab')
) {
if (!this.workspaceEnabled || this.#inChangingWorkspace) {
return;
}
this.#inChangingWorkspace = true;
@@ -2433,7 +2429,7 @@ class nsZenWorkspaces {
return workspaceData;
}
async updateTabsContainers(target = undefined, forAnimation = false) {
updateTabsContainers(target = undefined, forAnimation = false) {
this.makeSureEmptyTabIsFirst();
if (target && !target.target?.parentNode) {
target = null;
@@ -2443,7 +2439,7 @@ class nsZenWorkspaces {
if (target?.type === 'TabClose' || target?.type === 'TabOpen') {
animateContainer = target.target.pinned;
}
await this.onPinnedTabsResize(
this.onPinnedTabsResize(
// This is what happens when we join a resize observer, an event listener
// while using it as a method.
[{ target: (target?.target ? target.target : target) ?? this.pinnedTabsContainer }],
@@ -2491,7 +2487,7 @@ class nsZenWorkspaces {
}
}
async onPinnedTabsResize(entries, forAnimation = false, animateContainer = false) {
onPinnedTabsResize(entries, forAnimation = false, animateContainer = false) {
if (
document.documentElement.hasAttribute('inDOMFullscreen') ||
!this._hasInitializedTabsStrip ||
@@ -2515,9 +2511,7 @@ class nsZenWorkspaces {
// Get all workspaces that have the same userContextId
const activeWorkspace = this.getActiveWorkspace();
const userContextId = activeWorkspace.containerTabId;
const workspaces = this._workspaceCache.filter(
(w) => w.containerTabId === userContextId && w.uuid !== originalWorkspaceId
);
const workspaces = this.getWorkspaces().filter((w) => w.containerTabId === userContextId);
workspacesIds.push(...workspaces.map((w) => w.uuid));
} else {
workspacesIds.push(originalWorkspaceId);
@@ -2690,7 +2684,7 @@ class nsZenWorkspaces {
return tab;
}
async changeWorkspaceShortcut(offset = 1, whileScrolling = false) {
async changeWorkspaceShortcut(offset = 1, whileScrolling = false, disableWrap = false) {
// Cycle through workspaces
let workspaces = this.getWorkspaces();
let activeWorkspace = this.getActiveWorkspace();
@@ -2698,7 +2692,7 @@ class nsZenWorkspaces {
// note: offset can be negative
let targetIndex = workspaceIndex + offset;
if (this.shouldWrapAroundNavigation) {
if (this.shouldWrapAroundNavigation && !disableWrap) {
// Add length to handle negative indices and loop
targetIndex = (targetIndex + workspaces.length) % workspaces.length;
} else {