gh-15329: Prevent split view preview from opening for multiple tabs (gh-15340)

This commit is contained in:
mr. m
2026-09-10 22:20:12 +02:00
committed by GitHub
parent 6d158223b5
commit 0a548c7572
8 changed files with 142 additions and 7 deletions

View File

@@ -336,7 +336,9 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
if (
!gBrowser.isTab(draggedTab) ||
gBrowser.selectedTab.hasAttribute("zen-empty-tab") ||
draggedTab.documentGlobal !== window
draggedTab.documentGlobal !== window ||
// See gh-15329.
draggedTab.multiselected
) {
return;
}

View File

@@ -526,9 +526,6 @@ class nsZenSpacesSyncApplier {
if (!folder?.isZenFolder) {
continue;
}
// Members without their own tombstone survive: unpack, then delete
// the (now empty) folder.
await folder.unpackTabs();
await folder.delete();
} catch (e) {
fail(record, e);

View File

@@ -12,6 +12,8 @@ support-files = [
"head.js",
]
["browser_folder_delete.js"]
["browser_normal_tabs_apply.js"]
["browser_normal_tabs_projection.js"]

View File

@@ -0,0 +1,100 @@
/* Any copyright is dedicated to the Public Domain.
https://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function applyFolderWithMembers(fid, memberIds) {
const failed = await ZenSpacesSyncApplier.applyBatch([
folderRecord(fid),
...memberIds.map(id => tabRecord(id, { pinned: true, folderId: fid })),
]);
Assert.deepEqual(failed, [], "The folder batch should apply cleanly");
const folder = document.getElementById(fid);
Assert.ok(folder?.isZenFolder, "The folder should materialize");
for (const id of memberIds) {
Assert.equal(
document.getElementById(id)?.group,
folder,
`member ${id} should live in the folder`
);
}
}
add_task(async function test_FolderTombstoneWithMemberTombstones() {
await gZenWorkspaces.promiseInitialized;
const fid = "test-sync-del-a-folder";
const members = ["test-sync-del-a-1", "test-sync-del-a-2"];
await applyFolderWithMembers(fid, members);
const failed = await ZenSpacesSyncApplier.applyBatch([
tombstone(fid),
...members.map(tombstone),
]);
Assert.deepEqual(failed, [], "The tombstone batch should apply cleanly");
await TestUtils.waitForCondition(
() => !document.getElementById(fid),
"waiting for the folder to be removed"
);
for (const id of members) {
Assert.ok(!document.getElementById(id), `member ${id} should be gone`);
}
for (const id of [fid, ...members]) {
ZenSpacesSyncModel.noteApplied(id, null);
}
});
add_task(async function test_FolderTombstoneAloneDeletesMembers() {
await gZenWorkspaces.promiseInitialized;
const fid = "test-sync-del-b-folder";
const members = ["test-sync-del-b-1", "test-sync-del-b-2"];
await applyFolderWithMembers(fid, members);
const failed = await ZenSpacesSyncApplier.applyBatch([tombstone(fid)]);
Assert.deepEqual(failed, [], "The tombstone batch should apply cleanly");
await TestUtils.waitForCondition(
() => !document.getElementById(fid),
"waiting for the folder to be removed"
);
for (const id of members) {
const tab = document.getElementById(id);
Assert.ok(!tab, `member ${id} should be deleted, not orphaned as pinned`);
}
for (const id of [fid, ...members]) {
ZenSpacesSyncModel.noteApplied(id, null);
}
});
add_task(async function test_MemberMovedOutSurvivesFolderTombstone() {
await gZenWorkspaces.promiseInitialized;
const fid = "test-sync-del-c-folder";
const stays = "test-sync-del-c-stays";
const moved = "test-sync-del-c-moved";
await applyFolderWithMembers(fid, [stays, moved]);
const failed = await ZenSpacesSyncApplier.applyBatch([
tabRecord(moved, { pinned: true, folderId: null }),
tombstone(fid),
]);
Assert.deepEqual(failed, [], "The batch should apply cleanly");
await TestUtils.waitForCondition(
() => !document.getElementById(fid),
"waiting for the folder to be removed"
);
const movedTab = document.getElementById(moved);
Assert.ok(movedTab, "the moved-out member should survive");
Assert.ok(!movedTab.group?.isZenFolder, "and no longer be in a folder");
Assert.ok(
!document.getElementById(stays),
"the member with no surviving record should be deleted"
);
ZenSpacesSyncModel.noteApplied(moved, null);
for (const id of [fid, stays]) {
ZenSpacesSyncModel.noteApplied(id, null);
}
if (movedTab) {
BrowserTestUtils.removeTab(movedTab);
}
});

View File

@@ -22,6 +22,11 @@ const { TestUtils } = ChromeUtils.importESModule(
const { E10SUtils } = ChromeUtils.importESModule(
"resource://gre/modules/E10SUtils.sys.mjs"
);
const { PromiseTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/menu-bookmark-tab/);
const NORMAL_TABS_PREF = "zen.spaces-sync.normal-tabs";
@@ -47,7 +52,13 @@ async function openSyncableTab(url, { pinned = false } = {}) {
function tabRecord(
id,
{ pinned, url = "https://example.com/", current = null, workspaceUuid } = {}
{
pinned,
url = "https://example.com/",
current = null,
workspaceUuid,
folderId = null,
} = {}
) {
return {
id,
@@ -64,7 +75,7 @@ function tabRecord(
essential: false,
pinned,
workspaceUuid: workspaceUuid ?? gZenWorkspaces.activeWorkspace,
folderId: null,
folderId,
staticLabel: null,
hasStaticIcon: false,
defaultContainer: false,
@@ -72,3 +83,26 @@ function tabRecord(
},
};
}
function folderRecord(id, { name = "Synced folder", workspaceUuid } = {}) {
return {
id,
deleted: false,
cleartext: {
kind: "folder",
data: {
folderId: id,
name,
icon: null,
workspaceUuid: workspaceUuid ?? gZenWorkspaces.activeWorkspace,
parentFolderId: null,
live: null,
children: [],
},
},
};
}
function tombstone(id) {
return { id, deleted: true };
}

View File

@@ -6,6 +6,7 @@ BROWSER_CHROME_MANIFESTS += [
"boosts/browser.toml",
"compact_mode/browser.toml",
"container_essentials/browser.toml",
"device_sync/browser.toml",
"folders/browser.toml",
"glance/browser.toml",
"live-folders/browser.toml",
@@ -16,7 +17,6 @@ BROWSER_CHROME_MANIFESTS += [
"site_control/browser.toml",
"space_routing/browser.toml",
"spaces/browser.toml",
"spaces_sync/browser.toml",
"split_view/browser.toml",
"tabs/browser.toml",
"ub-actions/browser.toml",