This commit is contained in:
mr. m
2025-12-28 20:05:27 +01:00
37 changed files with 2047 additions and 701 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/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..4439fe5fb3c7002b173415b615892ef356b22959 100644
index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..697dde4378c43ae6db46a6b7eb2997982201ec27 100644
--- a/browser/components/sessionstore/SessionStore.sys.mjs
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
@@ -127,6 +127,8 @@ const TAB_EVENTS = [
@@ -177,7 +177,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..4439fe5fb3c7002b173415b615892ef3
+ winData.folders = aWindow.gZenFolders?.storeDataForSessionStore() || [];
+ winData.activeZenSpace = aWindow.gZenWorkspaces?.activeWorkspace || null;
+ winData.spaces = aWindow.gZenWorkspaces?.getWorkspaces();
+ winData.spaces = aWindow.gZenWorkspaces?.getWorkspacesForSessionStore();
// update tab group state for this window
winData.groups = [];
for (let tabGroup of aWindow.gBrowser.tabGroups) {

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/tab.js b/browser/components/tabbrowser/content/tab.js
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7600f564a 100644
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..160e277d64eaac8408aed90eaf62606479424001 100644
--- a/browser/components/tabbrowser/content/tab.js
+++ b/browser/components/tabbrowser/content/tab.js
@@ -21,6 +21,7 @@
@@ -87,7 +87,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
}
get lastAccessed() {
@@ -382,7 +395,12 @@
@@ -382,7 +395,18 @@
}
get group() {
@@ -97,11 +97,17 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
+ }
+ if (gBrowser.isTabGroup(this.parentElement?.parentElement)) {
+ return this.parentElement.parentElement;
+ }
+ if (this.pinned) {
+ let collapsiblePins = gZenWorkspaces.workspaceElement(this.getAttribute('zen-workspace-id'))?.collapsiblePins;
+ if (collapsiblePins?.collapsed) {
+ return collapsiblePins;
+ }
+ }
}
get splitview() {
@@ -473,6 +491,8 @@
@@ -473,6 +497,8 @@
this.style.MozUserFocus = "ignore";
} else if (
event.target.classList.contains("tab-close-button") ||
@@ -110,7 +116,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
event.target.classList.contains("tab-icon-overlay") ||
event.target.classList.contains("tab-audio-button")
) {
@@ -527,6 +547,10 @@
@@ -527,6 +553,10 @@
this.style.MozUserFocus = "";
}
@@ -121,7 +127,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
on_click(event) {
if (event.button != 0) {
return;
@@ -587,6 +611,14 @@
@@ -587,6 +617,14 @@
// (see tabbrowser-tabs 'click' handler).
gBrowser.tabContainer._blockDblClick = true;
}
@@ -136,7 +142,7 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
}
on_dblclick(event) {
@@ -610,6 +642,8 @@
@@ -610,6 +648,8 @@
animate: true,
triggeringEvent: event,
});

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,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js
index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057ebc71c2f5f 100644
index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..e92927612abf12631c384a8f54b6a607fb699424 100644
--- a/browser/components/tabbrowser/content/tabgroup.js
+++ b/browser/components/tabbrowser/content/tabgroup.js
@@ -14,11 +14,11 @@
@@ -68,10 +68,10 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
- return false;
- });
+ this.appendChild = function (child) {
+ this.querySelector(".tab-group-container").appendChild(child);
+ this.groupContainer.appendChild(child);
+ for (let tab of this.tabs) {
+ if (tab.hasAttribute("zen-empty-tab") && tab.group === this) {
+ this.querySelector(".zen-tab-group-start").after(tab);
+ this.groupStartElement.after(tab);
+ }
+ }
+ };
@@ -92,7 +92,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
});
}
- this.#tabChangeObserver.observe(this, { childList: true });
+ const container = this.querySelector(".tab-group-container");
+ const container = this.groupContainer;
+ if (container) {
+ this.#tabChangeObserver.observe(container, { childList: true });
+ }
@@ -117,7 +117,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
});
}
@@ -466,13 +492,57 @@
@@ -466,13 +492,65 @@
* @returns {MozTabbrowserTab[]}
*/
get tabs() {
@@ -126,20 +126,29 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
- if (childrenArray[i].tagName == "tab-split-view-wrapper") {
- childrenArray.splice(i, 1, ...childrenArray[i].tabs);
+ // add other group tabs if they are under this group
+ let childs = Array.from(this.querySelector(".tab-group-container")?.children ?? []);
+ let childs = Array.from(this.groupContainer?.children ?? []);
+ const tabsCollect = [];
+ for (let item of childs) {
+ tabsCollect.push(item);
+ if (gBrowser.isTabGroup(item)) {
+ tabsCollect.push(...item.tabs);
+ }
+ }
}
}
- return childrenArray.filter(node => node.matches("tab"));
+ return tabsCollect.filter(node => node.matches("tab"));
+ }
+
+ get groupContainer() {
+ return this.querySelector(".tab-group-container");
+ }
+
+ get groupStartElement() {
+ return this.querySelector(".zen-tab-group-start");
+ }
+
+ get childGroupsAndTabs() {
+ const result = [];
+ const container = this.querySelector(".tab-group-container");
+ const container = this.groupContainer;
+
+ for (const item of Array.from(container.children)) {
+ if (gBrowser.isTab(item)) {
@@ -169,9 +178,8 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
+ currentGroup = currentGroup?.group;
+ if (currentGroup.collapsed) {
+ return false;
}
}
- return childrenArray.filter(node => node.matches("tab"));
+ }
+ }
+ return true;
+ }
+
@@ -180,7 +188,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
}
/**
@@ -553,7 +623,6 @@
@@ -553,7 +631,6 @@
addTabs(tabs, metricsContext) {
for (let tab of tabs) {
if (tab.pinned) {
@@ -188,7 +196,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
}
let tabToMove =
this.ownerGlobal === tab.ownerGlobal
@@ -616,7 +685,7 @@
@@ -616,7 +693,7 @@
*/
on_click(event) {
let isToggleElement =
@@ -197,7 +205,7 @@ index 394b2af2e187b82bb3e98ebcdc6e66b63036e20d..4757555c7654a14578a5d057323057eb
event.target === this.#overflowCountLabel;
if (isToggleElement && event.button === 0) {
event.preventDefault();
@@ -687,5 +756,6 @@
@@ -687,5 +764,6 @@
}
}

