gh-12740: remove deleted workspace tabs from switch-to-tab (gh-12794)

Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
Co-authored-by: mr. m <mr.m@tuta.com>
This commit is contained in:
Vedaant Rajoo
2026-03-22 09:12:52 -07:00
committed by GitHub
parent cd0713a7ee
commit e80c16a732
16 changed files with 52 additions and 144 deletions

View File

@@ -13,6 +13,9 @@ window.ZenWorkspaceBookmarksStorage = {
if (!window.gZenWorkspaces) {
return;
}
this.promiseInitialized = new Promise(resolve => {
this._resolveInitialized = resolve;
});
await this._ensureTable();
},
@@ -54,9 +57,12 @@ window.ZenWorkspaceBookmarksStorage = {
// Create index for changes tracking
await db.execute(`
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_changes
ON zen_bookmarks_workspaces_changes(bookmark_guid, workspace_uuid)
`);
CREATE INDEX IF NOT EXISTS idx_bookmarks_workspaces_changes
ON zen_bookmarks_workspaces_changes(bookmark_guid, workspace_uuid)
`);
this._resolveInitialized();
delete this._resolveInitialized;
}
);
},
@@ -68,6 +74,7 @@ window.ZenWorkspaceBookmarksStorage = {
*/
async updateLastChangeTimestamp(db) {
const now = Date.now();
await this.promiseInitialized;
await db.execute(
`
INSERT OR REPLACE INTO moz_meta (key, value)
@@ -84,6 +91,7 @@ window.ZenWorkspaceBookmarksStorage = {
*/
async getLastChangeTimestamp() {
const db = await this.lazy.PlacesUtils.promiseDBConnection();
await this.promiseInitialized;
const result = await db.executeCached(`
SELECT value FROM moz_meta WHERE key = 'zen_bookmarks_workspaces_last_change'
`);
@@ -91,16 +99,21 @@ window.ZenWorkspaceBookmarksStorage = {
},
async getBookmarkWorkspaces(bookmarkGuid) {
await this.promiseInitialized;
const db = await this.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(
`
let rows = [];
try {
rows = await db.execute(
`
SELECT workspace_uuid
FROM zen_bookmarks_workspaces
WHERE bookmark_guid = :bookmark_guid
`,
{ bookmark_guid: bookmarkGuid }
);
{ bookmark_guid: bookmarkGuid }
);
} catch (e) {
console.error("Error fetching bookmark workspaces:", e);
}
return rows.map(row => row.getResultByName("workspace_uuid"));
},
@@ -117,8 +130,8 @@ window.ZenWorkspaceBookmarksStorage = {
* }
*/
async getBookmarkGuidsByWorkspace() {
await this.promiseInitialized;
const db = await this.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
SELECT workspace_uuid, GROUP_CONCAT(bookmark_guid) as bookmark_guids
FROM zen_bookmarks_workspaces
@@ -141,6 +154,7 @@ window.ZenWorkspaceBookmarksStorage = {
* @returns {Promise<object>} An object mapping bookmark+workspace pairs to their change data.
*/
async getChangedIDs() {
await this.promiseInitialized;
const db = await this.lazy.PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
SELECT bookmark_guid, workspace_uuid, change_type, timestamp
@@ -162,6 +176,7 @@ window.ZenWorkspaceBookmarksStorage = {
* Clear all recorded changes.
*/
async clearChangedIDs() {
await this.promiseInitialized;
await this.lazy.PlacesUtils.withConnectionWrapper(
"ZenWorkspaceBookmarksStorage.clearChangedIDs",
async db => {

View File

@@ -1261,12 +1261,23 @@ class nsZenWorkspaces {
}
removeWorkspace(windowID) {
let { promise, resolve } = Promise.withResolvers();
this.#deleteWorkspaceOwnedTabs(windowID);
let workspacesData = this.getWorkspaces();
// Remove the workspace from the cache
workspacesData = workspacesData.filter(
workspace => workspace.uuid !== windowID
);
window.addEventListener(
"ZenWorkspacesUIUpdate",
() => {
resolve();
},
{ once: true }
);
this.#propagateWorkspaceData(workspacesData);
gBrowser.tabContainer._invalidateCachedVisibleTabs();
return promise;
}
isWorkspaceActive(workspace) {
@@ -1472,6 +1483,18 @@ class nsZenWorkspaces {
});
}
#deleteWorkspaceOwnedTabs(workspaceID) {
const tabs = this.allStoredTabs.filter(
tab =>
tab.getAttribute("zen-workspace-id") === workspaceID &&
!tab.hasAttribute("zen-essential") &&
!(tab.hasAttribute("zen-empty-tab") && !tab.group)
);
gBrowser.removeTabs(tabs, {
closeWindowWithLastTab: false,
});
}
async unloadWorkspace() {
const workspaceId =
this.#contextMenuData?.workspaceId || this.activeWorkspace;
@@ -1659,7 +1682,7 @@ class nsZenWorkspaces {
onInit,
previousWorkspace.uuid
);
if (tabToSelect.linkedBrowser) {
if (tabToSelect?.linkedBrowser) {
gBrowser.warmupTab(tabToSelect);
}

View File

@@ -9,13 +9,13 @@ BROWSER_CHROME_MANIFESTS += [
"glance/browser.toml",
"live-folders/browser.toml",
"pinned/browser.toml",
"spaces/browser.toml",
"split_view/browser.toml",
"tabs/browser.toml",
"ub-actions/browser.toml",
"urlbar/browser.toml",
"welcome/browser.toml",
"window_sync/browser.toml",
"workspaces/browser.toml",
]
DIRS += [

View File

@@ -26,7 +26,7 @@ add_task(async function test_Check_Creation() {
await gZenWorkspaces.removeWorkspace(gZenWorkspaces.activeWorkspace);
const workspacesAfterRemove = gZenWorkspaces.getWorkspaces();
Assert.strictEqual(
workspacesAfterRemove.workspaces.length,
workspacesAfterRemove.length,
1,
"One workspace should exist."
);

View File

@@ -4,6 +4,7 @@
"use strict";
add_task(async function test_Issue_10455() {
debugger;
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.closeWindowWithLastTab", true]],
});
@@ -24,6 +25,7 @@ add_task(async function test_Issue_10455() {
});
add_task(async function test_Issue_10455_Dont_Close() {
debugger;
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.closeWindowWithLastTab", false]],
});

View File

@@ -1,132 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// verify that workspace unloading works
add_task(async function test_UnloadWorkspace_WithMultipleTabs() {
const workspaceId =
await gZenWorkspaces.createAndSaveWorkspace("Test Workspace 1");
const tabs = [];
for (let i = 0; i < 3; i++) {
const tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
`data:text/html,<title>Workspace Tab ${i}</title>`,
true,
{ skipAnimation: true }
);
tab.setAttribute("zen-workspace-id", workspaceId);
tabs.push(tab);
}
for (const tab of tabs) {
ok(!tab.hasAttribute("pending"), "Tab should not be pending before unload");
ok(tab.linkedPanel, "Tab should have linked panel before unload");
}
await gZenWorkspaces.unloadWorkspace();
for (const tab of tabs) {
ok(tab.hasAttribute("pending"), "Tab should be pending after unload");
ok(!tab.linkedPanel, "Tab should not have linked panel after unload");
}
await gZenWorkspaces.removeWorkspace(workspaceId);
});
// verify that essential tabs are not unloaded
add_task(async function test_UnloadWorkspace_WithEssentialTabs() {
const workspaceId =
await gZenWorkspaces.createAndSaveWorkspace("Test Workspace 2");
const regularTab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"data:text/html,<title>Hi! I am a Regular Tab</title>",
true,
{ skipAnimation: true }
);
regularTab.setAttribute("zen-workspace-id", workspaceId);
const essentialTab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
"data:text/html,<title>Hi! I am an Essential Tab</title>",
true,
{ skipAnimation: true }
);
essentialTab.setAttribute("zen-workspace-id", workspaceId);
essentialTab.setAttribute("zen-essential", "true");
await gZenWorkspaces.unloadWorkspace();
ok(regularTab.hasAttribute("pending"), "Regular tab should be unloaded");
ok(!regularTab.linkedPanel, "Regular tab should not have linked panel");
ok(
!essentialTab.hasAttribute("pending"),
"Essential tab should not be unloaded"
);
ok(essentialTab.linkedPanel, "Essential tab should still have linked panel");
await gZenWorkspaces.removeWorkspace(workspaceId);
});
// only tabs from the targeted workspace should be unloaded
add_task(async function test_UnloadWorkspace_TargetedWorkspaceIsolation() {
const inActiveWorkspaceId = await gZenWorkspaces.createAndSaveWorkspace(
"Test In-Active Workspace"
);
const activeWorkspaceId = await gZenWorkspaces.createAndSaveWorkspace(
"Test Active Workspace"
);
const inActiveWorkspaceTabs = [];
for (let i = 0; i < 2; i++) {
const tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
`data:text/html,<title>In-Active Workspace Tab ${i}</title>`,
true,
{ skipAnimation: true }
);
tab.setAttribute("zen-workspace-id", inActiveWorkspaceId);
inActiveWorkspaceTabs.push(tab);
}
const activeWorkspaceTabs = [];
for (let i = 0; i < 2; i++) {
const tab = await BrowserTestUtils.openNewForegroundTab(
window.gBrowser,
`data:text/html,<title>Active Workspace Tab ${i}</title>`,
true,
{ skipAnimation: true }
);
tab.setAttribute("zen-workspace-id", activeWorkspaceId);
activeWorkspaceTabs.push(tab);
}
await gZenWorkspaces.unloadWorkspace(); // this unloads the latest created workspace -> activeWorkspaceId
for (const tab of activeWorkspaceTabs) {
ok(
tab.hasAttribute("pending"),
"Active workspace tab should be pending after unload"
);
ok(
!tab.linkedPanel,
"Active workspace tab should not have linked panel after unload"
);
}
for (const tab of inActiveWorkspaceTabs) {
ok(
!tab.hasAttribute("pending"),
"In-Active workspace tab should NOT be pending after unload"
);
ok(
tab.linkedPanel,
"In-Active workspace tab should still have linked panel after unload"
);
}
await gZenWorkspaces.removeWorkspace(inActiveWorkspaceId);
await gZenWorkspaces.removeWorkspace(activeWorkspaceId);
});