mirror of
https://github.com/zen-browser/desktop.git
synced 2026-03-29 03:41:51 +00:00
feat: Added 'unload all other spaces' option in spaces context menu, p=#12751
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
<command id="cmd_zenCtxDeleteWorkspace" />
|
||||
<command id="cmd_zenUnloadWorkspace" />
|
||||
<command id="cmd_zenUnloadAllOtherWorkspace" />
|
||||
<command id="cmd_zenChangeWorkspaceName" />
|
||||
<command id="cmd_zenChangeWorkspaceIcon" />
|
||||
<command id="cmd_zenReorderWorkspaces" />
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
hide-if-usercontext-disabled="true">
|
||||
<menupopup />
|
||||
</menu>
|
||||
<menuitem id="context_zenUnloadWorkspace" data-l10n-id="zen-workspaces-panel-unload" command="cmd_zenUnloadWorkspace"/>
|
||||
<menuseparator id="context_zenWorkspacesSeparator"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="context_zenReorderWorkspaces" data-l10n-id="zen-workspaces-panel-context-reorder" command="cmd_zenReorderWorkspaces"/>
|
||||
<menuitem id="context_zenUnloadWorkspace" data-l10n-id="zen-workspaces-panel-unload" command="cmd_zenUnloadWorkspace"/>
|
||||
<menuitem id="context_zenUnloadAllOtherWorkspace" data-l10n-id="zen-workspaces-panel-unload-others" command="cmd_zenUnloadAllOtherWorkspace"/>
|
||||
<menuseparator/>
|
||||
<menuitem data-l10n-id="zen-panel-ui-workspaces-create" command="cmd_zenOpenWorkspaceCreation"/>
|
||||
<menuitem id="context_zenDeleteWorkspace" data-l10n-id="zen-workspaces-panel-context-delete" command="cmd_zenCtxDeleteWorkspace"/>
|
||||
|
||||
@@ -129,6 +129,10 @@ document.addEventListener(
|
||||
gZenWorkspaces.unloadWorkspace();
|
||||
break;
|
||||
}
|
||||
case "cmd_zenUnloadAllOtherWorkspace": {
|
||||
gZenWorkspaces.unloadAllOtherWorkspaces();
|
||||
break;
|
||||
}
|
||||
case "cmd_zenNewNavigatorUnsynced":
|
||||
OpenBrowserWindow({ zenSyncedWindow: false });
|
||||
break;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user