Skip to content

Commit 0e00aca

Browse files
committed
gh-8309: fix moving essential tabs to new window
1 parent d5cbe55 commit 0e00aca

3 files changed

Lines changed: 211 additions & 3 deletions

File tree

src/browser/components/tabbrowser/content/tabbrowser-js.patch

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -821,13 +821,35 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..028dfcba9e23a17e4152071dd58eb97a
821821
aTab.selected ||
822822
aTab.closing ||
823823
// Tabs that are sharing the screen, microphone or camera cannot be hidden.
824-
@@ -6780,7 +6972,8 @@
824+
@@ -6780,7 +6972,27 @@
825825
* @param {object} [aOptions={}]
826826
* Key-value pairs that will be serialized into the features string.
827827
*/
828828
- replaceTabWithWindow(aTab, aOptions = {}) {
829829
+ replaceTabWithWindow(aTab, aOptions = {}, zenForceSync = false) {
830-
+ if (!this.isTab(aTab)) return; // TODO: Handle tab groups
830+
+ const normalizeDuplicatedEssentialTab = tab => {
831+
+ tab.removeAttribute("zen-essential");
832+
+ tab.removeAttribute("zenDefaultUserContextId");
833+
+ if (tab.pinned) {
834+
+ tab.ownerGlobal.gBrowser.unpinTab(tab);
835+
+ }
836+
+ };
837+
+ if (
838+
+ !zenForceSync &&
839+
+ this.isTab(aTab) &&
840+
+ aTab.hasAttribute("zen-essential")
841+
+ ) {
842+
+ const duplicatedTab = this.duplicateTab(aTab, true, {
843+
+ tabIndex: aTab._tPos + 1,
844+
+ });
845+
+ normalizeDuplicatedEssentialTab(duplicatedTab);
846+
+ duplicatedTab.addEventListener(
847+
+ "SSTabRestored",
848+
+ () => normalizeDuplicatedEssentialTab(duplicatedTab),
849+
+ { once: true }
850+
+ );
851+
+ aTab = duplicatedTab;
852+
+ }
831853
if (this.tabs.length == 1) {
832854
return null;
833855
}
@@ -849,6 +871,40 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..028dfcba9e23a17e4152071dd58eb97a
849871
}
850872

851873
/**
874+
@@ -6830,3 +7041,13 @@
875+
- if (this.tabs.length == elements.length) {
876+
+ const normalizeDuplicatedEssentialTab = tab => {
877+
+ tab.removeAttribute("zen-essential");
878+
+ tab.removeAttribute("zenDefaultUserContextId");
879+
+ if (tab.pinned) {
880+
+ tab.ownerGlobal.gBrowser.unpinTab(tab);
881+
+ }
882+
+ };
883+
+ const containsEssentialTab = elements.some(
884+
+ element => this.isTab(element) && element.hasAttribute("zen-essential")
885+
+ );
886+
+ if (this.tabs.length == elements.length && !containsEssentialTab) {
887+
return null;
888+
}
889+
@@ -6834,2 +7062,18 @@
890+
+ elements = elements.map(element => {
891+
+ if (!this.isTab(element) || !element.hasAttribute("zen-essential")) {
892+
+ return element;
893+
+ }
894+
+ const duplicatedTab = this.duplicateTab(element, true, {
895+
+ tabIndex: element._tPos + 1,
896+
+ });
897+
+ normalizeDuplicatedEssentialTab(duplicatedTab);
898+
+ duplicatedTab.addEventListener(
899+
+ "SSTabRestored",
900+
+ () => normalizeDuplicatedEssentialTab(duplicatedTab),
901+
+ { once: true }
902+
+ );
903+
+ return duplicatedTab;
904+
+ });
905+
+
906+
if (elements.length == 1) {
907+
return this.replaceTabWithWindow(elements[0], aOptions);
852908
@@ -6917,7 +7112,7 @@
853909
* `true` if element is a `<tab-group>`
854910
*/
@@ -945,7 +1001,7 @@ index 43fb79a3060e20f671ae6ffc26350c7abf497702..028dfcba9e23a17e4152071dd58eb97a
9451001
+ if (!gZenFolders.canDropElement(element, targetElement)) {
9461002
+ element = element.group;
9471003
+ }
948-
+ if (!element.hasAttribute('zen-essential') && targetElement?.hasAttribute('zen-essential')) {
1004+
+ if (!element.hasAttribute('zen-essential') && targetElement?.hasAttribute('zen-essential')) {
9491005
+ targetElement = null;
9501006
+ moveBefore = false;
9511007
+ }

src/zen/tests/pinned/browser.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ prefs = ["zen.workspaces.separate-essentials=false"]
77

88
["browser_issue_8726.js"]
99

10+
["browser_essential_move_to_window.js"]
11+
1012
["browser_pinned_changed.js"]
1113

1214
["browser_pinned_close.js"]
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
https://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
"use strict";
5+
6+
const TEST_URL = "https://example.com/";
7+
8+
function isInEssentialsContainer(tab) {
9+
return !!tab.parentElement.closest(".zen-essentials-container");
10+
}
11+
12+
add_task(async function test_Move_Essential_Tab_To_Window_Duplicates() {
13+
const tab = await BrowserTestUtils.openNewForegroundTab(
14+
gBrowser,
15+
TEST_URL,
16+
true
17+
);
18+
19+
gZenPinnedTabManager.addToEssentials(tab);
20+
ok(tab.hasAttribute("zen-essential"), "The original tab is essential");
21+
ok(isInEssentialsContainer(tab), "The original tab is in Essentials");
22+
23+
const essentialCount = gBrowser._numZenEssentials;
24+
const newWindowPromise = BrowserTestUtils.waitForNewWindow();
25+
const openedWindow = gBrowser.replaceTabWithWindow(tab);
26+
ok(openedWindow, "Moving an Essential tab opens a new window");
27+
28+
const newWindow = await newWindowPromise;
29+
await BrowserTestUtils.waitForCondition(
30+
() => newWindow.gBrowser.selectedBrowser.currentURI.spec == TEST_URL,
31+
"The duplicated tab loaded in the new window"
32+
);
33+
34+
const duplicatedTab = newWindow.gBrowser.selectedTab;
35+
ok(
36+
tab.hasAttribute("zen-essential"),
37+
"The original tab remains marked as essential"
38+
);
39+
ok(
40+
isInEssentialsContainer(tab),
41+
"The original tab remains in the Essentials container"
42+
);
43+
is(
44+
gBrowser._numZenEssentials,
45+
essentialCount,
46+
"The source window keeps the same number of Essentials"
47+
);
48+
ok(
49+
!duplicatedTab.hasAttribute("zen-essential"),
50+
"The new window receives a regular tab copy"
51+
);
52+
ok(!duplicatedTab.pinned, "The new window tab is not pinned");
53+
is(
54+
duplicatedTab.linkedBrowser.currentURI.spec,
55+
TEST_URL,
56+
"The new window tab keeps the original URL"
57+
);
58+
59+
await BrowserTestUtils.closeWindow(newWindow);
60+
await BrowserTestUtils.removeTab(tab);
61+
});
62+
63+
add_task(async function test_Forced_Sync_Moves_Essential_Tab() {
64+
let newWindow;
65+
let duplicated = false;
66+
const tab = await BrowserTestUtils.openNewForegroundTab(
67+
gBrowser,
68+
TEST_URL,
69+
true
70+
);
71+
72+
gZenPinnedTabManager.addToEssentials(tab);
73+
ok(tab.hasAttribute("zen-essential"), "The original tab is essential");
74+
75+
const originalDuplicateTab = gBrowser.duplicateTab;
76+
gBrowser.duplicateTab = (...args) => {
77+
duplicated = true;
78+
return originalDuplicateTab.apply(gBrowser, args);
79+
};
80+
81+
try {
82+
const newWindowPromise = BrowserTestUtils.waitForNewWindow();
83+
const openedWindow = gBrowser.replaceTabWithWindow(
84+
tab,
85+
{},
86+
/* zenForceSync = */ true
87+
);
88+
ok(openedWindow, "Forced sync opens a new window for the Essential tab");
89+
90+
newWindow = await newWindowPromise;
91+
ok(!duplicated, "Forced sync does not duplicate an Essential tab");
92+
} finally {
93+
gBrowser.duplicateTab = originalDuplicateTab;
94+
95+
if (newWindow && !newWindow.closed) {
96+
await BrowserTestUtils.closeWindow(newWindow);
97+
}
98+
for (const candidate of [...gBrowser.tabs]) {
99+
if (
100+
candidate.linkedBrowser.currentURI.spec == TEST_URL &&
101+
!candidate.closing
102+
) {
103+
await BrowserTestUtils.removeTab(candidate);
104+
}
105+
}
106+
}
107+
});
108+
109+
add_task(async function test_Move_Tab_Group_Label_To_Window_Still_Works() {
110+
let newWindow;
111+
const tab1 = await BrowserTestUtils.openNewForegroundTab(
112+
gBrowser,
113+
TEST_URL,
114+
true
115+
);
116+
const tab2 = await BrowserTestUtils.openNewForegroundTab(
117+
gBrowser,
118+
"https://example.org/",
119+
true
120+
);
121+
122+
try {
123+
const group = gBrowser.addTabGroup([tab1, tab2], { insertBefore: tab1 });
124+
125+
const newWindowPromise = BrowserTestUtils.waitForNewWindow();
126+
const openedWindow = gBrowser.replaceTabWithWindow(group.labelElement);
127+
ok(openedWindow, "Moving a tab group label opens a new window");
128+
129+
newWindow = await newWindowPromise;
130+
await BrowserTestUtils.waitForCondition(
131+
() => newWindow.gBrowser.tabs.length >= 2,
132+
"The grouped tabs moved to the new window"
133+
);
134+
135+
is(
136+
newWindow.gBrowser.tabs.length,
137+
2,
138+
"The new window receives both grouped tabs"
139+
);
140+
} finally {
141+
if (newWindow && !newWindow.closed) {
142+
await BrowserTestUtils.closeWindow(newWindow);
143+
}
144+
for (const tab of [tab1, tab2]) {
145+
if (tab.isConnected && !tab.closing) {
146+
await BrowserTestUtils.removeTab(tab);
147+
}
148+
}
149+
}
150+
});

0 commit comments

Comments
 (0)