From 3655afbe13dff96a00af4c789a1d3180ceecc79c Mon Sep 17 00:00:00 2001 From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com> Date: Wed, 2 Sep 2026 02:29:59 +0200 Subject: [PATCH] gh-15089: Prevent folder expansion on sync (gh-15197) --- .../browser/preferences/zen-preferences.ftl | 4 + .../content/ContainerCreationPanel-mjs.patch | 21 +++ .../CustomizableUI-sys-mjs.patch | 29 +++- .../preferences/config/account-sync-mjs.patch | 40 ++++- .../tabbrowser/content/tabgroup-js.patch | 31 ++-- src/browser/themes/shared/zen-icons/icons.css | 2 +- .../dist/tokens-shared-css.patch | 19 +- src/zen/common/modules/ZenUIManager.mjs | 1 - src/zen/common/styles/zen-omnibox.css | 6 +- src/zen/common/styles/zen-theme.css | 10 +- src/zen/spaces/ZenSpaceCreation.mjs | 75 +++++++- src/zen/spaces/ZenSpaceManager.mjs | 29 +--- src/zen/spaces/create-workspace-form.css | 2 +- src/zen/sync/ZenSpacesSyncApplier.sys.mjs | 33 ++-- src/zen/sync/ZenSpacesSyncModel.sys.mjs | 163 ++++++++++-------- src/zen/tabs/zen-tabs/vertical-tabs.css | 11 +- 16 files changed, 317 insertions(+), 159 deletions(-) create mode 100644 src/browser/components/contextualidentity/content/ContainerCreationPanel-mjs.patch diff --git a/locales/en-US/browser/browser/preferences/zen-preferences.ftl b/locales/en-US/browser/browser/preferences/zen-preferences.ftl index 473bfb740..bb31d6c47 100644 --- a/locales/en-US/browser/browser/preferences/zen-preferences.ftl +++ b/locales/en-US/browser/browser/preferences/zen-preferences.ftl @@ -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 diff --git a/src/browser/components/contextualidentity/content/ContainerCreationPanel-mjs.patch b/src/browser/components/contextualidentity/content/ContainerCreationPanel-mjs.patch new file mode 100644 index 000000000..94fe02099 --- /dev/null +++ b/src/browser/components/contextualidentity/content/ContainerCreationPanel-mjs.patch @@ -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()); + }, + }; diff --git a/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch b/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch index ba2b8eb35..ba2213692 100644 --- a/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch +++ b/src/browser/components/customizableui/CustomizableUI-sys-mjs.patch @@ -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": { diff --git a/src/browser/components/preferences/config/account-sync-mjs.patch b/src/browser/components/preferences/config/account-sync-mjs.patch index ce14b3e05..893b2a58b 100644 --- a/src/browser/components/preferences/config/account-sync-mjs.patch +++ b/src/browser/components/preferences/config/account-sync-mjs.patch @@ -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", diff --git a/src/browser/components/tabbrowser/content/tabgroup-js.patch b/src/browser/components/tabbrowser/content/tabgroup-js.patch index 140b011c2..f4c2e5077 100644 --- a/src/browser/components/tabbrowser/content/tabgroup-js.patch +++ b/src/browser/components/tabbrowser/content/tabgroup-js.patch @@ -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 @@ } } diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index e76187864..5a083ca7d 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -880,7 +880,7 @@ border-radius: var(--tab-border-radius); &:hover { - background: var(--toolbarbutton-hover-background); + background: var(--toolbarbutton-background-color-hover); } & image { diff --git a/src/toolkit/themes/shared/design-system/dist/tokens-shared-css.patch b/src/toolkit/themes/shared/design-system/dist/tokens-shared-css.patch index 0dcc3ed65..f3da598d9 100644 --- a/src/toolkit/themes/shared/design-system/dist/tokens-shared-css.patch +++ b/src/toolkit/themes/shared/design-system/dist/tokens-shared-css.patch @@ -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); diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index d205f6951..770ffbb22 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -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. diff --git a/src/zen/common/styles/zen-omnibox.css b/src/zen/common/styles/zen-omnibox.css index 532632174..b392654a5 100644 --- a/src/zen/common/styles/zen-omnibox.css +++ b/src/zen/common/styles/zen-omnibox.css @@ -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"]) & { diff --git a/src/zen/common/styles/zen-theme.css b/src/zen/common/styles/zen-theme.css index fd5a01175..4be226527 100644 --- a/src/zen/common/styles/zen-theme.css +++ b/src/zen/common/styles/zen-theme.css @@ -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( diff --git a/src/zen/spaces/ZenSpaceCreation.mjs b/src/zen/spaces/ZenSpaceCreation.mjs index 12f2eb34e..14184a8d8 100644 --- a/src/zen/spaces/ZenSpaceCreation.mjs +++ b/src/zen/spaces/ZenSpaceCreation.mjs @@ -41,7 +41,7 @@ class nsZenWorkspaceCreation extends MozXULElement { - + 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(); } diff --git a/src/zen/spaces/ZenSpaceManager.mjs b/src/zen/spaces/ZenSpaceManager.mjs index c0defb1fa..801222b1f 100644 --- a/src/zen/spaces/ZenSpaceManager.mjs +++ b/src/zen/spaces/ZenSpaceManager.mjs @@ -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"); } } diff --git a/src/zen/spaces/create-workspace-form.css b/src/zen/spaces/create-workspace-form.css index 076c1ac73..dad57de59 100644 --- a/src/zen/spaces/create-workspace-form.css +++ b/src/zen/spaces/create-workspace-form.css @@ -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: ""; diff --git a/src/zen/sync/ZenSpacesSyncApplier.sys.mjs b/src/zen/sync/ZenSpacesSyncApplier.sys.mjs index e166f5367..be661c02b 100644 --- a/src/zen/sync/ZenSpacesSyncApplier.sys.mjs +++ b/src/zen/sync/ZenSpacesSyncApplier.sys.mjs @@ -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; diff --git a/src/zen/sync/ZenSpacesSyncModel.sys.mjs b/src/zen/sync/ZenSpacesSyncModel.sys.mjs index 1e59ff1b6..b180f9f12 100644 --- a/src/zen/sync/ZenSpacesSyncModel.sys.mjs +++ b/src/zen/sync/ZenSpacesSyncModel.sys.mjs @@ -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} root0.tabs - * @param {Array} root0.folders - * @param {Set} root0.splitIds - * @param {Map} root0.splitParents - * @param {Map} 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 }), }, }); } diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 813899459..a19990c6c 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -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;