View File

@@ -1,7 +1,16 @@
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac8793422474052f476573 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 (
@@ -54,7 +63,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
return this.hasAttribute("overflow");
}
@@ -837,29 +839,54 @@
@@ -837,29 +839,56 @@
if (pinnedChildren?.at(-1)?.id == "pinned-tabs-container-periphery") {
pinnedChildren.pop();
}
@@ -81,6 +90,8 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
+ tabs.splice(i, 1);
+ // add the tabs in the group to the list
+ tabs.splice(i, 0, ...tab.tabs);
+ } else if (tab.classList.contains("zen-tab-group-start")) {
+ tabs.splice(i, 1);
+ }
+ }
+ };
@@ -119,7 +130,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
}
/**
@@ -926,17 +953,10 @@
@@ -926,17 +955,10 @@
let elementIndex = 0;
@@ -139,7 +150,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
if (isTab(child) && child.visible) {
child.elementIndex = elementIndex++;
focusableItems.push(child);
@@ -944,11 +964,13 @@
@@ -944,11 +966,13 @@
child.labelElement.elementIndex = elementIndex++;
focusableItems.push(child.labelElement);
@@ -154,7 +165,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
} else if (child.tagName == "tab-split-view-wrapper") {
let visibleTabsInSplitView = child.tabs.filter(tab => tab.visible);
visibleTabsInSplitView.forEach(tab => {
@@ -992,6 +1014,7 @@
@@ -992,6 +1016,7 @@
_invalidateCachedTabs() {
this.#allTabs = null;
this._invalidateCachedVisibleTabs();
@@ -162,7 +173,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
}
_invalidateCachedVisibleTabs() {
@@ -1095,7 +1118,7 @@
@@ -1095,7 +1120,7 @@
if (node == null) {
// We have a container for non-tab elements at the end of the scrollbox.
@@ -171,7 +182,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
}
node.before(tab);
@@ -1193,7 +1216,7 @@
@@ -1193,7 +1218,7 @@
// There are separate "new tab" buttons for horizontal tabs toolbar, vertical tabs and
// for when the tab strip is overflowed (which is shared by vertical and horizontal tabs);
// Attach the long click popup to all of them.
@@ -180,7 +191,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
const newTab2 = this.newTabButton;
const newTabVertical = document.getElementById(
"vertical-tabs-newtab-button"
@@ -1294,8 +1317,10 @@
@@ -1294,8 +1319,10 @@
*/
_handleTabSelect(aInstant) {
let selectedTab = this.selectedItem;
@@ -191,7 +202,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
selectedTab._notselectedsinceload = false;
}
@@ -1304,7 +1329,7 @@
@@ -1304,7 +1331,7 @@
* @param {boolean} [shouldScrollInstantly=false]
*/
#ensureTabIsVisible(tab, shouldScrollInstantly = false) {
@@ -200,7 +211,7 @@ index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac879342247405
if (arrowScrollbox?.overflowing) {
arrowScrollbox.ensureElementIsVisible(tab, shouldScrollInstantly);
}
@@ -1437,7 +1462,7 @@
@@ -1437,7 +1464,7 @@
}
_notifyBackgroundTab(aTab) {

View File

@@ -7,6 +7,7 @@
.subviewbutton,
#zen-welcome-start-button,
.zen-toast button,
.zen-current-workspace-indicator-chevron,
.pinned-tabs-container-separator toolbarbutton {
-moz-context-properties: fill, fill-opacity !important;
fill: currentColor !important;
@@ -116,7 +117,7 @@
}
}
#zen-rice-share-options .options-header,
.zen-current-workspace-indicator-chevron,
#PanelUI-zen-gradient-generator-color-page-right {
list-style-image: url('arrow-right.svg');
}

