From 33846dbce0c35a1693e6aab9b84c8005419f23bf Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Mon, 2 Feb 2026 14:57:17 +0100 Subject: [PATCH] fix: Fixed drag and drop not calculating window bounds correctly, b=closes #12156, closes https://github.com/zen-browser/desktop/issues/11582, closes https://github.com/zen-browser/desktop/issues/12204, c=tabs --- .../tabbrowser/content/drag-and-drop-js.patch | 61 ++++++++---- src/zen/drag-and-drop/ZenDragAndDrop.js | 32 ++++--- src/zen/sessionstore/ZenWindowSync.sys.mjs | 3 + src/zen/tabs/ZenPinnedTabManager.mjs | 94 +++++++++++-------- 4 files changed, 123 insertions(+), 67 deletions(-) diff --git a/src/browser/components/tabbrowser/content/drag-and-drop-js.patch b/src/browser/components/tabbrowser/content/drag-and-drop-js.patch index 4c58e9310..860f837ff 100644 --- a/src/browser/components/tabbrowser/content/drag-and-drop-js.patch +++ b/src/browser/components/tabbrowser/content/drag-and-drop-js.patch @@ -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 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b1f2bf82b 100644 +index 57800333445ec7850742145527e04ae8d504b0bb..a13875436a72f89178455a09c8665b9a1ef240b5 100644 --- a/browser/components/tabbrowser/content/drag-and-drop.js +++ b/browser/components/tabbrowser/content/drag-and-drop.js @@ -35,6 +35,9 @@ @@ -39,7 +39,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b event.stopPropagation(); + if (draggedTab && dropEffect == "move") { + this.handle_drop_transition?.(draggedTab._dragData.dropElement, draggedTab, movingTabs, draggedTab._dragData.dropBefore); -+ gZenPinnedTabManager.moveToAnotherTabContainerIfNecessary(event, movingTabs); ++ gZenPinnedTabManager.moveToAnotherTabContainerIfNecessary(event, draggedTab, movingTabs, this._getDropIndex(event)); + } if (draggedTab && dropEffect == "copy") { let duplicatedDraggedTab; @@ -102,7 +102,36 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b postTransitionCleanup(); } else { let onTransitionEnd = transitionendEvent => { -@@ -584,6 +597,7 @@ +@@ -513,7 +526,7 @@ + if (tab.selected) { + selectedTab = tab; + indexForSelectedTab = newIndex; +- } else { ++ } else if (false) { + const newTab = gBrowser.adoptTab(tab, { + elementIndex: newIndex, + selectTab: tab == draggedTab, +@@ -523,7 +536,7 @@ + } + } + } +- if (selectedTab) { ++ if (false) { + const newTab = gBrowser.adoptTab(selectedTab, { + elementIndex: indexForSelectedTab, + selectTab: selectedTab == draggedTab, +@@ -534,10 +547,6 @@ + } + + // Restore tab selection +- gBrowser.addRangeToMultiSelectedTabs( +- this._tabbrowserTabs.dragAndDropElements[dropIndex], +- this._tabbrowserTabs.dragAndDropElements[newIndex - 1] +- ); + } else { + // Pass true to disallow dropping javascript: or data: urls + let links; +@@ -584,6 +593,7 @@ let nextItem = this._tabbrowserTabs.dragAndDropElements[newIndex]; let tabGroup = isTab(nextItem) && nextItem.group; @@ -110,7 +139,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b gBrowser.loadTabs(urls, { inBackground, replace, -@@ -621,7 +635,16 @@ +@@ -621,7 +631,16 @@ this._expandGroupOnDrop(draggedTab); } this._resetTabsAfterDrop(draggedTab.ownerDocument); @@ -128,7 +157,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b if ( dt.mozUserCancelled || dt.dropEffect != "none" || -@@ -825,7 +848,10 @@ +@@ -825,7 +844,10 @@ _getDragTarget(event, { ignoreSides = false } = {}) { let { target } = event; while (target) { @@ -140,7 +169,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b break; } target = target.parentNode; -@@ -842,14 +868,17 @@ +@@ -842,14 +864,17 @@ return null; } } @@ -160,7 +189,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b !this._tabbrowserTabs.expandOnHover ); } -@@ -880,7 +909,8 @@ +@@ -880,7 +905,8 @@ isTabGroupLabel(draggedTab) && draggedTab._dragData?.expandGroupOnDrop ) { @@ -170,7 +199,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b } } -@@ -1058,7 +1088,6 @@ +@@ -1058,7 +1084,6 @@ // using updateDragImage. On Linux, we can use a panel. if (platform == "win" || platform == "macosx") { captureListener = function () { @@ -178,7 +207,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b }; } else { // Create a panel to use it in setDragImage -@@ -1096,7 +1125,6 @@ +@@ -1096,7 +1121,6 @@ ); dragImageOffset = dragImageOffset * scale; } @@ -186,7 +215,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b // _dragData.offsetX/Y give the coordinates that the mouse should be // positioned relative to the corner of the new window created upon -@@ -1115,7 +1143,7 @@ +@@ -1115,7 +1139,7 @@ let dropEffect = this.getDropEffectForTabDrag(event); let isMovingInTabStrip = !fromTabList && dropEffect == "move"; let collapseTabGroupDuringDrag = @@ -195,7 +224,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b tab._dragData = { offsetX: this._tabbrowserTabs.verticalMode -@@ -1125,7 +1153,7 @@ +@@ -1125,7 +1149,7 @@ ? event.screenY - window.screenY - tabOffset : event.screenY - window.screenY, scrollPos: @@ -204,7 +233,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b ? this._tabbrowserTabs.pinnedTabsContainer.scrollPosition : this._tabbrowserTabs.arrowScrollbox.scrollPosition, screenX: event.screenX, -@@ -1152,6 +1180,7 @@ +@@ -1152,6 +1176,7 @@ if (collapseTabGroupDuringDrag) { tab.group.collapsed = true; @@ -212,7 +241,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b } } } -@@ -1176,6 +1205,7 @@ +@@ -1176,6 +1201,7 @@ if (tabStripItemElement.hasAttribute("dragtarget")) { return; } @@ -220,7 +249,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b let isPinned = tab.pinned; let numPinned = gBrowser.pinnedTabCount; let dragAndDropElements = this._tabbrowserTabs.dragAndDropElements; -@@ -1601,7 +1631,6 @@ +@@ -1601,7 +1627,6 @@ for (let item of this._tabbrowserTabs.dragAndDropElements) { item = elementToMove(item); @@ -228,7 +257,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b item.removeAttribute("multiselected-move-together"); delete item._moveTogetherSelectedTabsData; } -@@ -2429,7 +2458,6 @@ +@@ -2429,7 +2454,6 @@ for (let item of this._tabbrowserTabs.dragAndDropElements) { this._resetGroupTarget(item); item = elementToMove(item); @@ -236,7 +265,7 @@ index 57800333445ec7850742145527e04ae8d504b0bb..65361da4791b6418705f364f750f409b } this._tabbrowserTabs.removeAttribute("movingtab-group"); this._tabbrowserTabs.removeAttribute("movingtab-ungroup"); -@@ -2460,17 +2488,14 @@ +@@ -2460,17 +2484,14 @@ tab.style.left = ""; tab.style.top = ""; tab.style.maxWidth = ""; diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js index 3f44370d5..ed06b599b 100644 --- a/src/zen/drag-and-drop/ZenDragAndDrop.js +++ b/src/zen/drag-and-drop/ZenDragAndDrop.js @@ -672,16 +672,17 @@ if (!isTab(draggedTab)) { return; } - const { screenX, screenY } = event; + let { screenX, clientX, screenY, clientY } = event; if (!screenX && !screenY) { return; } - const { innerWidth, innerHeight, screenX: windowScreenX, screenY: windowScreenY } = window; + const { innerWidth: winWidth, innerHeight: winHeight } = window; + let allowedMargin = Services.prefs.getIntPref("zen.tabs.dnd-outside-window-margin", 5); const isOutOfWindow = - screenX < windowScreenX || - screenX > windowScreenX + innerWidth || - screenY < windowScreenY || - screenY > windowScreenY + innerHeight; + clientX <= allowedMargin || + clientX >= winWidth - allowedMargin || + clientY <= allowedMargin || + clientY >= winHeight - allowedMargin; if (isOutOfWindow && !this.#isOutOfWindow) { this.#isOutOfWindow = true; gZenViewSplitter.onBrowserDragEndToSplit(event, true); @@ -712,6 +713,8 @@ once: true, capture: true, }); + } else { + this.#isOutOfWindow = false; } } @@ -768,7 +771,8 @@ draggedTab.hasAttribute("zen-essential") || draggedTab.getAttribute("zen-workspace-id") != gZenWorkspaces.activeWorkspace || !dropElement.visible || - !draggedTab.visible + !draggedTab.visible || + draggedTab.ownerGlobal !== window ) { return; } @@ -857,18 +861,19 @@ handle_dragend(event) { const dt = event.dataTransfer; const draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); + let ownerGlobal = draggedTab?.ownerGlobal; draggedTab.style.visibility = ""; - let currentEssenialContainer = gZenWorkspaces.getCurrentEssentialsContainer(); + let currentEssenialContainer = ownerGlobal.gZenWorkspaces.getCurrentEssentialsContainer(); if (currentEssenialContainer?.essentialsPromo) { currentEssenialContainer.essentialsPromo.remove(); } // We also call it here to ensure we clear any highlight if the drop happened // outside of a valid drop target. - gZenFolders.highlightGroupOnDragOver(null); + ownerGlobal.gZenFolders.highlightGroupOnDragOver(null); this.ZenDragAndDropService.onDragEnd(); super.handle_dragend(event); this.#removeDragOverBackground(); - gZenPinnedTabManager.removeTabContainersDragoverClass(); + ownerGlobal.gZenPinnedTabManager.removeTabContainersDragoverClass(); this.#maybeClearVerticalPinnedGridDragOver(); this.originalDragImageArgs = []; window.removeEventListener("dragover", this.handle_windowDragEnter, { capture: true }); @@ -881,9 +886,9 @@ this._tempDragImageParent.remove(); delete this._tempDragImageParent; } - delete gZenCompactModeManager._isTabBeingDragged; + delete ownerGlobal.gZenCompactModeManager._isTabBeingDragged; if (dt.dropEffect !== "move") { - gZenCompactModeManager._clearAllHoverStates(); + ownerGlobal.gZenCompactModeManager._clearAllHoverStates(); } } @@ -1286,6 +1291,9 @@ return; } + // eslint-disable-next-line mozilla/valid-services + Services.zen.playHapticFeedback(); + dragData.animDropElementIndex = newIndex; dragData.dropElement = tabs[Math.min(newIndex, tabs.length - 1)]; dragData.dropBefore = newIndex < tabs.length; diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index 6877c90d8..b2c86dc6e 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -236,6 +236,9 @@ class nsZenWindowSync { if (tab.pinned && !tab._zenPinnedInitialState) { await this.setPinnedTabState(tab); } + if (!lazy.gWindowSyncEnabled) { + tab._zenContentsVisible = true; + } } }); } diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs index 976a79296..ce602c1a7 100644 --- a/src/zen/tabs/ZenPinnedTabManager.mjs +++ b/src/zen/tabs/ZenPinnedTabManager.mjs @@ -559,31 +559,50 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { } // eslint-disable-next-line complexity - moveToAnotherTabContainerIfNecessary(event, movingTabs) { + moveToAnotherTabContainerIfNecessary(event, draggedTab, movingTabs, dropIndex) { if (!this.enabled) { return false; } - movingTabs = movingTabs.map((tab) => { - let workspaceId; - if (tab.ownerGlobal !== window) { - if ( - !tab.hasAttribute("zen-essential") && - tab.getAttribute("zen-workspace-id") != gZenWorkspaces.activeWorkspace - ) { - workspaceId = gZenWorkspaces.activeWorkspace; - tab.ownerGlobal.gBrowser.selectedTab = tab.ownerGlobal.gBrowser._findTabToBlurTo( - tab, - movingTabs - ); - tab.ownerGlobal.gZenWorkspaces.moveTabToWorkspace(tab, workspaceId); + let newIndex = dropIndex; + let fromDifferentWindow = false; + movingTabs = Array.from(movingTabs) + .reverse() + .map((tab) => { + let workspaceId; + if (tab.ownerGlobal !== window) { + fromDifferentWindow = true; + if ( + !tab.hasAttribute("zen-essential") && + tab.getAttribute("zen-workspace-id") != gZenWorkspaces.activeWorkspace + ) { + workspaceId = gZenWorkspaces.activeWorkspace; + tab.ownerGlobal.gBrowser.selectedTab = tab.ownerGlobal.gBrowser._findTabToBlurTo( + tab, + movingTabs + ); + tab.ownerGlobal.gZenWorkspaces.moveTabToWorkspace(tab, workspaceId); + } + // Move the tabs into this window. To avoid multiple tab-switches in + // the original window, the selected tab should be adopted last. + tab = gBrowser.adoptTab(tab, { + elementIndex: newIndex, + selectTab: tab == draggedTab, + }); + if (tab) { + ++newIndex; + } + if (workspaceId) { + tab.setAttribute("zen-workspace-id", workspaceId); + } } - tab = gBrowser.adoptTab(tab); - if (workspaceId) { - tab.setAttribute("zen-workspace-id", workspaceId); - } - } - return tab; - }); + return tab; + }); + if (fromDifferentWindow) { + gBrowser.addRangeToMultiSelectedTabs( + gBrowser.tabContainer.dragAndDropElements[dropIndex], + gBrowser.tabContainer.dragAndDropElements[newIndex - 1] + ); + } try { const pinnedTabsTarget = event.target.closest( ":is(.zen-current-workspace-indicator, .zen-workspace-pinned-tabs-section)" @@ -605,9 +624,9 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { // Remove group labels from the moving tabs and replace it // with the sub tabs for (let i = 0; i < movingTabs.length; i++) { - const draggedTab = movingTabs[i]; - if (gBrowser.isTabGroupLabel(draggedTab)) { - const group = draggedTab.group; + const tab = movingTabs[i]; + if (gBrowser.isTabGroupLabel(tab)) { + const group = tab.group; // remove label and add sub tabs to moving tabs if (group) { movingTabs.splice(i, 1, ...group.tabs); @@ -618,35 +637,32 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { let isVertical = this.expandedSidebarMode; let moved = false; let hasActuallyMoved; - for (const draggedTab of movingTabs) { + for (const tab of movingTabs) { let isRegularTabs = false; // Check for essentials container if (essentialTabsTarget) { - if ( - !draggedTab.hasAttribute("zen-essential") && - !draggedTab?.group?.hasAttribute("split-view-group") - ) { + if (!tab.hasAttribute("zen-essential") && !tab?.group?.hasAttribute("split-view-group")) { moved = true; isVertical = false; - hasActuallyMoved = this.addToEssentials(draggedTab); + hasActuallyMoved = this.addToEssentials(tab); } } // Check for pinned tabs container else if (pinnedTabsTarget) { - if (!draggedTab.pinned) { - gBrowser.pinTab(draggedTab); - } else if (draggedTab.hasAttribute("zen-essential")) { - this.removeEssentials(draggedTab, false); + if (!tab.pinned) { + gBrowser.pinTab(tab); + } else if (tab.hasAttribute("zen-essential")) { + this.removeEssentials(tab, false); moved = true; } } // Check for normal tabs container else if (tabsTarget || event.target.id === "zen-tabs-wrapper") { - if (draggedTab.pinned && !draggedTab.hasAttribute("zen-essential")) { - gBrowser.unpinTab(draggedTab); + if (tab.pinned && !tab.hasAttribute("zen-essential")) { + gBrowser.unpinTab(tab); isRegularTabs = true; - } else if (draggedTab.hasAttribute("zen-essential")) { - this.removeEssentials(draggedTab); + } else if (tab.hasAttribute("zen-essential")) { + this.removeEssentials(tab); moved = true; isRegularTabs = true; } @@ -687,7 +703,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature { elementIndex++; } - gBrowser.moveTabTo(draggedTab, { + gBrowser.moveTabTo(tab, { elementIndex, forceUngrouped: targetElem?.group?.collapsed !== false, });