diff --git a/locales/en-US/browser/browser/zen-workspaces.ftl b/locales/en-US/browser/browser/zen-workspaces.ftl
index 5a4125163..d57b1e0da 100644
--- a/locales/en-US/browser/browser/zen-workspaces.ftl
+++ b/locales/en-US/browser/browser/zen-workspaces.ftl
@@ -35,6 +35,9 @@ zen-workspaces-panel-context-default-profile =
zen-workspaces-panel-unload =
.label = Unload Space
+zen-workspaces-panel-unload-others =
+ .label = Unload All Other Spaces
+
zen-workspaces-how-to-reorder-title = How to reorder spaces
zen-workspaces-how-to-reorder-desc = Drag the space icons at the bottom of the sidebar to reorder them
diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml
index 670e8725d..de2cec15d 100644
--- a/src/browser/base/content/zen-commands.inc.xhtml
+++ b/src/browser/base/content/zen-commands.inc.xhtml
@@ -40,6 +40,7 @@
+
diff --git a/src/browser/base/content/zen-panels/popups.inc b/src/browser/base/content/zen-panels/popups.inc
index 42dcbe14d..c79bf726b 100644
--- a/src/browser/base/content/zen-panels/popups.inc
+++ b/src/browser/base/content/zen-panels/popups.inc
@@ -42,10 +42,11 @@
hide-if-usercontext-disabled="true">
-
+
+
diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js
index c2959fd16..6d539836c 100644
--- a/src/zen/common/zen-sets.js
+++ b/src/zen/common/zen-sets.js
@@ -129,6 +129,10 @@ document.addEventListener(
gZenWorkspaces.unloadWorkspace();
break;
}
+ case "cmd_zenUnloadAllOtherWorkspace": {
+ gZenWorkspaces.unloadAllOtherWorkspaces();
+ break;
+ }
case "cmd_zenNewNavigatorUnsynced":
OpenBrowserWindow({ zenSyncedWindow: false });
break;
diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs
index b5e2506d8..54e5013d1 100644
--- a/src/zen/spaces/ZenSpaceManager.mjs
+++ b/src/zen/spaces/ZenSpaceManager.mjs
@@ -1493,6 +1493,21 @@ class nsZenWorkspaces {
await gBrowser.explicitUnloadTabs(tabsToUnload); // TODO: unit test this
}
+ async unloadAllOtherWorkspaces() {
+ const workspaceId =
+ this.#contextMenuData?.workspaceId || this.activeWorkspace;
+
+ const tabsToUnload = this.allStoredTabs.filter(
+ tab =>
+ tab.getAttribute("zen-workspace-id") !== workspaceId &&
+ !tab.hasAttribute("zen-empty-tab") &&
+ !tab.hasAttribute("zen-essential") &&
+ !tab.hasAttribute("pending")
+ );
+
+ await gBrowser.explicitUnloadTabs(tabsToUnload); // TODO: unit test this
+ }
+
moveTabToWorkspace(tab, workspaceID) {
return this.moveTabsToWorkspace([tab], workspaceID);
}
diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs
index 28dcb887b..b2df35b68 100644
--- a/src/zen/tabs/ZenPinnedTabManager.mjs
+++ b/src/zen/tabs/ZenPinnedTabManager.mjs
@@ -117,9 +117,13 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
event.stopPropagation();
if (event.getModifierState("Accel")) {
let newTab = gBrowser.duplicateTab(tab, true);
- newTab.addEventListener("SSTabRestored", () => {
- this._resetTabToStoredState(tab);
- }, { once: true });
+ newTab.addEventListener(
+ "SSTabRestored",
+ () => {
+ this._resetTabToStoredState(tab);
+ },
+ { once: true }
+ );
} else {
this._resetTabToStoredState(tab);
}
@@ -182,12 +186,13 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
if (!tab) {
return;
}
- let accelHeld = e.getModifierState("Accel") || (e.metaKey && e.type == "keydown");
+ let accelHeld =
+ e.getModifierState("Accel") || (e.metaKey && e.type == "keydown");
this._setResetPinSublabel(tab, accelHeld);
// Up <-> down events until the mouse leaves the button.
// When hovered with accelHeld, we should listen to the next keyup event
let nextEvent = accelHeld ? "keyup" : "keydown";
- let handler = (nextE) => this._onAccelKeyChange(nextE);
+ let handler = nextE => this._onAccelKeyChange(nextE);
window.addEventListener(nextEvent, handler, { once: true });
}
diff --git a/src/zen/tests/pinned/browser_pinned_reset_button.js b/src/zen/tests/pinned/browser_pinned_reset_button.js
index 98b47878c..4691c0980 100644
--- a/src/zen/tests/pinned/browser_pinned_reset_button.js
+++ b/src/zen/tests/pinned/browser_pinned_reset_button.js
@@ -7,7 +7,7 @@ async function pinAndNavigateTab(url, navigateTo) {
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
gBrowser.pinTab(tab);
await gBrowser.TabStateFlusher.flush(tab.linkedBrowser);
- await new Promise((r) => setTimeout(r, 500));
+ await new Promise(r => setTimeout(r, 500));
BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, navigateTo);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, navigateTo);
@@ -15,23 +15,45 @@ async function pinAndNavigateTab(url, navigateTo) {
}
add_task(async function test_ResetPinButton_SelectsTab() {
- const tab = await pinAndNavigateTab("https://example.com/1", "https://example.com/2");
+ const tab = await pinAndNavigateTab(
+ "https://example.com/1",
+ "https://example.com/2"
+ );
// Open another tab and select it
- const otherTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/other");
- Assert.notEqual(gBrowser.selectedTab, tab, "The pinned tab should not be selected initially");
+ const otherTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "https://example.com/other"
+ );
+ Assert.notEqual(
+ gBrowser.selectedTab,
+ tab,
+ "The pinned tab should not be selected initially"
+ );
// Simulate clicking the reset pin button (without Accel key)
gZenPinnedTabManager._onTabResetPinButton(
- { stopPropagation() {}, getModifierState() { return false; } },
+ {
+ stopPropagation() {},
+ getModifierState() {
+ return false;
+ },
+ },
tab
);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
- await new Promise((r) => setTimeout(r, 100));
+ await new Promise(r => setTimeout(r, 100));
- Assert.strictEqual(gBrowser.selectedTab, tab, "The pinned tab should be selected after reset");
- ok(!tab.hasAttribute("zen-pinned-changed"), "zen-pinned-changed should be removed after reset");
+ Assert.strictEqual(
+ gBrowser.selectedTab,
+ tab,
+ "The pinned tab should be selected after reset"
+ );
+ ok(
+ !tab.hasAttribute("zen-pinned-changed"),
+ "zen-pinned-changed should be removed after reset"
+ );
gBrowser.removeTab(otherTab);
gBrowser.removeTab(tab);
@@ -45,17 +67,29 @@ add_task(async function test_ResetPinButton_CmdClick_DuplicatesAndResets() {
// Simulate CMD+click on the reset pin button
gZenPinnedTabManager._onTabResetPinButton(
- { stopPropagation() {}, getModifierState() { return true; } },
+ {
+ stopPropagation() {},
+ getModifierState() {
+ return true;
+ },
+ },
tab
);
// Wait for the duplicate tab to be restored
- const restoredEvent = await BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "SSTabRestored");
+ const restoredEvent = await BrowserTestUtils.waitForEvent(
+ gBrowser.tabContainer,
+ "SSTabRestored"
+ );
const newTab = restoredEvent.target;
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
- await new Promise((r) => setTimeout(r, 100));
+ await new Promise(r => setTimeout(r, 100));
- Assert.equal(gBrowser.tabs.length, tabCountBefore + 1, "A new tab should be created from the duplicate");
+ Assert.equal(
+ gBrowser.tabs.length,
+ tabCountBefore + 1,
+ "A new tab should be created from the duplicate"
+ );
Assert.equal(
newTab.linkedBrowser.currentURI.spec,
navigatedUrl,
@@ -63,8 +97,15 @@ add_task(async function test_ResetPinButton_CmdClick_DuplicatesAndResets() {
);
ok(!newTab.pinned, "The duplicated tab should not be pinned");
- Assert.strictEqual(gBrowser.selectedTab, tab, "The pinned tab should be selected after CMD+click reset");
- ok(!tab.hasAttribute("zen-pinned-changed"), "zen-pinned-changed should be removed after reset");
+ Assert.strictEqual(
+ gBrowser.selectedTab,
+ tab,
+ "The pinned tab should be selected after CMD+click reset"
+ );
+ ok(
+ !tab.hasAttribute("zen-pinned-changed"),
+ "zen-pinned-changed should be removed after reset"
+ );
Assert.equal(
tab.linkedBrowser.currentURI.spec,
originalUrl,
@@ -76,7 +117,10 @@ add_task(async function test_ResetPinButton_CmdClick_DuplicatesAndResets() {
});
add_task(async function test_Hover_SublabelChangesWithAccelKey() {
- const tab = await pinAndNavigateTab("https://example.com/1", "https://example.com/2");
+ const tab = await pinAndNavigateTab(
+ "https://example.com/1",
+ "https://example.com/2"
+ );
// Track calls to document.l10n.setArgs to verify sublabel updates
const sublabelArgs = [];
@@ -92,36 +136,61 @@ add_task(async function test_Hover_SublabelChangesWithAccelKey() {
try {
// Simulate hovering with no modifier key held
gZenPinnedTabManager.onResetPinButtonMouseOver(tab, {
- getModifierState() { return false; },
+ getModifierState() {
+ return false;
+ },
metaKey: false,
type: "mouseover",
});
- Assert.equal(sublabelArgs.at(-1), "zen-default-pinned", "Sublabel should show default text on hover without Accel");
+ Assert.equal(
+ sublabelArgs.at(-1),
+ "zen-default-pinned",
+ "Sublabel should show default text on hover without Accel"
+ );
// Simulate pressing CMD while hovering
gZenPinnedTabManager._onAccelKeyChange({
- getModifierState() { return true; },
+ getModifierState() {
+ return true;
+ },
metaKey: true,
type: "keydown",
});
- Assert.equal(sublabelArgs.at(-1), "zen-default-pinned-cmd", "Sublabel should show CMD text when Accel key is pressed");
+ Assert.equal(
+ sublabelArgs.at(-1),
+ "zen-default-pinned-cmd",
+ "Sublabel should show CMD text when Accel key is pressed"
+ );
// Simulate releasing CMD while still hovering
gZenPinnedTabManager._onAccelKeyChange({
- getModifierState() { return false; },
+ getModifierState() {
+ return false;
+ },
metaKey: false,
type: "keyup",
});
- Assert.equal(sublabelArgs.at(-1), "zen-default-pinned", "Sublabel should revert to default text when Accel key is released");
+ Assert.equal(
+ sublabelArgs.at(-1),
+ "zen-default-pinned",
+ "Sublabel should revert to default text when Accel key is released"
+ );
// Simulate mouse out
gZenPinnedTabManager.onResetPinButtonMouseOut(tab);
- Assert.equal(sublabelArgs.at(-1), "zen-default-pinned", "Sublabel should show default text after mouse out");
- ok(!gZenPinnedTabManager._tabWithResetPinButtonHovered, "Hovered tab reference should be cleared after mouse out");
+ Assert.equal(
+ sublabelArgs.at(-1),
+ "zen-default-pinned",
+ "Sublabel should show default text after mouse out"
+ );
+ ok(
+ !gZenPinnedTabManager._tabWithResetPinButtonHovered,
+ "Hovered tab reference should be cleared after mouse out"
+ );
} finally {
document.l10n.setArgs = origSetArgs;
}