View File

@@ -2,4 +2,4 @@
# 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/.
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="1.5" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><polyline points="6.5 2.75 12.75 9 6.5 15.25"></polyline></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 18 18"><g stroke-linecap="round" stroke-width="2" fill="none" stroke="context-fill" stroke-opacity="context-fill-opacity" stroke-linejoin="round" class="nc-icon-wrapper"><polyline points="6.5 2.75 12.75 9 6.5 15.25"></polyline></g></svg>

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

@@ -125,8 +125,6 @@ window.gZenUIManager = {
}
menu.setAttribute('hidden', 'true');
}
// The first separator in the tab context menu is now useless.
document.getElementById('tabContextMenu').querySelector('menuseparator').remove();
},
_initCreateNewPopup() {
@@ -1225,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

@@ -2,11 +2,11 @@
// 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/.
class ZenFolder extends MozTabbrowserTabGroup {
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"/>
@@ -68,7 +68,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
}
this.#initialized = true;
this._activeTabs = [];
this.icon.appendChild(ZenFolder.rawIcon.cloneNode(true));
this.icon.appendChild(nsZenFolder.rawIcon.cloneNode(true));
this.labelElement.parentElement.setAttribute('context', 'zenFolderActions');
@@ -81,7 +81,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
};
if (this.collapsed) {
this.querySelector('.tab-group-container').setAttribute('hidden', true);
this.groupContainer.setAttribute('hidden', true);
}
}
@@ -141,7 +141,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
gZenFolders.createFolder([], {
renameFolder: !gZenUIManager.testingEnabled,
label: 'Subfolder',
insertAfter: this.querySelector('.tab-group-container').lastElementChild,
insertAfter: this.groupContainer.lastElementChild,
});
}
@@ -181,8 +181,12 @@ class ZenFolder extends MozTabbrowserTabGroup {
}
get allItems() {
return [...this.querySelector('.tab-group-container').children].filter(
(child) => !child.classList.contains('zen-tab-group-start')
return [...this.groupContainer.children].filter(
(child) =>
!(
child.classList.contains('zen-tab-group-start') ||
child.classList.contains('pinned-tabs-container-separator')
)
);
}
@@ -274,4 +278,4 @@ class ZenFolder extends MozTabbrowserTabGroup {
}
}
customElements.define('zen-folder', ZenFolder);
customElements.define('zen-folder', nsZenFolder);

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;
@@ -189,6 +187,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
window.addEventListener('TabSelect', this);
window.addEventListener('TabOpen', this);
const onNewFolder = this.#onNewFolder.bind(this);
document.getElementById('zen-context-menu-new-folder').addEventListener('command', onNewFolder);
document
.getElementById('zen-context-menu-new-folder-toolbar')
.addEventListener('command', onNewFolder);
@@ -803,6 +802,9 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
if (!isTab && !groupElem?.hasAttribute('selected') && !forCollapse) {
groupElem = null; // Don't indent if the group is not selected
}
if (groupElem?.tagName.toLowerCase() === 'zen-workspace-collapsible-pins') {
groupElem = null; // Don't indent if it's inside the collapsible pinned tabs
}
let level = groupElem?.level + 1 || 0;
if (gBrowser.isTabGroupLabel(groupElem)) {
// If it is a group label, we should not increase its level by one.
@@ -1036,8 +1038,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
}
default: {
// Should insert after zen-empty-tab
const start =
parentWorkingData.node.querySelector('.zen-tab-group-start').nextElementSibling;
const start = parentWorkingData.node.groupStartElement.nextElementSibling;
start.after(node);
}
}
@@ -1062,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 &&
!(
@@ -1081,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;
}
/**
@@ -1100,55 +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?.querySelector('.zen-tab-group-start')?.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'))
@@ -1212,6 +1162,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
return heightShift;
} else {
heightShift += window.windowUtils.getBoundsWithoutFlushing(tabsContainer).height;
if (tabsContainer.separatorElement) {
heightShift -= window.windowUtils.getBoundsWithoutFlushing(
tabsContainer.separatorElement
).height;
}
}
return heightShift;
}
@@ -1225,8 +1180,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const activeFoldersIds = new Set();
const itemsToHide = [];
const tabsContainer = group.querySelector('.tab-group-container');
const groupStart = group.querySelector('.zen-tab-group-start');
const tabsContainer = group.groupContainer;
const groupStart = group.groupStartElement;
const groupItems = this.#collectGroupItems(group, {
selectedTabs,
@@ -1304,11 +1259,11 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const animations = [];
const itemsToHide = [];
const tabsContainer = group.querySelector('.tab-group-container');
const tabsContainer = group.groupContainer;
tabsContainer.removeAttribute('hidden');
tabsContainer.style.overflow = 'hidden';
const groupStart = group.querySelector('.zen-tab-group-start');
const groupStart = group.groupStartElement;
const itemsToShow = this.#normalizeGroupItems(group.childGroupsAndTabs);
const activeFolders = group.childActiveGroups;
@@ -1422,7 +1377,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
folder.removeAttribute('has-active');
folder.activeTabs = [];
const groupItems = this.#normalizeGroupItems(folder.allItems);
const tabsContainer = folder.querySelector('.tab-group-container');
const tabsContainer = folder.groupContainer;
// Set correct margin-top after animation
const afterAnimate = () => {
@@ -1436,7 +1391,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
groupStart.style.marginTop = `${-(collapsedHeight + 4)}px`;
};
const groupStart = folder.querySelector('.zen-tab-group-start');
const groupStart = folder.groupStartElement;
const collapsedHeight = this.#calculateHeightShift(tabsContainer, []);
// Collect animations for this specific folder becoming inactive
@@ -1474,7 +1429,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
animations.push(async () => {
folder.removeAttribute('has-active');
const groupItems = this.#normalizeGroupItems(folder.allItems);
const tabsContainer = folder.querySelector('.tab-group-container');
const tabsContainer = folder.groupContainer;
// Set correct margin-top after animation
const afterAnimate = () => {
@@ -1488,7 +1443,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
groupStart.style.marginTop = `${-(collapsedHeight + 4)}px`;
};
const groupStart = folder.querySelector('.zen-tab-group-start');
const groupStart = folder.groupStartElement;
const collapsedHeight = this.#calculateHeightShift(tabsContainer, []);
// Collect animations for this specific folder becoming inactive
@@ -1573,8 +1528,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
currentGroup.activeTabs = activeTabs;
}
const tabsContainer = currentGroup.querySelector('.tab-group-container');
const groupStart = currentGroup.querySelector('.zen-tab-group-start');
const tabsContainer = currentGroup.groupContainer;
const groupStart = currentGroup.groupStartElement;
tabsContainer.style.overflow = 'clip';
if (tabsContainer.hasAttribute('hidden')) tabsContainer.removeAttribute('hidden');
@@ -1673,8 +1628,8 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
animateGroupMove(group, expand = false) {
if (!group?.isZenFolder) return;
const groupStart = group.querySelector('.zen-tab-group-start');
const tabsContainer = group.querySelector('.tab-group-container');
const groupStart = group.groupStartElement;
const tabsContainer = group.groupContainer;
const heightContainer = expand ? 0 : this.#calculateHeightShift(tabsContainer, []);
tabsContainer.style.overflow = 'clip';

View File

@@ -202,16 +202,6 @@ zen-folder {
}
}
&[collapsed] {
& > .tabbrowser-tab:not([hidden]) {
display: flex;
}
&:not([has-active]) > .tab-group-container {
overflow-y: clip;
}
}
:root[zen-sidebar-expanded] &[has-active] > .tab-group-label-container {
& .tab-reset-button {
display: flex;
@@ -224,6 +214,11 @@ zen-folder {
}
}
zen-workspace[collapsedpinnedtabs] .zen-workspace-pinned-tabs-section,
zen-folder[collapsed]:not([has-active]) > .tab-group-container {
overflow-y: clip;
}
/* Tabs popup */
#zen-folder-tabs-popup {
--arrowpanel-padding: 0;

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);
@@ -322,6 +326,9 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
const state = this.#getTabState(tab);
const initialState = tab._zenPinnedInitialState;
if (!initialState?.entry) {
return;
}
// Remove everything except the entry we want to keep
state.entries = [initialState.entry];
@@ -531,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
@@ -682,9 +690,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
removeTabContainersDragoverClass(hideIndicator = true) {
if (this._dragIndicator) {
Services.zen.playHapticFeedback();
}
this.dragIndicator.remove();
this._dragIndicator = null;
if (hideIndicator) {
@@ -692,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

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
import { nsZenMultiWindowFeature } from 'chrome://browser/content/zen-components/ZenCommonUtils.mjs';

View File

@@ -1,17 +1,59 @@
// 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/.
/* 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/. */
import { nsZenFolder } from 'chrome://browser/content/zen-components/ZenFolder.mjs';
// A helper class to manage collapsible pinned tabs in a workspace.
class nsZenCollapsiblePins extends nsZenFolder {
#spaceElement;
connectedCallback() {
this.setAttribute('hidden', 'true');
this.#spaceElement = this.parentElement;
super.connectedCallback();
}
get groupContainer() {
return this.#spaceElement.pinnedTabsContainer;
}
get groupStartElement() {
// Fetch this instead of the tab-group-start since it is not guaranteed this
// element will be the first child of the pinned tabs container.
return this.#spaceElement.pinnedTabsContainer.querySelector('.space-fake-collapsible-start');
}
get collapsed() {
return super.collapsed;
}
set collapsed(value) {
if (value) {
this.#spaceElement.setAttribute('collapsedpinnedtabs', 'true');
} else {
this.#spaceElement.removeAttribute('collapsedpinnedtabs');
}
super.collapsed = value;
}
}
export class nsZenWorkspace extends MozXULElement {
#initialPinnedElementChildrenCount;
class nsZenWorkspace extends MozXULElement {
static get markup() {
return `
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1" context="zenWorkspaceMoreActions">
<hbox class="zen-current-workspace-indicator-icon" />
<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" />
</stack>
<label class="zen-current-workspace-indicator-name" flex="1" />
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions" />
</vbox>
<arrowscrollbox orient="vertical" class="workspace-arrowscrollbox">
<vbox class="zen-workspace-tabs-section zen-workspace-pinned-tabs-section" hide-separator="true">
<html:div class="zen-tab-group-start space-fake-collapsible-start" style="order: -9999;" />
<hbox class="pinned-tabs-container-separator">
<toolbarseparator flex="1" />
<toolbarbutton command="cmd_zenCloseUnpinnedTabs"
@@ -68,6 +110,9 @@ class nsZenWorkspace extends MozXULElement {
this.tabsContainer = this.querySelector('.zen-workspace-normal-tabs-section');
this.indicator = this.querySelector('.zen-current-workspace-indicator');
this.pinnedTabsContainer = this.querySelector('.zen-workspace-pinned-tabs-section');
this.pinnedTabsContainer.separatorElement = this.pinnedTabsContainer.querySelector(
'.pinned-tabs-container-separator'
);
this.initializeAttributeInheritance();
this.scrollbox = this.querySelector('arrowscrollbox');
@@ -80,10 +125,17 @@ class nsZenWorkspace extends MozXULElement {
this.scrollbox.addEventListener('underflow', this);
this.scrollbox.addEventListener('overflow', this);
this.indicator.querySelector('.zen-current-workspace-indicator-name').onRenameFinished =
this.onIndicatorRenameFinished.bind(this);
const indicatorName = this.indicator.querySelector('.zen-current-workspace-indicator-name');
indicatorName.onRenameFinished = this.onIndicatorRenameFinished.bind(this);
indicatorName.addEventListener('dblclick', (event) => {
if (this.hasPinnedTabs) {
// Prevent renaming when there are pinned tabs
event.stopPropagation();
}
});
this.pinnedTabsContainer.scrollbox = this.scrollbox;
this.#initialPinnedElementChildrenCount = this.pinnedTabsContainer.children.length;
this.indicator
.querySelector('.zen-workspaces-actions')
@@ -92,10 +144,20 @@ class nsZenWorkspace extends MozXULElement {
this.indicator
.querySelector('.zen-current-workspace-indicator-icon')
.addEventListener('dblclick', (event) => {
if (this.hasPinnedTabs) {
return;
}
event.stopPropagation();
gZenWorkspaces.changeWorkspaceIcon();
});
this.indicator.addEventListener('click', (event) => {
if (this.hasPinnedTabs) {
event.stopPropagation();
this.collapsiblePins.collapsed = !this.collapsiblePins.collapsed;
}
});
if (!gZenWorkspaces.currentWindowIsSyncing) {
let actionsButton = this.indicator.querySelector('.zen-workspaces-actions');
const moveTabToFragment = window.MozXULElement.parseXULToFragment(
@@ -169,11 +231,26 @@ class nsZenWorkspace extends MozXULElement {
this.tabsContainer.setAttribute('zen-workspace-id', this.id);
this.pinnedTabsContainer.setAttribute('zen-workspace-id', this.id);
this.collapsiblePins = document.createXULElement('zen-workspace-collapsible-pins');
this.prepend(this.collapsiblePins);
this.#updateOverflow();
this.onGradientCacheChanged = this.#onGradientCacheChanged.bind(this);
window.addEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged);
const tabPinCallback = () => {
this.checkPinsExistence();
};
this.addEventListener('TabPinned', tabPinCallback);
this.addEventListener('TabUnpinned', tabPinCallback);
this.addEventListener('TabClose', (event) => {
if (event.target.pinned) {
tabPinCallback();
}
});
this.dispatchEvent(
new CustomEvent('ZenWorkspaceAttached', {
bubbles: true,
@@ -185,6 +262,7 @@ class nsZenWorkspace extends MozXULElement {
disconnectedCallback() {
window.removeEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged);
super.disconnectedCallback();
}
get active() {
@@ -200,6 +278,14 @@ class nsZenWorkspace extends MozXULElement {
this.#updateOverflow();
}
get hasPinnedTabs() {
return this.hasAttribute('haspinnedtabs');
}
get hasCollapsedPinnedTabs() {
return this.hasAttribute('collapsedpinnedtabs');
}
#updateOverflow() {
if (!this.scrollbox) return;
if (this.overflows) {
@@ -273,6 +359,15 @@ class nsZenWorkspace extends MozXULElement {
this.style.setProperty('--zen-primary-color', primaryColor);
}
checkPinsExistence() {
if (this.pinnedTabsContainer.children.length > this.#initialPinnedElementChildrenCount) {
this.setAttribute('haspinnedtabs', 'true');
} else {
this.removeAttribute('haspinnedtabs');
this.collapsiblePins.collapsed = false;
}
}
clearThemeStyles() {
this.style.colorScheme = '';
this.style.removeProperty('--toolbox-textcolor');
@@ -311,3 +406,4 @@ class nsZenWorkspace extends MozXULElement {
}
customElements.define('zen-workspace', nsZenWorkspace);
customElements.define('zen-workspace-collapsible-pins', nsZenCollapsiblePins);

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
class nsZenWorkspaceCreation extends MozXULElement {
#wasInCollapsedMode = false;

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
class nsZenWorkspaceIcons extends MozXULElement {
constructor() {

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
import { nsZenThemePicker } from 'chrome://browser/content/zen-components/ZenGradientGenerator.mjs';
@@ -465,6 +465,7 @@ class nsZenWorkspaces {
workspaceWrapper.pinnedTabsContainer,
tabs
);
workspaceWrapper.checkPinsExistence();
resolve();
},
{ once: true }
@@ -827,6 +828,20 @@ class nsZenWorkspaces {
return [...this._workspaceCache];
}
getWorkspacesForSessionStore() {
const spaces = this.getWorkspaces();
let spacesForSS = [];
for (const space of spaces) {
let newSpace = { ...space };
const element = this.workspaceElement(space.uuid);
if (element) {
newSpace.hasCollapsedPinnedTabs = element.hasCollapsedPinnedTabs;
}
spacesForSS.push(newSpace);
}
return spacesForSS;
}
async workspaceBookmarks() {
if (this.privateWindowOrDisabled) {
this._workspaceBookmarksCache = {
@@ -854,11 +869,22 @@ class nsZenWorkspaces {
if (this.#hasInitialized) {
return;
}
this._workspaceCache = aWinData.spaces?.length
? aWinData.spaces
const spacesFromStore = aWinData.spaces || [];
this._workspaceCache = spacesFromStore.length
? [...spacesFromStore]
: [await this.createAndSaveWorkspace('Space', undefined, true)];
for (const workspace of this._workspaceCache) {
// We don't want to depend on this by mistake
delete workspace.hasCollapsedPinnedTabs;
}
this.activeWorkspace = aWinData.activeZenSpace || this._workspaceCache[0].uuid;
await this.initializeWorkspaces();
for (const workspace of spacesFromStore) {
const element = this.workspaceElement(workspace.uuid);
if (element) {
element.collapsiblePins.collapsed = workspace.hasCollapsedPinnedTabs || false;
}
}
this.#hasInitialized = true;
}
@@ -1542,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;
@@ -1788,11 +1810,14 @@ class nsZenWorkspaces {
}
const indicatorName = workspaceIndicator.querySelector('.zen-current-workspace-indicator-name');
const indicatorIcon = workspaceIndicator.querySelector('.zen-current-workspace-indicator-icon');
const iconStack = workspaceIndicator.querySelector('.zen-current-workspace-indicator-stack');
if (this.workspaceHasIcon(currentWorkspace)) {
indicatorIcon.removeAttribute('no-icon');
iconStack.removeAttribute('no-icon');
} else {
indicatorIcon.setAttribute('no-icon', 'true');
iconStack.setAttribute('no-icon', 'true');
}
const icon = this.getWorkspaceIcon(currentWorkspace);
indicatorIcon.innerHTML = '';
@@ -2404,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;
@@ -2414,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 }],
@@ -2462,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 ||
@@ -2486,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);
@@ -2661,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();
@@ -2669,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 {

View File

@@ -1,6 +1,6 @@
// 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/.
/* 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/. */
// Integration of workspace-specific bookmarks into Places
window.ZenWorkspaceBookmarksStorage = {

View File

@@ -155,13 +155,14 @@
/* Mark workspaces indicator */
.zen-current-workspace-indicator {
--indicator-gap: 10px;
margin-top: 1px;
padding: calc(2px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
font-weight: 500;
position: relative;
max-height: var(--zen-workspace-indicator-height);
min-height: var(--zen-workspace-indicator-height);
gap: 10px;
gap: var(--indicator-gap);
align-items: center;
flex-direction: row !important;
max-width: 100%;
@@ -368,3 +369,52 @@ zen-workspace {
}
%include create-workspace-form.css
/* Pinned tabs collapse styles */
.zen-current-workspace-indicator-chevron {
display: none;
}
:root[zen-sidebar-expanded] {
.zen-current-workspace-indicator-stack {
transition: margin-inline-end 0.1s;
&[no-icon='true'] {
margin-inline-end: calc(-1 * (var(--indicator-gap) + 16px));
}
}
.zen-current-workspace-indicator-chevron {
width: 16px;
height: 16px;
transition: transform 0.2s, opacity 0.2s;
transform: rotate(90deg);
padding: 2px;
.zen-current-workspace-indicator-stack[no-icon='true'] & {
display: flex;
opacity: 0;
}
}
& zen-workspace[haspinnedtabs] .zen-current-workspace-indicator:hover,
& zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator {
.zen-current-workspace-indicator-chevron {
display: flex;
opacity: 1;
}
.zen-current-workspace-indicator-stack {
margin-inline-end: 0;
}
.zen-current-workspace-indicator-icon {
display: none;
}
}
zen-workspace[collapsedpinnedtabs] .zen-current-workspace-indicator-chevron {
transform: rotate(0deg);
}
}