gh-15089: Prevent folder expansion on sync (gh-15197)

This commit is contained in:
mr. m
2026-09-02 02:29:59 +02:00
committed by GitHub
parent bb023ac5b1
commit 3655afbe13
16 changed files with 317 additions and 159 deletions

View File

@@ -64,6 +64,10 @@ zen-tabs-select-recently-used-on-close =
zen-tabs-close-on-back-with-no-history =
.label = Close tab and switch to its owner tab (or most recently used tab) when going back with no history
zen-settings-workspaces-sync =
.label = Sync your sidebar across devices
.description = Keep your workspaces, pinned tabs and folders in sync on all your devices through your Mozilla account.
zen-settings-workspaces-sync-unpinned-tabs =
.label = Sync only pinned tabs in workspaces

View File

@@ -0,0 +1,21 @@
diff --git a/browser/components/contextualidentity/content/ContainerCreationPanel.mjs b/browser/components/contextualidentity/content/ContainerCreationPanel.mjs
index e04d7a4738192703e8f7f8555f50aaf0a619a5bd..b86e4853c421856f84ad8e017062f0d611b88aad 100644
--- a/browser/components/contextualidentity/content/ContainerCreationPanel.mjs
+++ b/browser/components/contextualidentity/content/ContainerCreationPanel.mjs
@@ -4,7 +4,7 @@
import { ContainerEditor } from "chrome://browser/content/usercontext/ContainerEditor.mjs";
-const ANCHOR_ID = "userContext-icons";
+const ANCHOR_ID = "navigator-toolbox";
const ANCHOR_PINNED_CLASS = "container-anchor-pinned";
const lazy = {};
@@ -95,6 +95,6 @@ export const ContainerCreationPanel = {
anchor.hidden = false;
}
- panel.openPopup(anchor, "bottomright topright");
+ panel.openPopup(anchor, win.gZenUIManager.panelUIPosition());
},
};

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/customizableui/CustomizableUI.sys.mjs b/browser/components/customizableui/CustomizableUI.sys.mjs
index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd8869254d59 100644
index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..3d6e092e7ed126cdee8c32229df2cfa13303d3a3 100644
--- a/browser/components/customizableui/CustomizableUI.sys.mjs
+++ b/browser/components/customizableui/CustomizableUI.sys.mjs
@@ -13,6 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
@@ -163,16 +163,27 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
webExtList.insertBefore(child, webExtList.firstElementChild);
} else {
child.setAttribute("cui-anchorid", this.#defaultListButton.id);
@@ -8006,7 +8009,7 @@ class OverflowableToolbar {
@@ -7995,6 +7998,7 @@ class OverflowableToolbar {
return aElement.getBoundingClientRect().width;
}
+ let toolbar = this.#toolbar;
function sumChildrenInlineSize(aParent, aExceptChild = null) {
let sum = 0;
for (let child of aParent.children) {
@@ -8006,7 +8010,10 @@ class OverflowableToolbar {
) {
continue;
}
- sum += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
+ sum += parseFloat(style.marginLeft) + (win.gZenVerticalTabsManager._hasSetSingleToolbar ? Math.max(0, parseFloat(style.marginRight)) : parseFloat(style.marginRight));
+ if (toolbar.id == 'zen-sidebar-top-buttons' && toolbar.hasAttribute("overflowing")) {
+ sum += 2; // Add extra padding that we remove from the CSS
+ }
if (child != aExceptChild) {
sum += getInlineSize(child);
}
@@ -8030,11 +8033,11 @@ class OverflowableToolbar {
@@ -8030,11 +8037,11 @@ class OverflowableToolbar {
parseFloat(style.paddingLeft) -
parseFloat(style.paddingRight) -
toolbarChildrenWidth;
@@ -186,7 +197,7 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
});
lazy.log.debug(
@@ -8049,7 +8052,39 @@ class OverflowableToolbar {
@@ -8049,7 +8056,39 @@ class OverflowableToolbar {
Math.max(targetWidth, targetChildrenWidth)
);
totalAvailWidth = Math.ceil(totalAvailWidth);
@@ -227,7 +238,7 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
return { isOverflowing, targetContentWidth, totalAvailWidth };
}
@@ -8110,7 +8145,11 @@ class OverflowableToolbar {
@@ -8110,7 +8149,11 @@ class OverflowableToolbar {
return;
}
}
@@ -240,7 +251,7 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
lazy.log.debug(
`Need ${minSize} but width is ${totalAvailWidth} so bailing`
);
@@ -8143,7 +8182,7 @@ class OverflowableToolbar {
@@ -8143,7 +8186,7 @@ class OverflowableToolbar {
}
}
if (!inserted) {
@@ -249,7 +260,7 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
}
child.removeAttribute("cui-anchorid");
child.removeAttribute("overflowedItem");
@@ -8269,6 +8308,9 @@ class OverflowableToolbar {
@@ -8269,6 +8312,9 @@ class OverflowableToolbar {
* if no such list exists.
*/
get #webExtList() {
@@ -259,7 +270,7 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
if (!this.#webExtListRef) {
let targetID = this.#toolbar.getAttribute("addon-webext-overflowtarget");
if (!targetID) {
@@ -8280,6 +8322,9 @@ class OverflowableToolbar {
@@ -8280,6 +8326,9 @@ class OverflowableToolbar {
let win = this.#toolbar.documentGlobal;
let { panel } = win.gUnifiedExtensions;
this.#webExtListRef = panel.querySelector(`#${targetID}`);
@@ -269,7 +280,7 @@ index 6d1885a54815d10b3a4e79f7e5acd61ab3abb159..97e07d85c69ec97baeab05aedd57bd88
}
return this.#webExtListRef;
}
@@ -8488,7 +8533,7 @@ class OverflowableToolbar {
@@ -8488,7 +8537,7 @@ class OverflowableToolbar {
break;
}
case "mousedown": {

View File

@@ -1,8 +1,8 @@
diff --git a/browser/components/preferences/config/account-sync.mjs b/browser/components/preferences/config/account-sync.mjs
index b503987a08e2ce64bfc01c4e3a21e5e19ef834f0..b1c03a0b219fd3eddd93c1deaeb18ab79c85f168 100644
index b01fd4bb163ad53139fcbd8e79e12ef994d1fa13..dd60be4fe268618d6596cd5cfac3342975bd241a 100644
--- a/browser/components/preferences/config/account-sync.mjs
+++ b/browser/components/preferences/config/account-sync.mjs
@@ -42,6 +42,7 @@ Preferences.addAll([
@@ -46,6 +46,7 @@ Preferences.addAll([
{ id: "services.sync.engine.creditcards", type: "bool" },
{ id: "services.sync.engine.addons", type: "bool" },
{ id: "services.sync.engine.prefs", type: "bool" },
@@ -10,7 +10,7 @@ index b503987a08e2ce64bfc01c4e3a21e5e19ef834f0..b1c03a0b219fd3eddd93c1deaeb18ab7
]);
/**
@@ -546,6 +547,7 @@ const SYNC_ENGINE_SETTINGS = [
@@ -557,6 +558,7 @@ const SYNC_ENGINE_SETTINGS = [
type: "payments",
},
{ id: "syncAddons", pref: "services.sync.engine.addons", type: "addons" },
@@ -18,3 +18,37 @@ index b503987a08e2ce64bfc01c4e3a21e5e19ef834f0..b1c03a0b219fd3eddd93c1deaeb18ab7
{ id: "syncSettings", pref: "services.sync.engine.prefs", type: "settings" },
];
@@ -564,6 +566,12 @@ SYNC_ENGINE_SETTINGS.forEach(({ id, pref }) => {
Preferences.addSetting({ id, pref });
});
+Preferences.addSetting({ id: "zenSyncWorkspacesGroup" });
+Preferences.addSetting({
+ id: "zenSyncWorkspaces",
+ pref: "services.sync.engine.spaces",
+});
+
Preferences.addSetting({
id: "syncEngines",
deps: SYNC_ENGINE_SETTINGS.map(({ id }) => id),
@@ -995,6 +1003,20 @@ SettingGroupManager.registerGroups({
id: "syncEnginesList",
control: "sync-engines-list",
},
+ {
+ id: "zenSyncWorkspacesGroup",
+ control: "moz-box-item",
+ items: [
+ {
+ id: "zenSyncWorkspaces",
+ l10nId: "zen-settings-workspaces-sync",
+ control: "moz-toggle",
+ controlAttrs: {
+ inputlayout: "inline-end",
+ },
+ },
+ ],
+ },
{
id: "syncChangeOptions",
control: "moz-box-button",

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabgroup.js b/browser/components/tabbrowser/content/tabgroup.js
index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e356c9f8c6f 100644
index 237950fb2c449ce088c204d33a70b1d95dae549b..635f9dbc5c79b6bb869b4ab4ec2d4b4e226b4672 100644
--- a/browser/components/tabbrowser/content/tabgroup.js
+++ b/browser/components/tabbrowser/content/tabgroup.js
@@ -14,11 +14,11 @@
@@ -101,7 +101,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
resetDefaultGroupName = () => {
this.#defaultGroupName = "";
@@ -175,10 +198,15 @@
@@ -175,10 +198,18 @@
if (!this.#tabChangeObserver) {
this.#tabChangeObserver = new window.MutationObserver(mutations => {
if (!this.tabs.length) {
@@ -112,12 +112,15 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
new CustomEvent("TabGroupRemoved", { bubbles: true })
);
+ gZenVerticalTabsManager.animateItemClose(this).then(() => {
+ this.dispatchEvent(
+ new CustomEvent("TabGroupRemovedFromDOM", { bubbles: true })
+ );
this.remove();
+ });
Services.obs.notifyObservers(
this,
"browser-tabgroup-removed-from-dom"
@@ -223,7 +251,10 @@
@@ -223,7 +254,10 @@
}
});
}
@@ -129,7 +132,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
}
get color() {
@@ -330,6 +361,9 @@
@@ -330,6 +364,9 @@
}
set collapsed(val) {
@@ -139,7 +142,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
if (!!val == this.collapsed) {
return;
}
@@ -416,7 +450,6 @@
@@ -416,7 +453,6 @@
tabGroupName,
})
.then(result => {
@@ -147,7 +150,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
});
}
@@ -491,13 +524,68 @@
@@ -491,13 +527,68 @@
* @returns {MozTabbrowserTab[]}
*/
get tabs() {
@@ -162,9 +165,8 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
+ tabsCollect.push(item);
+ if (gBrowser.isTabGroup(item)) {
+ tabsCollect.push(...item.tabs);
}
}
- return childrenArray.filter(node => node.matches("tab"));
+ }
+ }
+ return tabsCollect.filter(node => node.matches("tab"));
+ }
+
@@ -208,8 +210,9 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
+ currentGroup = currentGroup?.group;
+ if (currentGroup.collapsed) {
+ return false;
+ }
+ }
}
}
- return childrenArray.filter(node => node.matches("tab"));
+ if (this.pinned && gZenWorkspaces.activeWorkspaceElement?.hasCollapsedPinnedTabs) {
+ return false;
+ }
@@ -221,7 +224,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
}
/**
@@ -617,9 +705,6 @@
@@ -617,9 +708,6 @@
});
} else {
if (tabOrSplitView.pinned) {
@@ -231,7 +234,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
}
let tabToMove =
this.documentGlobal === tabOrSplitView.documentGlobal
@@ -682,7 +767,7 @@
@@ -682,7 +770,7 @@
*/
on_click(event) {
let isToggleElement =
@@ -240,7 +243,7 @@ index 237950fb2c449ce088c204d33a70b1d95dae549b..af200a043dcd2e24393fcefe32493e35
event.target === this.#overflowCountLabel;
if (isToggleElement && event.button === 0) {
event.preventDefault();
@@ -761,5 +846,6 @@
@@ -761,5 +849,6 @@
}
}

View File

@@ -880,7 +880,7 @@
border-radius: var(--tab-border-radius);
&:hover {
background: var(--toolbarbutton-hover-background);
background: var(--toolbarbutton-background-color-hover);
}
& image {

View File

@@ -1,5 +1,5 @@
diff --git a/toolkit/themes/shared/design-system/dist/tokens-shared.css b/toolkit/themes/shared/design-system/dist/tokens-shared.css
index 15c0d98a18b4f19e7f2e03d2b78066b0353f416a..b32f6244c7a07e93894bab422691a605456034a1 100644
index 63c641f560d4c0ce06b40a652ca99d1c48cd6941..39c81e773a442870763396e68fb57bd336e3625a 100644
--- a/toolkit/themes/shared/design-system/dist/tokens-shared.css
+++ b/toolkit/themes/shared/design-system/dist/tokens-shared.css
@@ -7,6 +7,8 @@
@@ -11,7 +11,22 @@ index 15c0d98a18b4f19e7f2e03d2b78066b0353f416a..b32f6244c7a07e93894bab422691a605
@layer tokens-foundation {
:root,
:host(.anonymous-content-host) {
@@ -424,8 +426,8 @@
@@ -28,10 +30,10 @@
--border-color-selected: var(--color-accent-primary);
--border-color-transparent: transparent;
--border-radius-circle: 9999px;
- --border-radius-xsmall: 2px;
- --border-radius-small: 4px;
- --border-radius-medium: 8px;
- --border-radius-large: 16px;
+ --border-radius-xsmall: calc(2px * var(--zen-squircle-value));
+ --border-radius-small: calc(4px * var(--zen-squircle-value));
+ --border-radius-medium: calc(8px * var(--zen-squircle-value));
+ --border-radius-large: calc(16px * var(--zen-squircle-value));
--border-width: 1px;
/** box-shadow **/
@@ -477,8 +479,8 @@
--panel-background-color-dimmed-further: var(--background-color-dimmed-further);
--panel-border-color: light-dark(rgb(240, 240, 244), rgb(82, 82, 94));
--panel-border-radius: var(--border-radius-medium);

View File

@@ -868,7 +868,6 @@ window.gZenUIManager = {
},
panelUIPosition(panel, anchor) {
void panel;
// The alignment position of the panel is determined during the "popuppositioned" event
// when the panel opens. The alignment positions help us determine in which orientation
// the panel is anchored to the screen space.

View File

@@ -18,7 +18,7 @@
.urlbar {
--urlbarview-separator-color: light-dark(hsl(0, 0%, 80%), hsl(0, 0%, 20%));
--urlbarview-background-color-hover: var(--toolbarbutton-hover-background);
--urlbarview-background-color-hover: var(--toolbarbutton-background-color-hover);
border-radius: calc(var(--toolbarbutton-border-radius) - 2px);
--urlbarView-results-padding: 10px !important;
--urlbar-container-height: 48px !important;
@@ -351,7 +351,7 @@
padding: 4px !important;
&:hover {
background: var(--toolbarbutton-hover-background);
background: var(--toolbarbutton-background-color-hover);
border-radius: var(--toolbarbutton-border-radius);
overflow: visible;
}
@@ -495,7 +495,7 @@
/* stylelint-disable-next-line stylelint-plugin-mozilla/no-has-selector */
#urlbar-container:has(&) {
border-radius: var(--border-radius-medium);
background: var(--toolbarbutton-hover-background);
background: var(--toolbarbutton-background-color-hover);
height: var(--urlbar-height) !important;
margin-inline: 0.15rem !important;
:root:not([zen-single-toolbar="true"]) & {

View File

@@ -168,7 +168,7 @@
--zen-toolbar-height-with-bookmarks: calc(var(--zen-toolbar-height) + 30px);
}
--toolbarbutton-hover-background: var(--zen-toolbar-element-bg-hover) !important;
--toolbarbutton-background-color-hover: var(--zen-toolbar-element-bg-hover) !important;
--zen-toolbar-button-size: 16px;
@media (-moz-platform: macos) {
--zen-toolbar-button-size: 17px;
@@ -181,7 +181,7 @@
* background on the native theme. */
--toolbar-background-color: light-dark(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.15));
--toolbarbutton-active-background: color-mix(
--toolbarbutton-background-color-active: color-mix(
in srgb,
var(--zen-branding-bg-reverse) 7%,
transparent 93%
@@ -191,9 +191,9 @@
--toolbox-textcolor-inactive: var(--toolbox-textcolor) !important;
/* Other colors */
--urlbar-box-active-bgcolor: var(--toolbarbutton-hover-background) !important;
--urlbar-box-active-bgcolor: var(--toolbarbutton-background-color-hover) !important;
--zen-input-border-color: light-dark(rgb(204, 204, 204), rgb(66, 65, 77));
--urlbar-box-hover-bgcolor: var(--toolbarbutton-hover-background) !important;
--urlbar-box-hover-bgcolor: var(--toolbarbutton-background-color-hover) !important;
--input-text-background-color: light-dark(rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.2)) !important;
--toolbar-field-focus-background-color: light-dark(
rgba(255, 255, 255, 0.25),
@@ -267,7 +267,7 @@
--zen-active-tab-scale: 0.985;
/* Define tab hover background color */
--tab-background-color-hover: var(--toolbarbutton-hover-background);
--tab-background-color-hover: var(--toolbarbutton-background-color-hover);
/* Sidebar Notifications */
--zen-sidebar-notification-bg: color-mix(

View File

@@ -41,7 +41,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
</vbox>
<vbox class="zen-workspace-creation-form">
<hbox class="zen-workspace-creation-name-wrapper">
<toolbarbutton class="zen-workspace-creation-icon-label" />
<toolbarbutton class="zen-workspace-creation-icon-label zen-squircle-before" />
<html:input
class="zen-workspace-creation-name"
type="text"
@@ -78,6 +78,18 @@ class nsZenWorkspaceCreation extends MozXULElement {
return this.getAttribute("previous-workspace-id");
}
get elementsToAnimate() {
return [
this.querySelector(".zen-workspace-creation-title"),
this.querySelector(".zen-workspace-creation-label").parentElement,
this.querySelector(".zen-workspace-creation-name-wrapper"),
this.querySelector(".zen-workspace-creation-profile-wrapper"),
this.querySelector(".zen-workspace-creation-edit-theme-button"),
this.createButton.parentNode,
this.cancelButton,
];
}
get #spaceSwitchDuration() {
return (
Services.prefs.getIntPref("zen.workspaces.switch-animation-duration") /
@@ -161,6 +173,10 @@ class nsZenWorkspaceCreation extends MozXULElement {
.getInterface(Ci.nsIAppWindow)
.rollupAllPopups();
gURLBar.view.close();
gURLBar.handleRevert();
gURLBar.blur();
this.handleZenWorkspacesChangeBind =
this.handleZenWorkspacesChange.bind(this);
@@ -262,7 +278,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
workspace.containerTabId = this.currentProfile;
await gZenWorkspaces.saveWorkspace(workspace);
this.#cleanup();
await this.#cleanup();
gZenWorkspaces._organizeWorkspaceStripLocations(workspace, true);
gZenWorkspaces.updateTabsContainers();
@@ -344,11 +360,30 @@ class nsZenWorkspaceCreation extends MozXULElement {
}
async handleZenWorkspacesChange() {
this.#cleanup();
// The space we were living in has already slid away, no need to animate
// ourselves out of it.
await this.#cleanup({ animateOut: false });
await gZenWorkspaces.removeWorkspace(this.workspaceId);
}
#cleanup() {
async #cleanup({ animateOut = true } = {}) {
if (animateOut && !gReduceMotion && this.isConnected) {
await gZenUIManager.motion.animate(
this.elementsToAnimate.reverse(),
{
y: [0, 20],
opacity: [1, 0],
filter: ["blur(0)", "blur(2px)"],
},
{
duration: 0.3,
type: "spring",
bounce: 0,
delay: gZenUIManager.motion.stagger(0.03),
}
);
}
document.getElementById("zen-sidebar-splitter").style.pointerEvents = "";
gZenWorkspaces.removeChangeListeners(this.handleZenWorkspacesChangeBind);
@@ -376,12 +411,38 @@ class nsZenWorkspaceCreation extends MozXULElement {
element.hidden = false;
}
// Now that the form is gone the space owns its essentials again.
// Now that the form is gone the space owns its essentials again, so let
// it lay itself out before we fade everything back in.
const workspace = gZenWorkspaces.getActiveWorkspace();
gZenWorkspaces._organizeWorkspaceStripLocations(workspace);
gZenWorkspaces.updateTabsContainers();
// The essentials slid away when we arrived, slide them back in.
gZenWorkspaces.restoreEssentialsPosition();
// The essentials were parked off screen while we were up, put them back.
gZenWorkspaces.resetEssentialsPosition();
if (animateOut && !gReduceMotion) {
const elementsToReveal = revealedElements.filter(
element => element?.isConnected
);
if (elementsToReveal.length) {
gZenUIManager.motion
.animate(
elementsToReveal,
{
opacity: [0, 1],
},
{
duration: 0.3,
type: "spring",
bounce: 0,
}
)
.then(() => {
for (const element of elementsToReveal) {
element.style.removeProperty("opacity");
}
});
}
}
gZenUIManager.updateTabsToolbar();
}

View File

@@ -1939,36 +1939,13 @@ class nsZenWorkspaces {
}
/**
* Slide the essentials back from wherever a creation form parked them.
* Drop the offset a creation form parked the essentials at.
*/
restoreEssentialsPosition() {
const duration =
Services.prefs.getIntPref("zen.workspaces.switch-animation-duration") /
1000;
resetEssentialsPosition() {
for (const container of document.querySelectorAll(
"#zen-essentials .zen-essentials-container"
)) {
const existingTransform = container.style.transform;
const parkedOffset = parseFloat(existingTransform.split("(")[1]) || 0;
if (!parkedOffset || gReduceMotion) {
container.style.removeProperty("transform");
continue;
}
gZenUIManager.motion
.animate(
container,
{
transform: [existingTransform, "translateX(0%)"],
},
{
type: "spring",
bounce: 0,
duration,
}
)
.then(() => {
container.style.removeProperty("transform");
});
container.style.removeProperty("transform");
}
}

View File

@@ -96,7 +96,7 @@ zen-workspace-creation {
&::before {
border: 1px dashed light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.5));
border-radius: 4px;
border-radius: 6px;
width: calc(100% + 2px);
height: calc(100% + 2px);
content: "";

View File

@@ -143,16 +143,23 @@ class nsZenSpacesSyncApplier {
} else {
await win.gZenWorkspaces.promiseInitialized;
this.#maybePlayFirstSyncAnimation(win);
const removals = this.#routeTombstones(win, deletions);
this.#deleteTabs(win, removals.tabs, fail);
this.#deleteSplits(win, removals.splits, fail);
await this.#applySpaces(win, incoming.spaces, fail);
await this.#applyFolders(win, incoming.folders, fail);
this.#applyTabs(win, incoming.tabs, fail);
this.#applySplits(win, incoming.splits, fail);
await this.#deleteFolders(win, removals.folders, fail);
await this.#deleteSpaces(win, removals.spaces, fail);
this.#applyOrdering(win, incoming, fail);
// A sync apply is a materialization just like session restore,
// so it must stay visually silent (gh-15089).
win.gZenFolders._sessionRestoring = true;
try {
const removals = this.#routeTombstones(win, deletions);
this.#deleteTabs(win, removals.tabs, fail);
this.#deleteSplits(win, removals.splits, fail);
await this.#applySpaces(win, incoming.spaces, fail);
await this.#applyFolders(win, incoming.folders, fail);
this.#applyTabs(win, incoming.tabs, fail);
this.#applySplits(win, incoming.splits, fail);
await this.#deleteFolders(win, removals.folders, fail);
await this.#deleteSpaces(win, removals.spaces, fail);
this.#applyOrdering(win, incoming, fail);
} finally {
delete win.gZenFolders._sessionRestoring;
}
// Collect the session soon so the stored sidebar (and with it the
// sync projections) reflects the applied state instead of re-uploading
// the pre-apply one.
@@ -401,9 +408,12 @@ class nsZenSpacesSyncApplier {
// Parents before children so nesting targets exist.
const depths = new Map(folders.map(f => [f.key, f.data.parentFolderId]));
const depthOf = key => {
// The seen set only guards against a corrupt parentId cycle.
const seen = new Set([key]);
let depth = 0;
let parent = depths.get(key);
while (parent && depths.has(parent) && depth < 10) {
while (parent && depths.has(parent) && !seen.has(parent)) {
seen.add(parent);
depth++;
parent = depths.get(parent);
}
@@ -423,6 +433,7 @@ class nsZenSpacesSyncApplier {
workspaceId:
data.workspaceUuid || win.gZenWorkspaces.activeWorkspace,
isLiveFolder: !!data.live,
collapsed: true,
});
} else if (data.name && folder.label !== data.name) {
folder.label = data.name;

View File

@@ -300,66 +300,95 @@ class nsZenSpacesSyncModel {
/**
* Builds the ordered child id list for one parent (a space's pinned
* section or a folder). Tabs and splits are ordered by their position in
* the collected tab list; folders are spliced in next to their recorded
* previous sibling.
* section or a folder) from the collected tab list, which is in strip
* order.
*
* @param {object} root0
* @param {Array<object>} root0.tabs
* @param {Array<object>} root0.folders
* @param {Set<string>} root0.splitIds
* @param {Map<string, ?string>} root0.splitParents
* @param {Map<string, ?string>} root0.splitWs
* @param {object} root0.scope
* @param {object} ctx - The projection context.
* @param {{space?: string, folder?: string}} scope
*/
#childSequence({ tabs, folders, splitIds, splitParents, splitWs, scope }) {
#childSequence(ctx, scope) {
const seq = [];
const seenSplits = new Set();
for (const tab of tabs) {
const seen = new Set();
const push = id => {
if (!seen.has(id)) {
seen.add(id);
seq.push(id);
}
};
for (const tab of ctx.allTabs) {
const groupId = tab.groupId || null;
if (groupId && splitIds.has(groupId)) {
if (!groupId) {
if (
!seenSplits.has(groupId) &&
scope.matchSplit(
splitParents.get(groupId) ?? null,
splitWs.get(groupId) ?? null
)
scope.space &&
!tab.zenEssential &&
(tab.zenWorkspace || null) === scope.space &&
ctx.syncableTabIds.has(tab.zenSyncId)
) {
seenSplits.add(groupId);
seq.push(groupId);
push(tab.zenSyncId);
}
continue;
}
if (scope.matchTab(tab, groupId)) {
seq.push(tab.zenSyncId);
}
}
for (const folder of folders) {
if (!scope.matchFolder(folder)) {
if (scope.folder && groupId === scope.folder) {
if (ctx.syncableTabIds.has(tab.zenSyncId)) {
push(tab.zenSyncId);
}
continue;
}
let index = seq.length;
const prev = folder.prevSiblingInfo;
if (!prev || prev.type === "start") {
// No stored sibling means the folder is the first child of its
// container (the section rebuild on startup places content before
// the fake start element, so the first item has no sibling at all).
index = 0;
} else if (prev.id) {
const at = seq.indexOf(prev.id);
if (at !== -1) {
index = at + 1;
} else if (prev.type === "tab") {
// An unsynced tab sibling (empty placeholders, live folder items)
// always sits at the start of its container.
index = 0;
if (ctx.splitIds.has(groupId) || ctx.splitParents.has(groupId)) {
const parent = ctx.splitParents.get(groupId) ?? null;
const isDirect = scope.folder ? parent === scope.folder : !parent;
if (isDirect) {
if (
ctx.splitIds.has(groupId) &&
(!scope.space || (ctx.splitWs.get(groupId) ?? null) === scope.space)
) {
push(groupId);
}
} else if (parent) {
const child = this.#scopeChildFolder(ctx, scope, parent);
if (child) {
push(child);
}
}
continue;
}
if (ctx.folderById.has(groupId)) {
const child = this.#scopeChildFolder(ctx, scope, groupId);
if (child) {
push(child);
}
}
seq.splice(index, 0, folder.id);
}
return seq;
}
/**
* Walks a folder's parent chain up to the direct child of the given
* scope, or null when the chain leads somewhere else.
*
* @param {object} ctx - The projection context.
* @param {{space?: string, folder?: string}} scope
* @param {string} folderId
* @returns {?string}
*/
#scopeChildFolder(ctx, scope, folderId) {
// The seen set only guards against a corrupt parentId cycle.
const seen = new Set();
let current = ctx.folderById.get(folderId);
while (current && !seen.has(current.id)) {
seen.add(current.id);
const parentId = current.parentId || null;
if (scope.folder ? parentId === scope.folder : !parentId) {
if (scope.space && (current.workspaceId || null) !== scope.space) {
return null;
}
return current.id;
}
current = parentId ? ctx.folderById.get(parentId) : null;
}
return null;
}
/**
* Derives the shared projection context from one sidebar collection.
* Syncable tabs, folders, the syncable splits and their parents and
@@ -368,11 +397,13 @@ class nsZenSpacesSyncModel {
* @param {object} sidebar
*/
#projectionContext(sidebar) {
const tabs = (sidebar.tabs || []).filter(t => this.#isSyncableTab(t));
const allTabs = sidebar.tabs || [];
const tabs = allTabs.filter(t => this.#isSyncableTab(t));
const folders = (sidebar.folders || []).filter(
f => f?.id && !f.splitViewGroup
);
const folderIds = new Set(folders.map(f => f.id));
const folderById = new Map(folders.map(f => [f.id, f]));
const allSplits = (sidebar.splitViewData || []).filter(g => g?.groupId);
const splitParents = new Map();
for (const entry of sidebar.folders || []) {
@@ -406,7 +437,18 @@ class nsZenSpacesSyncModel {
}
return folderIds.has(groupId) ? groupId : null;
};
return { tabs, folders, splits, splitIds, splitParents, splitWs, folderOf };
return {
allTabs,
tabs,
folders,
folderById,
syncableTabIds,
splits,
splitIds,
splitParents,
splitWs,
folderOf,
};
}
/**
@@ -452,7 +494,7 @@ class nsZenSpacesSyncModel {
const map = new Map();
const pending = new Set();
const ctx = this.#projectionContext(sidebar);
const { tabs, folders, splits, splitIds, splitParents, splitWs } = ctx;
const { tabs, folders, splits, splitParents, splitWs } = ctx;
for (const identity of lazy.ContextualIdentityService.getPublicIdentities()) {
if (!identity.name) {
@@ -491,21 +533,7 @@ class nsZenSpacesSyncModel {
containerGuid: this.guidForContextId(space.containerTabId, {
create: true,
}),
children: this.#childSequence({
tabs,
folders,
splitIds,
splitParents,
splitWs,
scope: {
matchTab: (t, groupId) =>
!groupId &&
!t.zenEssential &&
(t.zenWorkspace || null) === uuid,
matchSplit: (parent, ws) => !parent && ws === uuid,
matchFolder: f => !f.parentId && (f.workspaceId || null) === uuid,
},
}),
children: this.#childSequence(ctx, { space: uuid }),
},
});
}
@@ -533,18 +561,7 @@ class nsZenSpacesSyncModel {
workspaceUuid: folder.workspaceId || null,
parentFolderId: folder.parentId || null,
live,
children: this.#childSequence({
tabs,
folders,
splitIds,
splitParents,
splitWs,
scope: {
matchTab: (t, groupId) => groupId === fid,
matchSplit: parent => parent === fid,
matchFolder: f => f.parentId === fid,
},
}),
children: this.#childSequence(ctx, { folder: fid }),
},
});
}

View File

@@ -103,7 +103,7 @@
/* stylelint-disable-next-line stylelint-plugin-mozilla/no-has-selector */
& #urlbar-container[breakout="true"]:has(#urlbar[zen-floating-urlbar="true"]) {
--urlbar-container-height: 36px !important;
--urlbar-height: 38px !important;
--urlbar-height: 36px !important;
}
& #nav-bar {
@@ -114,6 +114,11 @@
& #zen-sidebar-top-buttons {
margin: calc(var(--zen-toolbox-padding) / 2) 0;
&[overflowing] .toolbarbutton-1 {
--toolbarbutton-padding-outer: 0px;
}
:root[zen-right-side="true"]:not([zen-window-buttons-reversed="true"]) & {
margin-top: 0;
}
@@ -598,7 +603,7 @@
}
&:is([open], [checked]) {
background: var(--toolbarbutton-active-background) !important;
background: var(--toolbarbutton-background-color-hover) !important;
& image,
label {
background: transparent !important;
@@ -606,7 +611,7 @@
}
&:hover {
background: var(--toolbarbutton-hover-background);
background: var(--toolbarbutton-background-color-hover);
& image,
label {
background: transparent !important;