Compare commits

...

28 Commits

Author SHA1 Message Date
mr-cheffy
d7efa809bb chore: Sync upstream to Firefox 152.0.6 2026-07-14 17:13:39 +00:00
Andrey Bochkarev
c7e4217204 no-bug: Ignore zen-empty-tab in #isLastTabWindow (gh-14564)
When dragging a tab from the blank window to the main window, the blank
window doesn't close because '#isLastTabWindow' considers
'zen-empty-tab' as a normal tab.
2026-07-13 13:40:43 +02:00
Andrey Bochkarev
e837158e4c gh-14545: Correctly handle dnd moving a tab to an empty space (gh-14562) 2026-07-12 17:29:29 +02:00
Andrey Bochkarev
a1a2268f9b gh-14511: Prevent switching spaces when dragging outside (gh-14515)
fixed: https://github.com/zen-browser/desktop/issues/14511
2026-07-12 12:57:45 +02:00
Nicholas Leigh
274b9f9f83 gh-14495: Fix misaligned symbolic space icons in Space options menu on Linux (gh-14498) 2026-07-11 13:38:05 +02:00
Ashvin Jangid
fe0cf3601a gh-14544: Fix essentials promo ui leaving gap on pressing esc (gh-14547)
Fixes https://github.com/zen-browser/desktop/issues/14544

Also fixes essentials promo having weird ui with container specific
essentials


https://github.com/user-attachments/assets/f6df3e48-5fa2-471e-afbb-2472efa0620b
2026-07-11 12:45:20 +02:00
Kristijan Ribarić
61e0790144 gh-14470: Rename all occurances of Workspaces to Spaces in sync (gh-14528) 2026-07-09 01:37:00 +02:00
Andrey Bochkarev
03f251ff4a gh-13870: Remove two spaces cycling (gh-14529) 2026-07-09 01:36:51 +02:00
mr. m
5e17ef46ca gh-14521: Fixed theme settings group being visible (gh-14524) 2026-07-08 20:59:48 +02:00
Ashvin Jangid
decd9548dd gh-14507: Fix essentials being added to folder without being removed (gh-14508)
Co-authored-by: mr. m <mr.m@tuta.com>
2026-07-07 21:35:23 +02:00
mr. m
27d290c77d gh-14514: Sync upstream Firefox to version 152.0.5 (gh-14513) 2026-07-07 21:31:01 +02:00
mr. m
5ecbc13eb6 gh-14470: Fixed patch export for sync modules (gh-14490) 2026-07-06 09:55:03 +02:00
mr. m
58611690b5 gh-14470: Fixed wrong sync engine name (gh-14486) 2026-07-05 20:32:03 +02:00
Andrey Bochkarev
68c8fe18bc gh-14242: Fix route tab doesn't change container (gh-14482) 2026-07-05 17:01:20 +02:00
Andrey Bochkarev
3a72290f16 gh-13870: Fix essentials overlap and fix two space wrap-around (gh-14476) 2026-07-05 12:45:35 +02:00
Ashvin Jangid
79d3d6a993 gh-14460: prevent nested glance tabs (gh-14480)
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-07-05 12:45:07 +02:00
InlitX
e9c35b0f92 gh-10749: Localize command palette actions, emoji picker, etc (gh-14473) 2026-07-05 12:19:18 +02:00
mr. m
647e219639 gh-14470: Add space and container sync (gh-14471)
Co-authored-by: Kristijan Ribarić <96492763+kristijanribaric@users.noreply.github.com>
Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
2026-07-04 11:18:08 +02:00
mr. m
a685738db4 gh-14431: Fixed pinned tabs broken on latest twilight (gh-14446) 2026-07-02 11:01:21 +02:00
Ashvin Jangid
70f19e6d3d gh-14431: Do not allow glance tabs to be pinned (gh-14432) 2026-07-01 12:41:31 +02:00
Raghav
312ad69d65 gh-12948: Removed ai related stuff from settings search (gh-14412) 2026-06-30 22:58:34 +02:00
mr. m
5d10922d91 gh-14427: Sync upstream Firefox to version 152.0.4 (gh-14426)
This PR syncs the upstream Firefox to version 152.0.4.
2026-06-30 19:55:10 +02:00
mr. m
44f7616238 Merge commit from fork 2026-06-30 13:01:01 +02:00
mr. m
dc3ff8b1c9 gh-14382: Fixed overflowing addons not having context menus (gh-14423) 2026-06-30 12:28:46 +02:00
Bernhard
06392af296 gh-14390: fix profile picker menu when creating new workspace (gh-14421) 2026-06-30 12:06:49 +02:00
mr. m
87220f71f4 gh-14417: Fixed tab renaming selection range not starting from the start (gh-14418) 2026-06-29 17:10:23 +02:00
Ashvin Jangid
84ba80bc11 no-bug: set last selected workspace tab to background tab (gh-14395) 2026-06-28 10:40:13 +02:00
Bernhard
e59e37fcbb no-bug: Appending ellipsis to menu items triggering further actions (gh-14388) 2026-06-27 21:12:58 +02:00
43 changed files with 1151 additions and 407 deletions

View File

@@ -34,8 +34,8 @@ Zen is a firefox-based browser with the aim of pushing your productivity to a ne
### Firefox Versions ### Firefox Versions
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `152.0.3`! 🚀 - [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `152.0.6`! 🚀
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.3`! - [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.5`!
### Contributing ### Contributing

View File

@@ -1 +1 @@
b223f3727a037a7b8a0f36f34bfe8c4622a7f387 e7fbb446077536139cdd78b6a0386a97237af20f

View File

@@ -24,3 +24,5 @@ files:
translation: browser/browser/zen-boosts.ftl translation: browser/browser/zen-boosts.ftl
- source: en-US/browser/browser/zen-space-routing.ftl - source: en-US/browser/browser/zen-space-routing.ftl
translation: browser/browser/zen-space-routing.ftl translation: browser/browser/zen-space-routing.ftl
- source: en-US/browser/browser/zen-command-palette.ftl
translation: browser/browser/zen-command-palette.ftl

View File

@@ -0,0 +1,36 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
zen-action-toggle-compact-mode = Toggle Compact Mode
zen-action-open-theme-picker = Open Theme Picker
zen-action-new-split-view = New Split View
zen-action-new-folder = New Folder
zen-action-copy-current-url = Copy Current URL
zen-action-settings = Settings
zen-action-open-private-window = Open Private Window
zen-action-open-new-window = Open New Window
zen-action-new-blank-window = New Blank Window
zen-action-pin-tab = Pin Tab
zen-action-unpin-tab = Unpin Tab
zen-action-open-space-routing = Open Space Routing
zen-action-new-boost = New Boost
zen-action-next-space = Next Space
zen-action-previous-space = Previous Space
zen-action-close-tab = Close Tab
zen-action-reload-tab = Reload Tab
zen-action-reload-tab-without-cache = Reload Tab Without Cache
zen-action-next-tab = Next Tab
zen-action-previous-tab = Previous Tab
zen-action-capture-screenshot = Capture Screenshot
zen-action-toggle-tabs-on-right = Toggle Tabs on right
zen-action-add-to-essentials = Add to Essentials
zen-action-remove-from-essentials = Remove from Essentials
zen-action-find-in-page = Find in Page
zen-action-manage-extensions = Manage Extensions
zen-action-switch-to-automatic-appearance = Switch to Automatic Appearance
zen-action-switch-to-light-mode = Switch to Light Mode
zen-action-switch-to-dark-mode = Switch to Dark Mode
zen-action-print = Print
zen-action-focus-on = Focus on
zen-action-extension = Extension

View File

@@ -6,13 +6,13 @@ zen-folders-search-placeholder =
.placeholder = Search { $folder-name }... .placeholder = Search { $folder-name }...
zen-folders-panel-rename-folder = zen-folders-panel-rename-folder =
.label = Rename Folder .label = Rename Folder
zen-folders-panel-unpack-folder = zen-folders-panel-unpack-folder =
.label = Unpack Folder .label = Unpack Folder
zen-folders-new-subfolder = zen-folders-new-subfolder =
.label = New Subfolder .label = New Subfolder
zen-folders-panel-delete-folder = zen-folders-panel-delete-folder =
.label = Delete Folder .label = Delete Folder
@@ -21,7 +21,7 @@ zen-folders-panel-convert-folder-to-space =
.label = Convert folder to Space .label = Convert folder to Space
zen-folders-panel-change-folder-space = zen-folders-panel-change-folder-space =
.label = Change Space... .label = Change Space
zen-folders-unload-all-tooltip = zen-folders-unload-all-tooltip =
.tooltiptext = Unload active in this folder .tooltiptext = Unload active in this folder

View File

@@ -30,12 +30,12 @@ tab-context-zen-replace-pinned-url-with-current =
.label = Replace with Current URL .label = Replace with Current URL
.accesskey = C .accesskey = C
tab-context-zen-edit-pinned-url = tab-context-zen-edit-pinned-url =
.label = Edit... .label = Edit
.accesskey = E .accesskey = E
tab-context-zen-edit-title = tab-context-zen-edit-title =
.label = Change Label... .label = Change Label
tab-context-zen-edit-icon = tab-context-zen-edit-icon =
.label = Change Icon... .label = Change Icon
zen-themes-corrupted = Your { -brand-short-name } mods file is corrupted. They have been reset to the default theme. zen-themes-corrupted = Your { -brand-short-name } mods file is corrupted. They have been reset to the default theme.
zen-shortcuts-corrupted = Your { -brand-short-name } shortcuts file is corrupted. They have been reset to the default shortcuts. zen-shortcuts-corrupted = Your { -brand-short-name } shortcuts file is corrupted. They have been reset to the default shortcuts.
@@ -87,6 +87,8 @@ zen-icons-picker-emoji =
.label = Emojis .label = Emojis
zen-icons-picker-svg = zen-icons-picker-svg =
.label = Icons .label = Icons
zen-emojis-picker-search =
.placeholder = Search emojis
urlbar-search-mode-zen_actions = Actions urlbar-search-mode-zen_actions = Actions
zen-site-data-settings = Settings zen-site-data-settings = Settings

View File

@@ -20,7 +20,7 @@ zen-toolbar-context-compact-mode-hide-both =
.accesskey = H .accesskey = H
zen-toolbar-context-move-to-folder = zen-toolbar-context-move-to-folder =
.label = Move to Folder... .label = Move to Folder
.accesskey = M .accesskey = M
zen-toolbar-context-new-folder = zen-toolbar-context-new-folder =
@@ -31,7 +31,7 @@ sidebar-zen-expand =
.label = Expand Sidebar .label = Expand Sidebar
sidebar-zen-create-new = sidebar-zen-create-new =
.label = Create New... .label = Create New
tabbrowser-unload-tab-button = tabbrowser-unload-tab-button =
.tooltiptext = .tooltiptext =

View File

@@ -24,10 +24,10 @@ zen-workspaces-panel-context-delete =
.accesskey = D .accesskey = D
zen-workspaces-panel-change-name = zen-workspaces-panel-change-name =
.label = Change Name .label = Change Name
zen-workspaces-panel-change-icon = zen-workspaces-panel-change-icon =
.label = Change Icon .label = Change Icon
zen-workspaces-panel-context-default-profile = zen-workspaces-panel-context-default-profile =
.label = Set Profile .label = Set Profile
@@ -42,7 +42,7 @@ 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 zen-workspaces-how-to-reorder-desc = Drag the space icons at the bottom of the sidebar to reorder them
zen-workspaces-change-theme = zen-workspaces-change-theme =
.label = Edit Theme .label = Edit Theme
zen-workspaces-panel-context-open = zen-workspaces-panel-context-open =
.label = Open Workspace .label = Open Workspace
@@ -72,7 +72,7 @@ zen-workspace-creation-name =
.placeholder = Space Name .placeholder = Space Name
zen-move-tab-to-workspace-button = zen-move-tab-to-workspace-button =
.label = Move To... .label = Move To
.tooltiptext = Move all tabs in this window to a Space .tooltiptext = Move all tabs in this window to a Space
zen-workspaces-panel-context-reorder = zen-workspaces-panel-context-reorder =

View File

@@ -23,3 +23,6 @@
- name: browser.ml.linkPreview.enabled - name: browser.ml.linkPreview.enabled
value: false value: false
- name: browser.preferences.aiControls
value: false

12
prefs/zen/sync.yaml Normal file
View File

@@ -0,0 +1,12 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
- name: services.sync.engine.spaces
value: true
condition: "@IS_TWILIGHT@"
- name: services.sync.engine.spaces
value: false
locked: true
condition: "!@IS_TWILIGHT@"

View File

@@ -32,9 +32,6 @@
- name: zen.workspaces.scroll-modifier-key - name: zen.workspaces.scroll-modifier-key
value: ctrl value: ctrl
- name: services.sync.engine.workspaces
value: false
- name: zen.workspaces.separate-essentials - name: zen.workspaces.separate-essentials
value: true value: true

View File

@@ -14,7 +14,7 @@
<hbox id="PanelUI-zen-emojis-picker-pages"> <hbox id="PanelUI-zen-emojis-picker-pages">
<vbox emojis="true"> <vbox emojis="true">
<hbox id="PanelUI-zen-emojis-picker-header"> <hbox id="PanelUI-zen-emojis-picker-header">
<html:input type="search" id="PanelUI-zen-emojis-picker-search" placeholder="Search emojis" /> <html:input type="search" id="PanelUI-zen-emojis-picker-search" data-l10n-id="zen-emojis-picker-search" />
</hbox> </hbox>
<hbox id="PanelUI-zen-emojis-picker-list" /> <hbox id="PanelUI-zen-emojis-picker-list" />
</vbox> </vbox>

View File

@@ -0,0 +1,20 @@
diff --git a/browser/components/preferences/config/account-sync.mjs b/browser/components/preferences/config/account-sync.mjs
index b503987a08e2ce64bfc01c4e3a21e5e19ef834f0..b1c03a0b219fd3eddd93c1deaeb18ab79c85f168 100644
--- a/browser/components/preferences/config/account-sync.mjs
+++ b/browser/components/preferences/config/account-sync.mjs
@@ -42,6 +42,7 @@ Preferences.addAll([
{ id: "services.sync.engine.creditcards", type: "bool" },
{ id: "services.sync.engine.addons", type: "bool" },
{ id: "services.sync.engine.prefs", type: "bool" },
+ { id: "services.sync.engine.spaces", type: "bool" },
]);
/**
@@ -546,6 +547,7 @@ const SYNC_ENGINE_SETTINGS = [
type: "payments",
},
{ id: "syncAddons", pref: "services.sync.engine.addons", type: "addons" },
+ { id: "syncSpaces", pref: "services.sync.engine.spaces", type: "workspaces" },
{ id: "syncSettings", pref: "services.sync.engine.prefs", type: "settings" },
];

View File

@@ -6,7 +6,7 @@ index 64aa0d98a0622c01f3dcfff1a04bfcda368354d2..2013e04b0881ad2295d6897b91e1573c
{ id: "services.sync.engine.passwords", type: "bool" }, { id: "services.sync.engine.passwords", type: "bool" },
{ id: "services.sync.engine.addresses", type: "bool" }, { id: "services.sync.engine.addresses", type: "bool" },
{ id: "services.sync.engine.creditcards", type: "bool" }, { id: "services.sync.engine.creditcards", type: "bool" },
+ { id: "services.sync.engine.workspaces", type: "bool" }, + { id: "services.sync.engine.spaces", type: "bool" },
]); ]);
let gSyncChooseWhatToSync = { let gSyncChooseWhatToSync = {

View File

@@ -20,7 +20,7 @@ index a893c5ec3d007820d98f5d92dd039640faa2c181..9cbd00102e44ccf98b37845474d92d57
+ <html:div class="sync-engine-workspaces"> + <html:div class="sync-engine-workspaces">
+ <checkbox + <checkbox
+ data-l10n-id="sync-engine-workspaces" + data-l10n-id="sync-engine-workspaces"
+ preference="services.sync.engine.workspaces" + preference="services.sync.engine.spaces"
+ /> + />
+ </html:div> + </html:div>
</html:div> </html:div>

View File

@@ -10,7 +10,7 @@ index 57add34d876fb885275f1147209c6fbeee367a7c..be0ab43b299317c0022a5e719f47a070
], ],
resizeCallback: async ({ title, frame }) => { resizeCallback: async ({ title, frame }) => {
// Search within main document and highlight matched keyword. // Search within main document and highlight matched keyword.
@@ -437,6 +438,8 @@ const CONFIG_PANES = Object.freeze({ @@ -437,6 +437,8 @@ const CONFIG_PANES = Object.freeze({
tabsBrowsing: { tabsBrowsing: {
l10nId: "tabs-browsing-section", l10nId: "tabs-browsing-section",
groupIds: [ groupIds: [

View File

@@ -6,7 +6,7 @@ index c379e1a5f82692406a92d9fcd3bca2769dfac5b2..af037dd3d995813d966524ac44a3795d
<image class="sync-engine-image sync-engine-prefs" alt=""/> <image class="sync-engine-image sync-engine-prefs" alt=""/>
<label data-l10n-id="sync-currently-syncing-settings"/> <label data-l10n-id="sync-currently-syncing-settings"/>
</html:div> </html:div>
+ <html:div engine_preference="services.sync.engine.workspaces"> + <html:div engine_preference="services.sync.engine.spaces">
+ <image class="sync-engine-image sync-engine-workspaces" alt=""/> + <image class="sync-engine-image sync-engine-workspaces" alt=""/>
+ <label data-l10n-id="sync-currently-syncing-workspaces"/> + <label data-l10n-id="sync-currently-syncing-workspaces"/>
+ </html:div> + </html:div>

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d83330c265dfc 100644 index 08b5b56e069d038d72c87355920c4ce8a55ed805..0e4f57c21d28ccf173d8d1e13e96c2c1f8a79f34 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js --- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js +++ b/browser/components/tabbrowser/content/tabbrowser.js
@@ -511,6 +511,7 @@ @@ -511,6 +511,7 @@
@@ -109,7 +109,13 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
tab.linkedPanel = uniqueId; tab.linkedPanel = uniqueId;
this._selectedTab = tab; this._selectedTab = tab;
this._selectedBrowser = browser; this._selectedBrowser = browser;
@@ -1131,13 +1192,18 @@ @@ -1126,18 +1187,24 @@
aTab,
{ telemetrySource = this.TabMetrics.METRIC_SOURCE.UNKNOWN } = {}
) {
+ aTab = gZenGlanceManager.getTabOrGlanceParent(aTab);
if (aTab.pinned || aTab == FirefoxViewHandler.tab) {
return;
} }
this.showTab(aTab); this.showTab(aTab);
@@ -129,7 +135,13 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab.setAttribute("pinned", "true"); aTab.setAttribute("pinned", "true");
this._updateTabBarForPinnedTabs(); this._updateTabBarForPinnedTabs();
@@ -1150,11 +1216,19 @@ @@ -1145,16 +1212,25 @@
}
unpinTab(aTab) {
+ aTab = gZenGlanceManager.getTabOrGlanceParent(aTab);
if (!aTab.pinned) {
return;
} }
this.#handleTabMove(aTab, () => { this.#handleTabMove(aTab, () => {
@@ -150,7 +162,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}); });
aTab.style.marginInlineStart = ""; aTab.style.marginInlineStart = "";
@@ -1369,6 +1443,9 @@ @@ -1369,6 +1445,9 @@
let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"]; let LOCAL_PROTOCOLS = ["chrome:", "about:", "resource:", "data:"];
@@ -160,7 +172,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if ( if (
aIconURL && aIconURL &&
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol)) !LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
@@ -1378,6 +1455,9 @@ @@ -1378,6 +1457,9 @@
); );
return; return;
} }
@@ -170,7 +182,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let browser = this.getBrowserForTab(aTab); let browser = this.getBrowserForTab(aTab);
browser.mIconURL = aIconURL; browser.mIconURL = aIconURL;
@@ -1700,7 +1780,6 @@ @@ -1700,7 +1782,6 @@
// Preview mode should not reset the owner // Preview mode should not reset the owner
if (!this._previewMode && !oldTab.selected) { if (!this._previewMode && !oldTab.selected) {
@@ -178,7 +190,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
let lastRelatedTab = this._lastRelatedTabMap.get(oldTab); let lastRelatedTab = this._lastRelatedTabMap.get(oldTab);
@@ -1791,6 +1870,7 @@ @@ -1791,6 +1872,7 @@
if (!this._previewMode) { if (!this._previewMode) {
newTab.recordTimeFromUnloadToReload(); newTab.recordTimeFromUnloadToReload();
newTab.updateLastAccessed(); newTab.updateLastAccessed();
@@ -186,7 +198,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
oldTab.updateLastAccessed(); oldTab.updateLastAccessed();
// if this is the foreground window, update the last-seen timestamps. // if this is the foreground window, update the last-seen timestamps.
if (this.documentGlobal == BrowserWindowTracker.getTopWindow()) { if (this.documentGlobal == BrowserWindowTracker.getTopWindow()) {
@@ -2005,6 +2085,9 @@ @@ -2005,6 +2087,9 @@
} }
let activeEl = document.activeElement; let activeEl = document.activeElement;
@@ -196,7 +208,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// If focus is on the old tab, move it to the new tab. // If focus is on the old tab, move it to the new tab.
if (activeEl == oldTab) { if (activeEl == oldTab) {
newTab.focus(); newTab.focus();
@@ -2043,7 +2126,7 @@ @@ -2043,7 +2128,7 @@
// Focus the location bar if it was previously focused for that tab. // Focus the location bar if it was previously focused for that tab.
// In full screen mode, only bother making the location bar visible // In full screen mode, only bother making the location bar visible
// if the tab is a blank one. // if the tab is a blank one.
@@ -205,7 +217,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let selectURL = () => { let selectURL = () => {
if (this._asyncTabSwitching) { if (this._asyncTabSwitching) {
// Set _awaitingSetURI flag to suppress popup notification // Set _awaitingSetURI flag to suppress popup notification
@@ -2331,7 +2414,12 @@ @@ -2331,7 +2416,12 @@
return this._setTabLabel(aTab, aLabel); return this._setTabLabel(aTab, aLabel);
} }
@@ -219,7 +231,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (!aLabel || (isURL && /^about:reader\?url=/.test(aLabel))) { if (!aLabel || (isURL && /^about:reader\?url=/.test(aLabel))) {
return false; return false;
} }
@@ -2457,7 +2545,7 @@ @@ -2457,7 +2547,7 @@
newIndex = this.selectedTab._tPos + 1; newIndex = this.selectedTab._tPos + 1;
} }
@@ -228,7 +240,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (this.isTabGroupLabel(targetTab)) { if (this.isTabGroupLabel(targetTab)) {
throw new Error( throw new Error(
"Replacing a tab group label with a tab is not supported" "Replacing a tab group label with a tab is not supported"
@@ -2737,6 +2825,7 @@ @@ -2737,6 +2827,7 @@
uriIsAboutBlank, uriIsAboutBlank,
userContextId, userContextId,
skipLoad, skipLoad,
@@ -236,7 +248,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} = {}) { } = {}) {
let b = document.createXULElement("browser"); let b = document.createXULElement("browser");
// Use the JSM global to create the permanentKey, so that if the // Use the JSM global to create the permanentKey, so that if the
@@ -2810,8 +2899,7 @@ @@ -2810,8 +2901,7 @@
// we use a different attribute name for this? // we use a different attribute name for this?
b.setAttribute("name", name); b.setAttribute("name", name);
} }
@@ -246,7 +258,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
b.setAttribute("transparent", "true"); b.setAttribute("transparent", "true");
} }
@@ -2981,7 +3069,7 @@ @@ -2981,7 +3071,7 @@
let panel = this.getPanel(browser); let panel = this.getPanel(browser);
let uniqueId = this._generateUniquePanelID(); let uniqueId = this._generateUniquePanelID();
@@ -255,7 +267,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab.linkedPanel = uniqueId; aTab.linkedPanel = uniqueId;
// Inject the <browser> into the DOM if necessary. // Inject the <browser> into the DOM if necessary.
@@ -3041,8 +3129,8 @@ @@ -3041,8 +3131,8 @@
// If we transitioned from one browser to two browsers, we need to set // If we transitioned from one browser to two browsers, we need to set
// hasSiblings=false on both the existing browser and the new browser. // hasSiblings=false on both the existing browser and the new browser.
if (this.tabs.length == 2) { if (this.tabs.length == 2) {
@@ -266,7 +278,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else { } else {
aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1; aTab.linkedBrowser.browsingContext.hasSiblings = this.tabs.length > 1;
} }
@@ -3227,7 +3315,6 @@ @@ -3227,7 +3317,6 @@
this.selectedTab = this.addTrustedTab(BROWSER_NEW_TAB_URL, { this.selectedTab = this.addTrustedTab(BROWSER_NEW_TAB_URL, {
tabIndex: tab._tPos + 1, tabIndex: tab._tPos + 1,
userContextId: tab.userContextId, userContextId: tab.userContextId,
@@ -274,7 +286,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
focusUrlBar: true, focusUrlBar: true,
}); });
resolve(this.selectedBrowser); resolve(this.selectedBrowser);
@@ -3337,6 +3424,10 @@ @@ -3337,6 +3426,10 @@
schemelessInput, schemelessInput,
hasValidUserGestureActivation = false, hasValidUserGestureActivation = false,
textDirectiveUserActivation = false, textDirectiveUserActivation = false,
@@ -285,7 +297,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} = {} } = {}
) { ) {
// all callers of addTab that pass a params object need to pass // all callers of addTab that pass a params object need to pass
@@ -3347,10 +3438,25 @@ @@ -3347,10 +3440,25 @@
); );
} }
@@ -296,7 +308,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
+ +
+ let hasZenDefaultUserContextId = false; + let hasZenDefaultUserContextId = false;
+ let zenForcedWorkspaceId = undefined; + let zenForcedWorkspaceId = undefined;
+ if (beforeRouteResult.isRouteFound && typeof userContextId !== "undefined") { + if (beforeRouteResult.isRouteFound && typeof userContextId === "undefined") {
+ userContextId = beforeRouteResult.userContextId; + userContextId = beforeRouteResult.userContextId;
+ hasZenDefaultUserContextId = true; + hasZenDefaultUserContextId = true;
+ } else if (typeof gZenWorkspaces !== "undefined" && !_forZenEmptyTab) { + } else if (typeof gZenWorkspaces !== "undefined" && !_forZenEmptyTab) {
@@ -311,7 +323,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// If we're opening a foreground tab, set the owner by default. // If we're opening a foreground tab, set the owner by default.
ownerTab ??= inBackground ? null : this.selectedTab; ownerTab ??= inBackground ? null : this.selectedTab;
@@ -3358,6 +3464,7 @@ @@ -3358,6 +3466,7 @@
if (this.selectedTab.owner) { if (this.selectedTab.owner) {
this.selectedTab.owner = null; this.selectedTab.owner = null;
} }
@@ -319,7 +331,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Find the tab that opened this one, if any. This is used for // Find the tab that opened this one, if any. This is used for
// determining positioning, and inherited attributes such as the // determining positioning, and inherited attributes such as the
@@ -3410,6 +3517,22 @@ @@ -3410,6 +3519,22 @@
noInitialLabel, noInitialLabel,
skipBackgroundNotify, skipBackgroundNotify,
}); });
@@ -342,7 +354,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (insertTab) { if (insertTab) {
// Insert the tab into the tab container in the correct position. // Insert the tab into the tab container in the correct position.
this.#insertTabAtIndex(t, { this.#insertTabAtIndex(t, {
@@ -3418,6 +3541,7 @@ @@ -3418,6 +3543,7 @@
ownerTab, ownerTab,
openerTab, openerTab,
pinned, pinned,
@@ -350,7 +362,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
bulkOrderedOpen, bulkOrderedOpen,
tabGroup: tabGroup ?? openerTab?.group, tabGroup: tabGroup ?? openerTab?.group,
}); });
@@ -3436,6 +3560,7 @@ @@ -3436,6 +3562,7 @@
openWindowInfo, openWindowInfo,
skipLoad, skipLoad,
triggeringRemoteType, triggeringRemoteType,
@@ -358,7 +370,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
})); }));
if (focusUrlBar) { if (focusUrlBar) {
@@ -3560,6 +3685,12 @@ @@ -3560,6 +3687,12 @@
} }
} }
@@ -371,7 +383,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Additionally send pinned tab events // Additionally send pinned tab events
if (pinned) { if (pinned) {
this.#notifyPinnedStatus(t); this.#notifyPinnedStatus(t);
@@ -3570,6 +3701,15 @@ @@ -3570,6 +3703,15 @@
if (!inBackground) { if (!inBackground) {
this.selectedTab = t; this.selectedTab = t;
} }
@@ -387,7 +399,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
return t; return t;
} }
@@ -3802,6 +3942,7 @@ @@ -3802,6 +3944,7 @@
isAdoptingGroup = false, isAdoptingGroup = false,
isUserTriggered = false, isUserTriggered = false,
telemetryUserCreateSource = "unknown", telemetryUserCreateSource = "unknown",
@@ -395,7 +407,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} = {} } = {}
) { ) {
if ( if (
@@ -3812,9 +3953,6 @@ @@ -3812,9 +3955,6 @@
!this.isSplitViewWrapper(tabOrSplitView) !this.isSplitViewWrapper(tabOrSplitView)
) )
) { ) {
@@ -405,7 +417,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
if (!color) { if (!color) {
@@ -3835,9 +3973,14 @@ @@ -3835,9 +3975,14 @@
label, label,
isAdoptingGroup isAdoptingGroup
); );
@@ -422,7 +434,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
); );
group.addTabs(tabsAndSplitViews); group.addTabs(tabsAndSplitViews);
@@ -3958,7 +4101,7 @@ @@ -3958,7 +4103,7 @@
} }
this.#handleTabMove(tab, () => this.#handleTabMove(tab, () =>
@@ -431,7 +443,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
); );
} }
@@ -4044,6 +4187,7 @@ @@ -4044,6 +4189,7 @@
color: group.color, color: group.color,
insertBefore: newTabs[0], insertBefore: newTabs[0],
isAdoptingGroup: true, isAdoptingGroup: true,
@@ -439,7 +451,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}); });
} }
@@ -4254,6 +4398,7 @@ @@ -4254,6 +4400,7 @@
openWindowInfo, openWindowInfo,
skipLoad, skipLoad,
triggeringRemoteType, triggeringRemoteType,
@@ -447,7 +459,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
) { ) {
// If we don't have a preferred remote type (or it is `NOT_REMOTE`), and // If we don't have a preferred remote type (or it is `NOT_REMOTE`), and
@@ -4323,6 +4468,7 @@ @@ -4323,6 +4470,7 @@
openWindowInfo, openWindowInfo,
name, name,
skipLoad, skipLoad,
@@ -455,7 +467,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}); });
} }
@@ -4536,9 +4682,9 @@ @@ -4536,9 +4684,9 @@
} }
// Add a new tab if needed. // Add a new tab if needed.
@@ -467,7 +479,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let url = "about:blank"; let url = "about:blank";
if (tabData.entries?.length) { if (tabData.entries?.length) {
@@ -4575,8 +4721,10 @@ @@ -4575,8 +4723,10 @@
insertTab: false, insertTab: false,
skipLoad: true, skipLoad: true,
preferredRemoteType, preferredRemoteType,
@@ -479,7 +491,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (select) { if (select) {
tabToSelect = tab; tabToSelect = tab;
} }
@@ -4598,7 +4746,8 @@ @@ -4598,7 +4748,8 @@
this.pinTab(tab); this.pinTab(tab);
// Then ensure all the tab open/pinning information is sent. // Then ensure all the tab open/pinning information is sent.
this._fireTabOpen(tab, {}); this._fireTabOpen(tab, {});
@@ -489,7 +501,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let { groupId } = tabData; let { groupId } = tabData;
const tabGroup = tabGroupWorkingData.get(groupId); const tabGroup = tabGroupWorkingData.get(groupId);
// if a tab refers to a tab group we don't know, skip any group // if a tab refers to a tab group we don't know, skip any group
@@ -4618,7 +4767,10 @@ @@ -4618,7 +4769,10 @@
tabGroup.stateData.id, tabGroup.stateData.id,
tabGroup.stateData.color, tabGroup.stateData.color,
tabGroup.stateData.collapsed, tabGroup.stateData.collapsed,
@@ -501,7 +513,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
); );
tabsFragment.appendChild(tabGroup.node); tabsFragment.appendChild(tabGroup.node);
} }
@@ -4673,9 +4825,21 @@ @@ -4673,9 +4827,21 @@
// to remove the old selected tab. // to remove the old selected tab.
if (tabToSelect) { if (tabToSelect) {
let leftoverTab = this.selectedTab; let leftoverTab = this.selectedTab;
@@ -523,7 +535,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (tabs.length > 1 || !tabs[0].selected) { if (tabs.length > 1 || !tabs[0].selected) {
this._updateTabsAfterInsert(); this._updateTabsAfterInsert();
@@ -4866,11 +5030,17 @@ @@ -4866,11 +5032,17 @@
if (ownerTab) { if (ownerTab) {
tab.owner = ownerTab; tab.owner = ownerTab;
} }
@@ -542,7 +554,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if ( if (
!bulkOrderedOpen && !bulkOrderedOpen &&
((openerTab && ((openerTab &&
@@ -4882,7 +5052,7 @@ @@ -4882,7 +5054,7 @@
let lastRelatedTab = let lastRelatedTab =
openerTab && this._lastRelatedTabMap.get(openerTab); openerTab && this._lastRelatedTabMap.get(openerTab);
let previousTab = lastRelatedTab || openerTab || this.selectedTab; let previousTab = lastRelatedTab || openerTab || this.selectedTab;
@@ -551,7 +563,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
tabGroup = previousTab.group; tabGroup = previousTab.group;
} }
if ( if (
@@ -4898,7 +5068,7 @@ @@ -4898,7 +5070,7 @@
previousTab.splitview previousTab.splitview
) + 1; ) + 1;
} else if (previousTab.visible) { } else if (previousTab.visible) {
@@ -560,7 +572,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else if (previousTab == FirefoxViewHandler.tab) { } else if (previousTab == FirefoxViewHandler.tab) {
elementIndex = 0; elementIndex = 0;
} }
@@ -4926,14 +5096,14 @@ @@ -4926,14 +5098,14 @@
} }
// Ensure index is within bounds. // Ensure index is within bounds.
if (tab.pinned) { if (tab.pinned) {
@@ -579,7 +591,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (pinned && !itemAfter?.pinned) { if (pinned && !itemAfter?.pinned) {
itemAfter = null; itemAfter = null;
@@ -4950,7 +5120,7 @@ @@ -4950,7 +5122,7 @@
this.tabContainer._invalidateCachedTabs(); this.tabContainer._invalidateCachedTabs();
@@ -588,7 +600,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if ( if (
(this.isTab(itemAfter) && itemAfter.group == tabGroup) || (this.isTab(itemAfter) && itemAfter.group == tabGroup) ||
this.isSplitViewWrapper(itemAfter) this.isSplitViewWrapper(itemAfter)
@@ -4981,7 +5151,11 @@ @@ -4981,7 +5153,11 @@
const tabContainer = pinned const tabContainer = pinned
? this.tabContainer.pinnedTabsContainer ? this.tabContainer.pinnedTabsContainer
: this.tabContainer; : this.tabContainer;
@@ -600,7 +612,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
if (tab.group?.collapsed) { if (tab.group?.collapsed) {
@@ -4996,6 +5170,7 @@ @@ -4996,6 +5172,7 @@
if (pinned) { if (pinned) {
this._updateTabBarForPinnedTabs(); this._updateTabBarForPinnedTabs();
} }
@@ -608,7 +620,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
TabBarVisibility.update(); TabBarVisibility.update();
} }
@@ -5544,6 +5719,7 @@ @@ -5544,6 +5721,7 @@
telemetrySource, telemetrySource,
} = {} } = {}
) { ) {
@@ -616,7 +628,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// When 'closeWindowWithLastTab' pref is enabled, closing all tabs // When 'closeWindowWithLastTab' pref is enabled, closing all tabs
// can be considered equivalent to closing the window. // can be considered equivalent to closing the window.
if ( if (
@@ -5633,6 +5809,7 @@ @@ -5633,6 +5811,7 @@
if (lastToClose) { if (lastToClose) {
this.removeTab(lastToClose, aParams); this.removeTab(lastToClose, aParams);
} }
@@ -624,7 +636,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@@ -5678,6 +5855,14 @@ @@ -5678,6 +5857,14 @@
return; return;
} }
@@ -639,7 +651,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let isVisibleTab = aTab.visible; let isVisibleTab = aTab.visible;
// We have to sample the tab width now, since _beginRemoveTab might // We have to sample the tab width now, since _beginRemoveTab might
// end up modifying the DOM in such a way that aTab gets a new // end up modifying the DOM in such a way that aTab gets a new
@@ -5685,6 +5870,9 @@ @@ -5685,6 +5872,9 @@
// state). // state).
let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width; let tabWidth = window.windowUtils.getBoundsWithoutFlushing(aTab).width;
let isLastTab = this.#isLastTabInWindow(aTab); let isLastTab = this.#isLastTabInWindow(aTab);
@@ -649,7 +661,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if ( if (
!this._beginRemoveTab(aTab, { !this._beginRemoveTab(aTab, {
closeWindowFastpath: true, closeWindowFastpath: true,
@@ -5696,13 +5884,14 @@ @@ -5696,13 +5886,14 @@
telemetrySource, telemetrySource,
}) })
) { ) {
@@ -665,7 +677,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let lockTabSizing = let lockTabSizing =
!this.tabContainer.verticalMode && !this.tabContainer.verticalMode &&
!aTab.pinned && !aTab.pinned &&
@@ -5733,7 +5922,13 @@ @@ -5733,7 +5924,13 @@
// We're not animating, so we can cancel the animation stopwatch. // We're not animating, so we can cancel the animation stopwatch.
Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId); Glean.browserTabclose.timeAnim.cancel(aTab._closeTimeAnimTimerId);
aTab._closeTimeAnimTimerId = null; aTab._closeTimeAnimTimerId = null;
@@ -680,7 +692,16 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
return; return;
} }
@@ -5867,7 +6062,7 @@ @@ -5776,7 +5973,7 @@
*/
#isLastTabInWindow(tab) {
for (const otherTab of this.tabs) {
- if (otherTab != tab && otherTab.isOpen && !otherTab.hidden) {
+ if (otherTab != tab && otherTab.isOpen && !otherTab.hidden && !otherTab.hasAttribute("zen-empty-tab")) {
return false;
}
}
@@ -5867,7 +6064,7 @@
closeWindowWithLastTab != null closeWindowWithLastTab != null
? closeWindowWithLastTab ? closeWindowWithLastTab
: !window.toolbar.visible || : !window.toolbar.visible ||
@@ -689,7 +710,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (closeWindow) { if (closeWindow) {
// We've already called beforeunload on all the relevant tabs if we get here, // We've already called beforeunload on all the relevant tabs if we get here,
@@ -5891,6 +6086,7 @@ @@ -5891,6 +6088,7 @@
newTab = true; newTab = true;
} }
@@ -697,7 +718,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab._endRemoveArgs = [closeWindow, newTab]; aTab._endRemoveArgs = [closeWindow, newTab];
// swapBrowsersAndCloseOther will take care of closing the window without animation. // swapBrowsersAndCloseOther will take care of closing the window without animation.
@@ -5931,13 +6127,7 @@ @@ -5931,13 +6129,7 @@
aTab._mouseleave(); aTab._mouseleave();
if (newTab) { if (newTab) {
@@ -712,7 +733,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else { } else {
TabBarVisibility.update(); TabBarVisibility.update();
} }
@@ -6070,6 +6260,7 @@ @@ -6070,6 +6262,7 @@
this.tabs[i]._tPos = i; this.tabs[i]._tPos = i;
} }
@@ -720,7 +741,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (!this._windowIsClosing) { if (!this._windowIsClosing) {
// update tab close buttons state // update tab close buttons state
this.tabContainer._updateCloseButtons(); this.tabContainer._updateCloseButtons();
@@ -6255,6 +6446,7 @@ @@ -6255,6 +6448,7 @@
memory_after: await getTotalMemoryUsage(), memory_after: await getTotalMemoryUsage(),
time_to_unload_in_ms: timeElapsed, time_to_unload_in_ms: timeElapsed,
}); });
@@ -728,7 +749,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
/** /**
@@ -6300,6 +6492,7 @@ @@ -6300,6 +6494,7 @@
} }
let excludeTabs = new Set(aExcludeTabs); let excludeTabs = new Set(aExcludeTabs);
@@ -736,7 +757,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// If this tab has a successor, it should be selectable, since // If this tab has a successor, it should be selectable, since
// hiding or closing a tab removes that tab as a successor. // hiding or closing a tab removes that tab as a successor.
@@ -6312,15 +6505,22 @@ @@ -6312,15 +6507,22 @@
!excludeTabs.has(aTab.owner) && !excludeTabs.has(aTab.owner) &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose") Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")
) { ) {
@@ -761,7 +782,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let tab = this.tabContainer.findNextTab(aTab, { let tab = this.tabContainer.findNextTab(aTab, {
direction: 1, direction: 1,
filter: _tab => remainingTabs.includes(_tab), filter: _tab => remainingTabs.includes(_tab),
@@ -6334,7 +6534,7 @@ @@ -6334,7 +6536,7 @@
} }
if (tab) { if (tab) {
@@ -770,7 +791,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
// If no qualifying visible tab was found, see if there is a tab in // If no qualifying visible tab was found, see if there is a tab in
@@ -6355,7 +6555,7 @@ @@ -6355,7 +6557,7 @@
}); });
} }
@@ -779,7 +800,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
_blurTab(aTab) { _blurTab(aTab) {
@@ -6366,7 +6566,7 @@ @@ -6366,7 +6568,7 @@
* @returns {boolean} * @returns {boolean}
* False if swapping isn't permitted, true otherwise. * False if swapping isn't permitted, true otherwise.
*/ */
@@ -788,7 +809,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Do not allow transfering a private tab to a non-private window // Do not allow transfering a private tab to a non-private window
// and vice versa. // and vice versa.
if ( if (
@@ -6420,6 +6620,7 @@ @@ -6420,6 +6622,7 @@
// fire the beforeunload event in the process. Close the other // fire the beforeunload event in the process. Close the other
// window if this was its last tab. // window if this was its last tab.
if ( if (
@@ -796,7 +817,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
!remoteBrowser._beginRemoveTab(aOtherTab, { !remoteBrowser._beginRemoveTab(aOtherTab, {
adoptedByTab: aOurTab, adoptedByTab: aOurTab,
closeWindowWithLastTab: true, closeWindowWithLastTab: true,
@@ -6431,7 +6632,7 @@ @@ -6431,7 +6634,7 @@
// If this is the last tab of the window, hide the window // If this is the last tab of the window, hide the window
// immediately without animation before the docshell swap, to avoid // immediately without animation before the docshell swap, to avoid
// about:blank being painted. // about:blank being painted.
@@ -805,7 +826,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (closeWindow) { if (closeWindow) {
let win = aOtherTab.documentGlobal; let win = aOtherTab.documentGlobal;
win.windowUtils.suppressAnimation(true); win.windowUtils.suppressAnimation(true);
@@ -6565,11 +6766,13 @@ @@ -6565,11 +6768,13 @@
} }
// Finish tearing down the tab that's going away. // Finish tearing down the tab that's going away.
@@ -819,7 +840,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
this.setTabTitle(aOurTab); this.setTabTitle(aOurTab);
@@ -6771,10 +6974,10 @@ @@ -6771,10 +6976,10 @@
SessionStore.deleteCustomTabValue(aTab, "hiddenBy"); SessionStore.deleteCustomTabValue(aTab, "hiddenBy");
} }
@@ -832,7 +853,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab.selected || aTab.selected ||
aTab.closing || aTab.closing ||
// Tabs that are sharing the screen, microphone or camera cannot be hidden. // Tabs that are sharing the screen, microphone or camera cannot be hidden.
@@ -6834,7 +7037,8 @@ @@ -6834,7 +7039,8 @@
* @param {object} [aOptions={}] * @param {object} [aOptions={}]
* Key-value pairs that will be serialized into the features string. * Key-value pairs that will be serialized into the features string.
*/ */
@@ -842,7 +863,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (this.tabs.length == 1) { if (this.tabs.length == 1) {
return null; return null;
} }
@@ -6851,7 +7055,7 @@ @@ -6851,7 +7057,7 @@
// tell a new window to take the "dropped" tab // tell a new window to take the "dropped" tab
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
args.appendElement(aTab.splitview ?? aTab); args.appendElement(aTab.splitview ?? aTab);
@@ -851,7 +872,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
private: PrivateBrowsingUtils.isWindowPrivate(window), private: PrivateBrowsingUtils.isWindowPrivate(window),
features: Object.entries(aOptions) features: Object.entries(aOptions)
.map(([key, value]) => `${key}=${value}`) .map(([key, value]) => `${key}=${value}`)
@@ -6859,6 +7063,8 @@ @@ -6859,6 +7065,8 @@
openerWindow: window, openerWindow: window,
args, args,
}); });
@@ -860,7 +881,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
/** /**
@@ -6971,7 +7177,7 @@ @@ -6971,7 +7179,7 @@
* `true` if element is a `<tab-group>` * `true` if element is a `<tab-group>`
*/ */
isTabGroup(element) { isTabGroup(element) {
@@ -869,7 +890,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
/** /**
@@ -7056,8 +7262,8 @@ @@ -7056,8 +7264,8 @@
} }
// Don't allow mixing pinned and unpinned tabs. // Don't allow mixing pinned and unpinned tabs.
@@ -880,7 +901,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else { } else {
tabIndex = Math.max(tabIndex, this.pinnedTabCount); tabIndex = Math.max(tabIndex, this.pinnedTabCount);
} }
@@ -7103,8 +7309,8 @@ @@ -7103,8 +7311,8 @@
this.#handleTabMove( this.#handleTabMove(
element, element,
() => { () => {
@@ -891,7 +912,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
neighbor = neighbor.group; neighbor = neighbor.group;
} }
if (neighbor?.splitview) { if (neighbor?.splitview) {
@@ -7115,6 +7321,12 @@ @@ -7115,6 +7323,12 @@
return; return;
} }
} }
@@ -904,7 +925,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
if (movingForwards && neighbor) { if (movingForwards && neighbor) {
neighbor.after(element); neighbor.after(element);
@@ -7173,23 +7385,31 @@ @@ -7173,23 +7387,31 @@
#moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) { #moveTabNextTo(element, targetElement, moveBefore = false, metricsContext) {
if (this.isTabGroupLabel(targetElement)) { if (this.isTabGroupLabel(targetElement)) {
targetElement = targetElement.group; targetElement = targetElement.group;
@@ -942,7 +963,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} else if (!element.pinned && targetElement && targetElement.pinned) { } else if (!element.pinned && targetElement && targetElement.pinned) {
// If the caller asks to move an unpinned element next to a pinned // If the caller asks to move an unpinned element next to a pinned
// tab, move the unpinned element to be the first unpinned element // tab, move the unpinned element to be the first unpinned element
@@ -7202,12 +7422,35 @@ @@ -7202,12 +7424,35 @@
// move the tab group right before the first unpinned tab. // move the tab group right before the first unpinned tab.
// 4. Moving a tab group and the first unpinned tab is grouped: // 4. Moving a tab group and the first unpinned tab is grouped:
// move the tab group right before the first unpinned tab's tab group. // move the tab group right before the first unpinned tab's tab group.
@@ -979,7 +1000,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// We want to include the splitview wrapper if it's the targetElement, but // We want to include the splitview wrapper if it's the targetElement, but
// not in the case where we want to reverse tabs within the same splitview. // not in the case where we want to reverse tabs within the same splitview.
@@ -7216,6 +7459,7 @@ @@ -7216,6 +7461,7 @@
} }
let getContainer = () => let getContainer = () =>
@@ -987,7 +1008,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
element.pinned element.pinned
? this.tabContainer.pinnedTabsContainer ? this.tabContainer.pinnedTabsContainer
: this.tabContainer; : this.tabContainer;
@@ -7224,11 +7468,15 @@ @@ -7224,11 +7470,15 @@
element, element,
() => { () => {
if (moveBefore) { if (moveBefore) {
@@ -1004,7 +1025,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
}, },
metricsContext metricsContext
@@ -7302,11 +7550,15 @@ @@ -7302,11 +7552,15 @@
* @param {TabMetricsContext} [metricsContext] * @param {TabMetricsContext} [metricsContext]
*/ */
moveTabToExistingGroup(aTab, aGroup, metricsContext) { moveTabToExistingGroup(aTab, aGroup, metricsContext) {
@@ -1023,7 +1044,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
if (aTab.group && aTab.group.id === aGroup.id) { if (aTab.group && aTab.group.id === aGroup.id) {
return; return;
@@ -7378,6 +7630,7 @@ @@ -7378,6 +7632,7 @@
let state = { let state = {
tabIndex: tab._tPos, tabIndex: tab._tPos,
@@ -1031,7 +1052,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}; };
if (tab.visible) { if (tab.visible) {
state.elementIndex = tab.elementIndex; state.elementIndex = tab.elementIndex;
@@ -7409,7 +7662,7 @@ @@ -7409,7 +7664,7 @@
let changedSplitView = let changedSplitView =
previousTabState.splitViewId != currentTabState.splitViewId; previousTabState.splitViewId != currentTabState.splitViewId;
@@ -1040,7 +1061,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
tab.dispatchEvent( tab.dispatchEvent(
new CustomEvent("TabMove", { new CustomEvent("TabMove", {
bubbles: true, bubbles: true,
@@ -7456,6 +7709,10 @@ @@ -7456,6 +7711,10 @@
moveActionCallback(); moveActionCallback();
@@ -1051,7 +1072,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Clear tabs cache after moving nodes because the order of tabs may have // Clear tabs cache after moving nodes because the order of tabs may have
// changed. // changed.
this.tabContainer._invalidateCachedTabs(); this.tabContainer._invalidateCachedTabs();
@@ -7506,7 +7763,22 @@ @@ -7506,7 +7765,22 @@
* @returns {object} * @returns {object}
* The new tab in the current window, null if the tab couldn't be adopted. * The new tab in the current window, null if the tab couldn't be adopted.
*/ */
@@ -1075,7 +1096,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
// Swap the dropped tab with a new one we create and then close // Swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between // it in the other window (making it seem to have moved between
// windows). We also ensure that the tab we create to swap into has // windows). We also ensure that the tab we create to swap into has
@@ -7549,6 +7821,8 @@ @@ -7549,6 +7823,8 @@
} }
params.skipLoad = true; params.skipLoad = true;
let newTab = this.addWebTab("about:blank", params); let newTab = this.addWebTab("about:blank", params);
@@ -1084,7 +1105,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
aTab.container.tabDragAndDrop.finishAnimateTabMove(); aTab.container.tabDragAndDrop.finishAnimateTabMove();
@@ -8259,7 +8533,7 @@ @@ -8259,7 +8535,7 @@
// preventDefault(). It will still raise the window if appropriate. // preventDefault(). It will still raise the window if appropriate.
return; return;
} }
@@ -1093,7 +1114,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
window.focus(); window.focus();
aEvent.preventDefault(); aEvent.preventDefault();
} }
@@ -8276,7 +8550,6 @@ @@ -8276,7 +8552,6 @@
on_TabGroupCollapse(aEvent) { on_TabGroupCollapse(aEvent) {
aEvent.target.tabs.forEach(tab => { aEvent.target.tabs.forEach(tab => {
@@ -1101,7 +1122,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
}); });
} }
@@ -8630,7 +8903,9 @@ @@ -8630,7 +8905,9 @@
let filter = this._tabFilters.get(tab); let filter = this._tabFilters.get(tab);
if (filter) { if (filter) {
@@ -1111,7 +1132,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
let listener = this._tabListeners.get(tab); let listener = this._tabListeners.get(tab);
if (listener) { if (listener) {
@@ -9435,6 +9710,7 @@ @@ -9435,6 +9712,7 @@
aWebProgress.isTopLevel aWebProgress.isTopLevel
) { ) {
this.mTab.setAttribute("busy", "true"); this.mTab.setAttribute("busy", "true");
@@ -1119,7 +1140,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
gBrowser._tabAttrModified(this.mTab, ["busy"]); gBrowser._tabAttrModified(this.mTab, ["busy"]);
this.mTab._notselectedsinceload = !this.mTab.selected; this.mTab._notselectedsinceload = !this.mTab.selected;
} }
@@ -9515,6 +9791,7 @@ @@ -9515,6 +9793,7 @@
// known defaults. Note we use the original URL since about:newtab // known defaults. Note we use the original URL since about:newtab
// redirects to a prerendered page. // redirects to a prerendered page.
const shouldRemoveFavicon = const shouldRemoveFavicon =
@@ -1127,7 +1148,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
!this.mBrowser.mIconURL && !this.mBrowser.mIconURL &&
!ignoreBlank && !ignoreBlank &&
!(originalLocation.spec in FAVICON_DEFAULTS); !(originalLocation.spec in FAVICON_DEFAULTS);
@@ -9689,13 +9966,6 @@ @@ -9689,13 +9968,6 @@
this.mBrowser.originalURI = aRequest.originalURI; this.mBrowser.originalURI = aRequest.originalURI;
} }
@@ -1141,7 +1162,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..87f41c8583c26f364530def5bd5d8333
} }
let userContextId = this.mBrowser.getAttribute("usercontextid") || 0; let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
@@ -10587,7 +10857,8 @@ var TabContextMenu = { @@ -10587,7 +10859,8 @@ var TabContextMenu = {
); );
contextUnpinSelectedTabs.hidden = contextUnpinSelectedTabs.hidden =
!this.contextTab.pinned || !this.multiselected; !this.contextTab.pinned || !this.multiselected;

View File

@@ -1,9 +1,9 @@
diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs
index 2f03bdf632c6ca15c728df2801162da5159682da..6834daede8cb11854f137602100183d85a930686 100644 index aa7c67f2bfaa3f15d592a14b527a8721dfc9bc6f..d2fe012796ea3a5be05090a732011b3cf92b78be 100644
--- a/browser/components/urlbar/UrlbarUtils.sys.mjs --- a/browser/components/urlbar/UrlbarUtils.sys.mjs
+++ b/browser/components/urlbar/UrlbarUtils.sys.mjs +++ b/browser/components/urlbar/UrlbarUtils.sys.mjs
@@ -109,6 +109,8 @@ export var UrlbarUtils = { @@ -110,6 +110,8 @@ export var UrlbarUtils = {
RESTRICT_SEARCH_KEYWORD: "restrictSearchKeyword", SEMANTIC_HISTORY: "semanticHistory",
SUGGESTED_INDEX: "suggestedIndex", SUGGESTED_INDEX: "suggestedIndex",
TAIL_SUGGESTION: "tailSuggestion", TAIL_SUGGESTION: "tailSuggestion",
+ ZEN_ACTION: "zenAction", + ZEN_ACTION: "zenAction",
@@ -11,7 +11,7 @@ index 2f03bdf632c6ca15c728df2801162da5159682da..6834daede8cb11854f137602100183d8
}), }),
// Defines provider types. // Defines provider types.
@@ -170,6 +172,8 @@ export var UrlbarUtils = { @@ -171,6 +173,8 @@ export var UrlbarUtils = {
OTHER_NETWORK: 6, OTHER_NETWORK: 6,
ADDON: 7, ADDON: 7,
ACTIONS: 8, ACTIONS: 8,
@@ -20,7 +20,7 @@ index 2f03bdf632c6ca15c728df2801162da5159682da..6834daede8cb11854f137602100183d8
}), }),
// Per-result exposure telemetry. // Per-result exposure telemetry.
@@ -319,6 +323,14 @@ export var UrlbarUtils = { @@ -320,6 +324,14 @@ export var UrlbarUtils = {
telemetryLabel: "actions", telemetryLabel: "actions",
uiLabel: "urlbar-searchmode-actions2", uiLabel: "urlbar-searchmode-actions2",
}, },
@@ -35,7 +35,7 @@ index 2f03bdf632c6ca15c728df2801162da5159682da..6834daede8cb11854f137602100183d8
]); ]);
}, },
@@ -611,6 +623,12 @@ export var UrlbarUtils = { @@ -612,6 +624,12 @@ export var UrlbarUtils = {
return this.RESULT_GROUP.HEURISTIC_FALLBACK; return this.RESULT_GROUP.HEURISTIC_FALLBACK;
case "UrlbarProviderHistoryUrlHeuristic": case "UrlbarProviderHistoryUrlHeuristic":
return this.RESULT_GROUP.HEURISTIC_HISTORY_URL; return this.RESULT_GROUP.HEURISTIC_HISTORY_URL;

View File

@@ -484,13 +484,10 @@ groupbox h2 {
#helpButton, #helpButton,
#support-firefox, #support-firefox,
#tabGroupSuggestions, #tabGroupSuggestions,
#web-appearance-manage-themes-link,
#setting-control-sidebarChatbotFieldset, #setting-control-sidebarChatbotFieldset,
#aiControlsDescription,
#category-ai-features,
#setting-control-supportFirefox, #setting-control-supportFirefox,
.mission-message, .mission-message,
html|setting-group:is([data-subcategory="layout"], [groupid="support"]) { html|setting-group:is([data-subcategory="layout"], [groupid="support"], [groupid="browserTheme"]) {
display: none !important; display: none !important;
} }

View File

@@ -0,0 +1,15 @@
diff --git a/services/sync/modules/service.sys.mjs b/services/sync/modules/service.sys.mjs
index 4f4efe572909441abdd81909282936702c225621..81ab06a9414ed656b644a21b40b683f06a4f8dbf 100644
--- a/services/sync/modules/service.sys.mjs
+++ b/services/sync/modules/service.sys.mjs
@@ -99,6 +99,10 @@ function getEngineModules() {
whenTrue: "ExtensionStorageEngineKinto",
whenFalse: "ExtensionStorageEngineBridge",
};
+ result.Spaces = {
+ module: "resource:///modules/zen/ZenWorkspacesSync.sys.mjs",
+ symbol: "ZenWorkspacesEngine",
+ };
return result;
}

View File

@@ -0,0 +1,37 @@
diff --git a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
index 5702ff28cc22206f5ce16584dac8a78d816562ce..3d08ecc97ce5995b30d8a4af0c33df329b428008 100644
--- a/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
+++ b/toolkit/components/contextualidentity/ContextualIdentityService.sys.mjs
@@ -270,11 +270,29 @@ _ContextualIdentityService.prototype = {
});
},
- create(name, icon, color) {
+ create(name, icon, color, id = null) {
this.ensureDataReady();
- // Retrieve the next userContextId available.
- let userContextId = ++this._lastUserContextId;
+ if (id !== null) {
+ id = typeof id == "string" ? Number(id) : id;
+ if (!Number.isSafeInteger(id) || id <= 0) {
+ throw new Error(`Invalid userContextId '${id}'`);
+ }
+ }
+
+ let userContextId;
+ if (
+ id !== null &&
+ !this._identities.some(identity => identity.userContextId === id)
+ ) {
+ userContextId = id;
+ this._lastUserContextId = Math.max(
+ this._lastUserContextId,
+ userContextId
+ );
+ } else {
+ userContextId = ++this._lastUserContextId;
+ }
// Throw an error if the next userContextId available is invalid (the one associated to
// MAX_USER_CONTEXT_ID is already reserved to "userContextIdInternal.webextStorageLocal", which

View File

@@ -1579,6 +1579,7 @@ window.gZenVerticalTabsManager = {
overflowElements.appendChild(child); overflowElements.appendChild(child);
} else { } else {
const element = document.getElementById("page-action-buttons"); const element = document.getElementById("page-action-buttons");
child.setAttribute("context", "toolbar-context-menu");
element.before(child); element.before(child);
} }
return; return;
@@ -1612,7 +1613,9 @@ window.gZenVerticalTabsManager = {
// it will reset to the original name anyway // it will reset to the original name anyway
if (hasChanged || (this._tabEdited.zenStaticLabel && newName)) { if (hasChanged || (this._tabEdited.zenStaticLabel && newName)) {
this._tabEdited.zenStaticLabel = newName; this._tabEdited.zenStaticLabel = newName;
gBrowser._setTabLabel(this._tabEdited, newName); gBrowser._setTabLabel(this._tabEdited, newName, {
_zenChangeLabelFlag: true,
});
gZenUIManager.showToast("zen-tabs-renamed"); gZenUIManager.showToast("zen-tabs-renamed");
} else { } else {
delete this._tabEdited.zenStaticLabel; delete this._tabEdited.zenStaticLabel;
@@ -1708,7 +1711,8 @@ window.gZenVerticalTabsManager = {
this._tabEdited.after(input); this._tabEdited.after(input);
} }
input.focus(); input.focus();
input.select(); input.setSelectionRange(0, input.value.length, "backward");
input.scrollLeft = 0;
input.addEventListener("blur", this._renameTabHalt); input.addEventListener("blur", this._renameTabHalt);
}, },

View File

@@ -115,7 +115,9 @@ document.addEventListener(
}); });
break; break;
case "cmd_zenTogglePinTab": { case "cmd_zenTogglePinTab": {
const currentTab = gBrowser.selectedTab; const currentTab = gZenGlanceManager.getTabOrGlanceParent(
gBrowser.selectedTab
);
if (currentTab && !currentTab.hasAttribute("zen-empty-tab")) { if (currentTab && !currentTab.hasAttribute("zen-empty-tab")) {
if (currentTab.pinned) { if (currentTab.pinned) {
gBrowser.unpinTab(currentTab); gBrowser.unpinTab(currentTab);

View File

@@ -51,6 +51,10 @@
--zen-toolbox-max-width: 74px !important; --zen-toolbox-max-width: 74px !important;
--zen-compact-float: var(--zen-element-separation); --zen-compact-float: var(--zen-element-separation);
@media (-moz-mac-tahoe-theme) {
--zen-compact-mode-no-padding-radius-fix: -2px;
}
:root[zen-no-padding='true'] & { :root[zen-no-padding='true'] & {
--zen-compact-float: 10px; --zen-compact-float: 10px;
--zen-compact-top-toolbar-hidden-fix: var(--zen-compact-float); --zen-compact-top-toolbar-hidden-fix: var(--zen-compact-float);

View File

@@ -69,6 +69,7 @@
#changeSpaceTimer = null; #changeSpaceTimer = null;
#isAnimatingTabMove = false; #isAnimatingTabMove = false;
#firstHapticFeedbackPlayed = false; #firstHapticFeedbackPlayed = false;
#lastDragOverTime = 0;
#dragOverSplit = {}; #dragOverSplit = {};
@@ -723,9 +724,11 @@
const { isNearLeftEdge, isNearRightEdge } = const { isNearLeftEdge, isNearRightEdge } =
this.#shouldSwitchSpace(event); this.#shouldSwitchSpace(event);
if (isNearLeftEdge || isNearRightEdge) { if (isNearLeftEdge || isNearRightEdge) {
if (!this.#changeSpaceTimer && !this.#isOutOfWindow) { this.#lastDragOverTime = Date.now();
if (!this.#changeSpaceTimer) {
this.#changeSpaceTimer = setTimeout(() => { this.#changeSpaceTimer = setTimeout(() => {
if (this.#isOutOfWindow) { const timeSinceLastDragOver = Date.now() - this.#lastDragOverTime;
if (timeSinceLastDragOver > 250) {
return; return;
} }
this.clearDragOverVisuals(); this.clearDragOverVisuals();
@@ -1333,7 +1336,8 @@
// Essentials should be properly handled by ::animateVerticalPinnedGridDragOver // Essentials should be properly handled by ::animateVerticalPinnedGridDragOver
if (!dropElement || dropElement.hasAttribute("zen-essential")) { if (!dropElement || dropElement.hasAttribute("zen-essential")) {
this.clearDragOverVisuals(); this.clearDragOverVisuals();
return null; // If dropElement is null or essential, dropElement should be the empty tab
return [dropBefore, gZenWorkspaces._emptyTab];
} }
if (dropElement.hasAttribute("zen-empty-tab") && dropElement.group) { if (dropElement.hasAttribute("zen-empty-tab") && dropElement.group) {
let secondTab = dropElement.group.tabs[1]; let secondTab = dropElement.group.tabs[1];

View File

@@ -289,6 +289,9 @@ export class nsZenFolder extends MozTabbrowserTabGroup {
addTabs(tabs) { addTabs(tabs) {
let tabsFromOutside = []; let tabsFromOutside = [];
for (let tab of tabs) { for (let tab of tabs) {
if (tab.hasAttribute("zen-essential")) {
gZenPinnedTabManager.removeEssentials(tab, false);
}
if (tab.group !== this) { if (tab.group !== this) {
tabsFromOutside.push(tab); tabsFromOutside.push(tab);
} }

View File

@@ -622,15 +622,13 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
} }
createFolder(tabs = [], options = {}) { createFolder(tabs = [], options = {}) {
const filteredTabs = tabs const filteredTabs = tabs.map(tab => {
.filter(tab => !tab.hasAttribute("zen-essential")) gBrowser.pinTab(tab);
.map(tab => { if (tab?.group?.hasAttribute("split-view-group")) {
gBrowser.pinTab(tab); tab = tab.group;
if (tab?.group?.hasAttribute("split-view-group")) { }
tab = tab.group; return tab;
} });
return tab;
});
const workspacePinned = gZenWorkspaces.workspaceElement( const workspacePinned = gZenWorkspaces.workspaceElement(
options.workspaceId options.workspaceId

View File

@@ -85,8 +85,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
menuitem.addEventListener("command", () => menuitem.addEventListener("command", () =>
this.openGlance({ this.openGlance({
url: gContextMenu.linkURL, url: gContextMenu.linkURL,
triggeringPrincipal: triggeringPrincipal: gContextMenu.principal,
Services.scriptSecurityManager.getSystemPrincipal(),
}) })
); );
@@ -354,6 +353,28 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
return this.#lastLinkClickData; return this.#lastLinkClickData;
} }
/**
* Check whether a glance load is permitted for its triggering principal.
*
* @param {object} data - Glance data including URL and triggeringPrincipal
* @returns {boolean} Whether the load is allowed
*/
#isGlanceLoadAllowed(data) {
const { url, triggeringPrincipal } = data ?? {};
if (typeof url !== "string" || !url.length || !triggeringPrincipal) {
return false;
}
try {
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
triggeringPrincipal,
url
);
} catch {
return false;
}
return true;
}
/** /**
* Open a glance overlay with the specified data * Open a glance overlay with the specified data
* *
@@ -371,6 +392,13 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
return Promise.resolve(this.#currentTab); return Promise.resolve(this.#currentTab);
} }
// Existing-tab glances perform no navigation and are exempt; for fresh
// loads, refuse any URL the triggering principal isn't allowed to load
// (e.g. a web page linking to file://).
if (!existingTab && !this.#isGlanceLoadAllowed(data)) {
return Promise.resolve(null);
}
if (!data.height || !data.width) { if (!data.height || !data.width) {
data = { data = {
...data, ...data,
@@ -1523,6 +1551,7 @@ class nsZenGlanceManager extends nsZenDOMOperatedFeature {
return ( return (
owner && owner &&
owner.pinned && owner.pinned &&
!owner.hasAttribute("glance-id") &&
this._lazyPref.SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE && this._lazyPref.SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE &&
owner.linkedBrowser?.browsingContext?.isAppTab && owner.linkedBrowser?.browsingContext?.isAppTab &&
this.tabDomainsDiffer(owner, uri) && this.tabDomainsDiffer(owner, uri) &&

View File

@@ -20,4 +20,5 @@ DIRS += [
"share", "share",
"spaces", "spaces",
"space-routing", "space-routing",
"sync",
] ]

View File

@@ -10,6 +10,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
ZenLiveFoldersManager: ZenLiveFoldersManager:
"resource:///modules/zen/ZenLiveFoldersManager.sys.mjs", "resource:///modules/zen/ZenLiveFoldersManager.sys.mjs",
ZenSyncStore: "resource:///modules/zen/ZenSyncManager.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs", SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
@@ -609,6 +610,7 @@ export class nsZenSessionManager {
} }
); );
this.#collectWindowData(windows); this.#collectWindowData(windows);
lazy.ZenSyncStore.notifyAboutChanges();
// This would save the data to disk asynchronously or when quitting the app. // This would save the data to disk asynchronously or when quitting the app.
let sidebar = this.#sidebarWithoutCloning; let sidebar = this.#sidebarWithoutCloning;
this.#file.data = sidebar; this.#file.data = sidebar;
@@ -622,6 +624,15 @@ export class nsZenSessionManager {
this.log(`Saving Zen session data with ${sidebar.tabs?.length || 0} tabs`); this.log(`Saving Zen session data with ${sidebar.tabs?.length || 0} tabs`);
} }
/**
* Returns the sidebar data object.
*
* @returns {object}
*/
getSidebarData() {
return this.#sidebarWithoutCloning;
}
/** /**
* Called when the last known backup should be deleted and a new one * Called when the last known backup should be deleted and a new one
* created. This uses the #deferredBackupTask to debounce clusters of * created. This uses the #deferredBackupTask to debounce clusters of

View File

@@ -245,10 +245,6 @@ class nsZenSpaceRoutingManager {
if (targetWorkspace) { if (targetWorkspace) {
workspaces.moveTabToWorkspace(newTab, targetWorkspace.uuid); workspaces.moveTabToWorkspace(newTab, targetWorkspace.uuid);
if (inBackground) {
return;
}
const mostRecentWindow = const mostRecentWindow =
Services.wm.getMostRecentWindow("navigator:browser"); Services.wm.getMostRecentWindow("navigator:browser");
const isOriginatingWindow = win === mostRecentWindow; const isOriginatingWindow = win === mostRecentWindow;
@@ -256,7 +252,10 @@ class nsZenSpaceRoutingManager {
win.gZenWorkspaces.lastSelectedWorkspaceTabs[ win.gZenWorkspaces.lastSelectedWorkspaceTabs[
targetWorkspace.uuid targetWorkspace.uuid
] = newTab; ] = newTab;
await win.gZenWorkspaces.changeWorkspace(targetWorkspace);
if (!inBackground) {
await win.gZenWorkspaces.changeWorkspace(targetWorkspace);
}
} }
} }
} }

View File

@@ -182,8 +182,8 @@ class nsZenWorkspaceCreation extends MozXULElement {
this.onProfileCommand.bind(this) this.onProfileCommand.bind(this)
); );
this.profilesPopup.addEventListener( this.profilesPopup.addEventListener(
"popupshown", "popupshowing",
this.onProfilePopupShown.bind(this) this.onProfilePopupShowing.bind(this)
); );
this.profilesPopup.addEventListener( this.profilesPopup.addEventListener(
"command", "command",
@@ -192,7 +192,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
this.currentProfile = { this.currentProfile = {
id: 0, id: 0,
name: "Default", name: ContextualIdentityService.formatContextLabel("user-context-none"),
}; };
} else { } else {
this.inputProfile.parentNode.hidden = true; this.inputProfile.parentNode.hidden = true;
@@ -296,7 +296,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
this.profilesPopup.openPopup(event.target, "after_start"); this.profilesPopup.openPopup(event.target, "after_start");
} }
onProfilePopupShown(event) { onProfilePopupShowing(event) {
return window.createUserContextMenu(event, { return window.createUserContextMenu(event, {
isContextMenu: true, isContextMenu: true,
showDefaultTab: true, showDefaultTab: true,

View File

@@ -11,6 +11,7 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs", ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
ZenSyncStore: "resource:///modules/zen/ZenSyncManager.sys.mjs",
}); });
ChromeUtils.defineLazyGetter(lazy, "browserBackgroundElement", () => { ChromeUtils.defineLazyGetter(lazy, "browserBackgroundElement", () => {
@@ -148,7 +149,7 @@ class nsZenWorkspaces {
if (!this.privateWindowOrDisabled) { if (!this.privateWindowOrDisabled) {
const observerFunction = async () => { const observerFunction = async () => {
delete this._workspaceBookmarksCache; delete this._workspaceBookmarksCache;
await this.workspaceBookmarks(); await this.#initializeWorkspaceBookmarks();
this._invalidateBookmarkContainers(); this._invalidateBookmarkContainers();
}; };
Services.obs.addObserver(observerFunction, "workspace-bookmarks-updated"); Services.obs.addObserver(observerFunction, "workspace-bookmarks-updated");
@@ -168,6 +169,63 @@ class nsZenWorkspaces {
} }
} }
/**
* Applies live sync changes: updates workspace cache, removes deleted items,
* then creates/updates pulled items.
*
* @param {{ spaces: Array}} pulled Reconcile-pulled items.
* @param {{ spaces: Array}} removals Items to remove.
*/
async _applySyncChanges(pulled, removals = {}) {
if (!this.shouldHaveWorkspaces || this.privateWindowOrDisabled) {
return;
}
await this.promiseInitialized;
// 1. Update workspace cache (remove deleted, merge pulled)
const removedSpaceIds = new Set((removals.spaces || []).map(s => s.uuid));
if (removedSpaceIds.size || pulled.spaces?.length) {
const localMap = new Map(
this.getWorkspaces()
.filter(w => !removedSpaceIds.has(w.uuid))
.map(w => [w.uuid, w])
);
for (const space of pulled.spaces || []) {
if (!space?.uuid) {
continue;
}
const existing = localMap.get(space.uuid);
localMap.set(space.uuid, existing ? { ...existing, ...space } : space);
}
await this.propagateWorkspaces(
this.#getOrderedWorkspacesByPosition(Array.from(localMap.values()))
);
this.#propagateWorkspaceData();
}
}
#getOrderedWorkspacesByPosition(workspaces) {
return [...workspaces]
.map((workspace, index) => ({ workspace, index }))
.sort((a, b) => {
const aPosition =
typeof a.workspace.position === "number"
? a.workspace.position
: a.index;
const bPosition =
typeof b.workspace.position === "number"
? b.workspace.position
: b.index;
return aPosition - bPosition || a.index - b.index;
})
.map(({ workspace }) => {
// strip the position property that comes from pulled workspaces
const rest = { ...workspace };
delete rest.position;
return rest;
});
}
#afterLoadInit() { #afterLoadInit() {
const onResize = (...args) => { const onResize = (...args) => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
@@ -391,7 +449,7 @@ class nsZenWorkspaces {
container = 0; container = 0;
} }
let essentialsContainer = document.querySelector( let essentialsContainer = document.querySelector(
`.zen-essentials-container[container="${container}"]:not([cloned])` `.zen-essentials-container[container="${container}"]`
); );
if (!essentialsContainer) { if (!essentialsContainer) {
essentialsContainer = document.createXULElement("hbox"); essentialsContainer = document.createXULElement("hbox");
@@ -402,16 +460,19 @@ class nsZenWorkspaces {
document document
.getElementById("zen-essentials") .getElementById("zen-essentials")
.appendChild(essentialsContainer); .appendChild(essentialsContainer);
// Set an initial hidden state if the essentials section is not supposed
// to be shown on the current workspace
if (
this.containerSpecificEssentials &&
this.getActiveWorkspaceFromCache()?.containerTabId != container
) {
essentialsContainer.setAttribute("hidden", "true");
}
} }
// Set a hidden state if the essentials section is not supposed
// to be shown on the current workspace, else remove the hidden state
if (
this.containerSpecificEssentials &&
this.getActiveWorkspaceFromCache()?.containerTabId != container
) {
essentialsContainer.setAttribute("hidden", "true");
} else {
essentialsContainer.removeAttribute("hidden");
}
return essentialsContainer; return essentialsContainer;
} }
@@ -691,17 +752,17 @@ class nsZenWorkspaces {
return spacesForSS; return spacesForSS;
} }
async workspaceBookmarks() { async #initializeWorkspaceBookmarks() {
if (this.privateWindowOrDisabled) { if (this.privateWindowOrDisabled) {
this._workspaceBookmarksCache = { this._workspaceBookmarksCache = {
bookmarks: [], bookmarks: [],
lastChangeTimestamp: 0, lastChangeTimestamp: 0,
}; };
return this._workspaceBookmarksCache; return;
} }
if (this._workspaceBookmarksCache) { if (this._workspaceBookmarksCache) {
return this._workspaceBookmarksCache; return;
} }
const [bookmarks, lastChangeTimestamp] = await Promise.all([ const [bookmarks, lastChangeTimestamp] = await Promise.all([
@@ -710,8 +771,6 @@ class nsZenWorkspaces {
]); ]);
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp }; this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
return this._workspaceCache;
} }
restoreWorkspacesFromSessionStore(aWinData = {}) { restoreWorkspacesFromSessionStore(aWinData = {}) {
@@ -771,7 +830,7 @@ class nsZenWorkspaces {
return (async () => { return (async () => {
await this.#waitForPromises(); await this.#waitForPromises();
this.#afterLoadInit(); this.#afterLoadInit();
await this.workspaceBookmarks(); await this.#initializeWorkspaceBookmarks();
await this.changeWorkspace(activeWorkspace, { onInit: true }); await this.changeWorkspace(activeWorkspace, { onInit: true });
this.#fixTabPositions(); this.#fixTabPositions();
this.onWindowResize(); this.onWindowResize();
@@ -797,6 +856,13 @@ class nsZenWorkspaces {
})(); })();
} }
#markWorkspaceChanged(workspaceId) {
lazy.ZenSyncStore.markItemChanged({
type: "space",
id: workspaceId,
});
}
async selectStartPage() { async selectStartPage() {
if (!this.workspaceEnabled || gZenUIManager.testingEnabled) { if (!this.workspaceEnabled || gZenUIManager.testingEnabled) {
return; return;
@@ -1098,6 +1164,9 @@ class nsZenWorkspaces {
const item = document.createXULElement("menuitem"); const item = document.createXULElement("menuitem");
item.className = "zen-workspace-context-menu-item"; item.className = "zen-workspace-context-menu-item";
item.setAttribute("zen-workspace-id", workspace.uuid); item.setAttribute("zen-workspace-id", workspace.uuid);
if (AppConstants.platform === "linux") {
disableCurrent = true;
}
if (!disableCurrent) { if (!disableCurrent) {
item.setAttribute("type", "radio"); item.setAttribute("type", "radio");
} }
@@ -1214,12 +1283,19 @@ class nsZenWorkspaces {
} else { } else {
workspacesData.push(workspaceData); workspacesData.push(workspaceData);
} }
// mark item as changed for sync
this.#markWorkspaceChanged(workspaceData.uuid);
this.#propagateWorkspaceData(); this.#propagateWorkspaceData();
} }
removeWorkspace(windowID) { removeWorkspace(windowID) {
let { promise, resolve } = Promise.withResolvers(); let { promise, resolve } = Promise.withResolvers();
this.#deleteWorkspaceOwnedTabs(windowID); this.#deleteWorkspaceOwnedTabs(windowID);
// mark item as changed for sync
this.#markWorkspaceChanged(windowID);
let workspacesData = this.getWorkspaces(); let workspacesData = this.getWorkspaces();
// Remove the workspace from the cache // Remove the workspace from the cache
workspacesData = workspacesData.filter( workspacesData = workspacesData.filter(
@@ -1351,30 +1427,50 @@ class nsZenWorkspaces {
if (this.privateWindowOrDisabled) { if (this.privateWindowOrDisabled) {
return; return;
} }
const workspaces = this._workspaceCache; const workspaces = this.getWorkspaces();
// Track previous positions so we only notify observers for workspaces whose
// position changed during the reorder.
const previousPositions = new Map(
workspaces.map((workspace, index) => [workspace.uuid, index])
);
const workspace = workspaces.find(w => w.uuid === id); const workspace = workspaces.find(w => w.uuid === id);
if (!workspace) { if (!workspace) {
console.warn(`Workspace with ID ${id} not found for reordering.`); console.warn(`Workspace with ID ${id} not found for reordering.`);
return; return;
} }
// Remove the workspace from its current position // Remove the workspace from its current position
const currentIndex = workspaces.indexOf(workspace); const currentIndex = workspaces.indexOf(workspace);
if (currentIndex === -1) { if (currentIndex === -1) {
console.warn(`Workspace with ID ${id} not found in the list.`); console.warn(`Workspace with ID ${id} not found in the list.`);
return; return;
} }
workspaces.splice(currentIndex, 1);
// Insert the workspace at the new position // Insert the workspace at the new position
if (newPosition < 0 || newPosition > workspaces.length) { if (newPosition < 0 || newPosition >= workspaces.length) {
console.warn( console.warn(
`Invalid position ${newPosition} for reordering workspace with ID ${id}.` `Invalid position ${newPosition} for reordering workspace with ID ${id}.`
); );
return; return;
} }
workspaces.splice(currentIndex, 1);
workspaces.splice(newPosition, 0, workspace); workspaces.splice(newPosition, 0, workspace);
// Propagate the changes if the order has changed // Propagate the changes if the order has changed
if (currentIndex !== newPosition) { if (currentIndex !== newPosition) {
this.#propagateWorkspaceData(); this._workspaceCache = workspaces;
for (const [i, ws] of workspaces.entries()) {
if (previousPositions.get(ws.uuid) === i) {
continue;
}
// mark item as changed for sync
this.#markWorkspaceChanged(ws.uuid);
}
this.#propagateWorkspaceData(workspaces);
} }
} }
@@ -1927,28 +2023,20 @@ class nsZenWorkspaces {
diff += spaceLen; diff += spaceLen;
} }
const isGoingLeft = diff < 0; const isGoingLeft = diff < 0;
const clonedEssentials = []; const essentialsAnimData = [];
if (shouldAnimate && this.shouldAnimateEssentials && previousWorkspace) { if (shouldAnimate && this.shouldAnimateEssentials && previousWorkspace) {
const containerIds = new Map();
for (const workspace of workspaces) { for (const workspace of workspaces) {
const essentialsContainer = this.getEssentialsSection( const containerId = workspace.containerTabId;
workspace.containerTabId if (!containerIds.has(containerId)) {
); containerIds.set(containerId, []);
let lastCloned = clonedEssentials[clonedEssentials.length - 1];
if (lastCloned && lastCloned.contextId == workspace.containerTabId) {
lastCloned.repeat++;
lastCloned.workspaces.push(workspace);
continue;
} }
essentialsContainer.setAttribute("hidden", "true"); containerIds.get(containerId).push(workspace);
const essentialsClone = essentialsContainer.cloneNode(true); }
essentialsClone.removeAttribute("hidden"); for (const [containerId, spaces] of containerIds) {
essentialsClone.setAttribute("cloned", "true"); essentialsAnimData.push({
clonedEssentials.push({ element: this.getEssentialsSection(containerId),
container: essentialsClone, workspaces: spaces,
workspaces: [workspace],
contextId: workspace.containerTabId,
originalContainer: essentialsContainer,
repeat: 0,
}); });
} }
} }
@@ -2055,190 +2143,48 @@ class nsZenWorkspaces {
} }
} }
if (this.shouldAnimateEssentials && previousWorkspace) { if (this.shouldAnimateEssentials && previousWorkspace) {
// Animate essentials for (const data of essentialsAnimData) {
const newWorkspaceEssentialsContainer = clonedEssentials.find(cloned => const container = data.element;
cloned.workspaces.some(w => w.uuid === newWorkspace.uuid) const essentialsWorkspaces = data.workspaces;
);
// Get a list of essentials containers that are in between the first and last workspace const containsPrev = essentialsWorkspaces.some(
const essentialsContainersInBetween = clonedEssentials.filter(cloned => { w => w.uuid === previousWorkspace.uuid
const essentialsWorkspaces = cloned.workspaces;
const firstIndex = workspaces.findIndex(
w => w.uuid === essentialsWorkspaces[0].uuid
); );
const lastIndex = workspaces.findIndex( const containsNew = essentialsWorkspaces.some(
w => w => w.uuid === newWorkspace.uuid
w.uuid ===
essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
); );
const [start, end] = [ if (!containsPrev && !containsNew) {
Math.min(previousWorkspaceIndex, newWorkspaceIndex), container.setAttribute("hidden", "true");
Math.max(previousWorkspaceIndex, newWorkspaceIndex),
];
// Check if any part of the container overlaps with the movement range
return firstIndex <= end && lastIndex >= start;
});
for (const cloned of clonedEssentials) {
const container = cloned.container;
const essentialsWorkspaces = cloned.workspaces;
const repeats = cloned.repeat;
// Animate like the workspaces above expect essentials are a bit more
// complicated because they are not based on workspaces but on containers
// So, if we have the following arangement:
// | [workspace1] [workspace2] [workspace3] [workspace4]
// | [container1] [container1] [container2] [container1]
// And if we are changing from workspace 1 to workspace 4,
// we should be doing the following:
// First container (repeat 2 times) will stay in place until
// we reach container 3, then animate to the left and container 2
// also move to the left after that while container 1 in workspace 4
// will slide in from the right
// Get the index from first and last workspace
const firstWorkspaceIndex = workspaces.findIndex(
w => w.uuid === essentialsWorkspaces[0].uuid
);
const lastWorkspaceIndex = workspaces.findIndex(
w =>
w.uuid ===
essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
);
cloned.originalContainer.style.removeProperty("transform");
// Check if the container is even going to appear on the screen, to save on animation
if (
// We also need to check if the container is even going to appear on the screen.
// In order to do this, we need to check if the container is between the first and last workspace.
// Note that essential containers can have multiple workspaces,
// so we need to check if any of the workspaces in the container are between the
// first and last workspace.
!essentialsContainersInBetween.find(
ce =>
ce.workspaces.some(
w => w.uuid === essentialsWorkspaces[0].uuid
) &&
ce.workspaces.some(
w =>
w.uuid ===
essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
)
)
) {
continue; continue;
} }
cloned.originalContainer.parentNode.appendChild(container);
let stepsInBetween =
Math.abs(
newWorkspaceIndex -
(isGoingLeft ? firstWorkspaceIndex : lastWorkspaceIndex)
) + 1;
const usingSameContainer =
newWorkspaceEssentialsContainer?.workspaces.some(
w => w.uuid === newWorkspace.uuid
) &&
newWorkspaceEssentialsContainer?.workspaces.some(
w => w.uuid === previousWorkspace.uuid
);
let newOffset =
-(
newWorkspaceIndex -
(isGoingLeft ? firstWorkspaceIndex : lastWorkspaceIndex) +
(!isGoingLeft ? repeats - 1 : -repeats + 1)
) * 100;
let existingOffset = container.removeAttribute("hidden");
-(
newWorkspaceIndex -
(isGoingLeft ? lastWorkspaceIndex : firstWorkspaceIndex) +
(isGoingLeft ? repeats - 1 : -repeats + 1)
) * 100;
// If we are on the same container and both new and old workspace are in the same "essentialsWorkspaces" if (containsPrev && containsNew) {
// we can simply not animate the essentials container.style.transform = "translateX(0%)";
if ( continue;
usingSameContainer && }
essentialsWorkspaces.some(w => w.uuid === newWorkspace.uuid) &&
essentialsWorkspaces.some(w => w.uuid === previousWorkspace.uuid) let existingOffset, newOffset;
) { const currentTransform =
parseFloat(container.style.transform.split("(")[1]) || 0;
if (containsPrev && !containsNew) {
existingOffset = currentTransform;
newOffset = isGoingLeft ? 100 : -100;
} else {
existingOffset = currentTransform || (isGoingLeft ? -100 : 100);
newOffset = 0; newOffset = 0;
existingOffset = 0;
} }
const needsOffsetAdjustment =
stepsInBetween > essentialsWorkspaces.length || usingSameContainer;
if (repeats > 0 && needsOffsetAdjustment) {
if (!isGoingLeft) {
if (existingOffset !== 0) {
existingOffset += 100;
}
if (newOffset !== 0) {
newOffset += 100;
}
} else {
if (existingOffset !== 0) {
existingOffset -= 100;
}
if (newOffset !== 0) {
newOffset -= 100;
}
}
}
// Special case: going forward from single reused container to a new one
if (
!usingSameContainer &&
!isGoingLeft &&
lastWorkspaceIndex === newWorkspaceIndex - 1
) {
existingOffset = 0;
newOffset = -100;
stepsInBetween = 1;
}
if (
!usingSameContainer &&
isGoingLeft &&
firstWorkspaceIndex === newWorkspaceIndex + 1
) {
existingOffset = 0;
newOffset = 100;
stepsInBetween = 1;
}
if (
!usingSameContainer &&
isGoingLeft &&
(firstWorkspaceIndex === newWorkspaceIndex - 1 ||
firstWorkspaceIndex === newWorkspaceIndex)
) {
existingOffset = -100;
newOffset = 0;
stepsInBetween = 1;
}
if (
!usingSameContainer &&
!isGoingLeft &&
firstWorkspaceIndex === newWorkspaceIndex
) {
existingOffset = 100;
newOffset = 0;
stepsInBetween = 1;
}
const newTransform = `translateX(${newOffset}%)`; const newTransform = `translateX(${newOffset}%)`;
let existingTransform = `translateX(${existingOffset}%)`; let existingTransform = `translateX(${existingOffset}%)`;
if (container.style.transform && container.style.transform !== "none") {
existingTransform = container.style.transform;
}
if (shouldAnimate) { if (shouldAnimate) {
container.style.transform = existingTransform; container.style.transform = existingTransform;
animations.push( animations.push(
gZenUIManager.motion.animate( gZenUIManager.motion.animate(
container, container,
{ {
transform: [ transform: [existingTransform, newTransform],
existingTransform,
new Array(stepsInBetween).fill(newTransform).join(","),
],
}, },
{ {
type: "spring", type: "spring",
@@ -2267,17 +2213,12 @@ class nsZenWorkspaces {
this.#currentSpaceSwitchContext.animations = []; this.#currentSpaceSwitchContext.animations = [];
document.documentElement.removeAttribute("animating-background"); document.documentElement.removeAttribute("animating-background");
if (shouldAnimate) { if (shouldAnimate) {
for (const cloned of clonedEssentials) { for (const data of essentialsAnimData) {
cloned.container.remove(); data.element.style.removeProperty("transform");
} }
this._alwaysAnimatePaddingTop = true; this._alwaysAnimatePaddingTop = true;
this.updateTabsContainers(); this.updateTabsContainers();
} }
const essentialsContainer = this.getEssentialsSection(
newWorkspace.containerTabId
);
essentialsContainer.removeAttribute("hidden");
essentialsContainer.style.transform = "none";
gBrowser.tabContainer._invalidateCachedTabs(); gBrowser.tabContainer._invalidateCachedTabs();
gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width"); gZenUIManager.tabsWrapper.style.removeProperty("scrollbar-width");
this._animatingChange = false; this._animatingChange = false;
@@ -2493,7 +2434,7 @@ class nsZenWorkspaces {
for (const tab of gBrowser.tabs) { for (const tab of gBrowser.tabs) {
if ( if (
!tab.hasAttribute("zen-workspace-id") && !tab.hasAttribute("zen-workspace-id") &&
!tab.hasAttribute("zen-workspace-id") !tab.hasAttribute("zen-essential")
) { ) {
tab.setAttribute("zen-workspace-id", workspace.uuid); tab.setAttribute("zen-workspace-id", workspace.uuid);
} }
@@ -3152,9 +3093,6 @@ class nsZenWorkspaces {
...normalContainers, ...normalContainers,
]; ];
for (const container of containers) { for (const container of containers) {
if (container.hasAttribute("cloned")) {
continue;
}
for (const tab of container.children) { for (const tab of container.children) {
if (gBrowser.isTab(tab)) { if (gBrowser.isTab(tab)) {
tabs.push(tab); tabs.push(tab);

View File

@@ -1229,7 +1229,11 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
const newTab = this.openAndSwitchToTab(url, { const newTab = this.openAndSwitchToTab(url, {
skipRoute: true, skipRoute: true,
inBackground: false, inBackground: false,
triggeringPrincipal: window.gContextMenu.principal,
}); });
if (!newTab) {
return;
}
this.splitTabs([currentTab, newTab], undefined, 1); this.splitTabs([currentTab, newTab], undefined, 1);
} }
@@ -1979,9 +1983,24 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
* @returns {tab} The tab that was opened * @returns {tab} The tab that was opened
*/ */
openAndSwitchToTab(url, options) { openAndSwitchToTab(url, options) {
const triggeringPrincipal = options?.triggeringPrincipal;
if (!triggeringPrincipal) {
return null;
}
try {
Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
triggeringPrincipal,
url
);
} catch {
return null;
}
const parentWindow = window.parent; const parentWindow = window.parent;
const targetWindow = parentWindow || window; const targetWindow = parentWindow || window;
const tab = targetWindow.gBrowser.addTrustedTab(url, options); const tab = targetWindow.gBrowser.addTab(url, {
...options,
triggeringPrincipal,
});
targetWindow.gBrowser.selectedTab = tab; targetWindow.gBrowser.selectedTab = tab;
return tab; return tab;
} }

View File

@@ -0,0 +1,160 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ZenSessionStore: "resource:///modules/zen/ZenSessionManager.sys.mjs",
ContextualIdentityService:
"resource://gre/modules/ContextualIdentityService.sys.mjs",
ZenWindowSync: "resource:///modules/zen/ZenWindowSync.sys.mjs",
});
function normalizeUserContextId(value) {
const normalized = typeof value === "string" ? Number(value) : value;
if (!Number.isSafeInteger(normalized) || normalized <= 0) {
return null;
}
return normalized;
}
class ZenSyncManager {
getSidebarData() {
return lazy.ZenSessionStore.getSidebarData();
}
/**
* Whether to ignore changes to items. This is used to prevent
* infinite loops when applying incoming sync changes.
*
* @type {boolean}
*/
#ignoreChanges = false;
#changedItems = new Map();
markItemChanged(item) {
if (item.type && item.id && !this.#ignoreChanges) {
const key = `${item.type}~${item.id}`;
this.#changedItems.set(key, { type: item.type, id: item.id });
}
}
#getChangedItems() {
return Array.from(this.#changedItems.values());
}
#clearChangedItems() {
this.#changedItems.clear();
}
notifyAboutChanges() {
const changedItems = this.#getChangedItems();
for (const item of changedItems) {
Services.obs.notifyObservers(
{ wrappedJSObject: item },
"zen-workspace-item-changed"
);
}
this.#clearChangedItems();
}
async applyIncomingBatch(pulled, removals) {
try {
this.#ignoreChanges = true;
this.#applyIncomingContainers(
pulled.containers || [],
removals.containers || []
);
const win = lazy.ZenWindowSync.firstSyncedWindow;
if (win?.gZenWorkspaces) {
await win.gZenWorkspaces._applySyncChanges(pulled, removals);
}
} catch (e) {
console.error("ZenSyncManager: Failed to apply incoming sync data:", e);
throw e;
} finally {
this.#ignoreChanges = false;
}
}
#applyIncomingContainers(pulledContainers, removedContainers) {
const localContainersById = new Map(
lazy.ContextualIdentityService.getPublicIdentities().map(container => [
container.userContextId,
container,
])
);
for (const container of pulledContainers) {
if (!container.name) {
continue;
}
const userContextId = normalizeUserContextId(container.userContextId);
if (userContextId === null) {
console.warn(
"ZenSyncManager: Ignoring incoming container with invalid userContextId",
{ container }
);
continue;
}
const existsLocally = localContainersById.has(userContextId);
if (existsLocally) {
lazy.ContextualIdentityService.update(
userContextId,
container.name,
container.icon,
container.color
);
continue;
}
const createdIdentity = lazy.ContextualIdentityService.create(
container.name,
container.icon,
container.color,
userContextId
);
if (createdIdentity) {
localContainersById.set(createdIdentity.userContextId, createdIdentity);
}
if (createdIdentity && createdIdentity.userContextId !== userContextId) {
console.warn("ZenSyncManager: Container sync created unexpected ID", {
requestedId: userContextId,
createdId: createdIdentity.userContextId,
name: container.name,
});
}
}
for (const container of removedContainers) {
const userContextId = normalizeUserContextId(container.userContextId);
if (userContextId === null) {
console.warn(
"ZenSyncManager: Ignoring container removal with invalid userContextId",
{ container }
);
continue;
}
if (!localContainersById.has(userContextId)) {
continue;
}
try {
lazy.ContextualIdentityService.remove(userContextId);
localContainersById.delete(userContextId);
} catch {
// Container may already be gone locally.
}
}
}
}
export const ZenSyncStore = new ZenSyncManager();

View File

@@ -0,0 +1,404 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import {
Store,
SyncEngine,
Tracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ZenSyncStore: "resource:///modules/zen/ZenSyncManager.sys.mjs",
ContextualIdentityService:
"resource://gre/modules/ContextualIdentityService.sys.mjs",
});
const RECORD_ID_PREFIX_BY_TYPE = Object.freeze({
space: "s",
container: "c",
});
const RECORD_TYPE_BY_PREFIX = Object.freeze({
s: "space",
c: "container",
});
/**
* Sync record wrapper for workspace and container items stored in the
* Workspaces engine collection.
*/
export class ZenWorkspacesRecord extends CryptoWrapper {
_logName = "Sync.Record.ZenSpaces";
}
ZenWorkspacesRecord.prototype.type = "spaces";
function parseRecordId(id) {
const sep = id.indexOf("~");
if (sep === -1) {
return null;
}
const prefix = id.slice(0, sep);
const key = id.slice(sep + 1);
return { type: RECORD_TYPE_BY_PREFIX[prefix] || prefix, key };
}
function createRecordId(type, id) {
const prefix = RECORD_ID_PREFIX_BY_TYPE[type];
if (!prefix) {
throw new Error(`Unknown Spaces Sync record type: ${type}`);
}
return `${prefix}~${id}`;
}
function normalizeUserContextId(value) {
const normalized = typeof value === "string" ? Number(value) : value;
if (!Number.isSafeInteger(normalized) || normalized <= 0) {
return null;
}
return normalized;
}
/**
* Strips the sync-envelope fields (`id` and `type`) from incoming record data
* and restores the item's real identity key where needed
*
* @param {object} data
*/
function stripSyncFields(data) {
const rest = { ...data };
delete rest.id;
delete rest.type;
return rest;
}
/**
* Sync store implementation that serializes local workspace and container
* state into records and applies incoming remote changes.
*/
class ZenWorkspacesStore extends Store {
constructor(name, engine) {
super(name, engine);
}
async getAllIDs() {
const ids = {};
const sidebar = lazy.ZenSyncStore.getSidebarData();
for (const space of sidebar.spaces || []) {
if (space.uuid) {
ids[createRecordId("space", space.uuid)] = true;
}
}
for (const c of lazy.ContextualIdentityService.getPublicIdentities()) {
ids[createRecordId("container", c.userContextId)] = true;
}
return ids;
}
async itemExists(id) {
const parsed = parseRecordId(id);
if (!parsed) {
return false;
}
const sidebar = lazy.ZenSyncStore.getSidebarData();
switch (parsed.type) {
case "space":
return (sidebar.spaces || []).some(s => s.uuid === parsed.key);
case "container":
return lazy.ContextualIdentityService.getPublicIdentities().some(
c => String(c.userContextId) === parsed.key
);
default:
return false;
}
}
async createRecord(id, collection) {
const record = new ZenWorkspacesRecord(collection, id);
const parsed = parseRecordId(id);
if (!parsed) {
record.deleted = true;
return record;
}
const sidebar = lazy.ZenSyncStore.getSidebarData();
switch (parsed.type) {
case "space": {
const spaces = sidebar.spaces || [];
const idx = spaces.findIndex(s => s.uuid === parsed.key);
if (idx === -1) {
record.deleted = true;
return record;
}
const rest = { ...spaces[idx] };
delete rest.syncStatus;
record.cleartext = { id, type: "space", ...rest, position: idx };
break;
}
case "container": {
const container =
lazy.ContextualIdentityService.getPublicIdentities().find(
c => String(c.userContextId) === parsed.key
);
if (!container) {
record.deleted = true;
return record;
}
record.cleartext = {
id,
type: "container",
userContextId: container.userContextId,
name: container.name,
icon: container.icon,
color: container.color,
};
break;
}
default:
record.deleted = true;
}
return record;
}
async applyIncomingBatch(records, _countTelemetry) {
const pulled = { spaces: [], containers: [] };
const removals = { spaces: [], containers: [] };
for (const record of records) {
if (record.deleted) {
this._collectRemoval(record.id, removals);
continue;
}
const data = record.cleartext;
if (!data?.type) {
continue;
}
const clean = stripSyncFields(data);
switch (data.type) {
case "space":
pulled.spaces.push(clean);
break;
case "container":
pulled.containers.push(clean);
break;
}
}
// Suppress change tracking while applying incoming data to prevent
// feedback loops where applied items get re-uploaded immediately.
this.engine._tracker.ignoreAll = true;
try {
await lazy.ZenSyncStore.applyIncomingBatch(pulled, removals);
} finally {
this.engine._tracker.ignoreAll = false;
}
return [];
}
_collectRemoval(id, removals) {
const parsed = parseRecordId(id);
if (!parsed) {
return;
}
switch (parsed.type) {
case "space":
removals.spaces.push({ uuid: parsed.key });
break;
case "container": {
const userContextId = normalizeUserContextId(parsed.key);
if (userContextId === null) {
console.warn(
"ZenWorkspacesStore: Ignoring container removal with invalid userContextId",
{ id }
);
break;
}
removals.containers.push({ userContextId });
break;
}
}
}
async create(record) {
await this._applySingle(record);
}
async update(record) {
await this._applySingle(record);
}
async _applySingle(record) {
this.engine._tracker.ignoreAll = true;
try {
if (record.deleted) {
const removals = { spaces: [], containers: [] };
this._collectRemoval(record.id, removals);
await lazy.ZenSyncStore.applyIncomingBatch(
{ spaces: [], containers: [] },
removals
);
return;
}
const data = record.cleartext;
if (!data?.type) {
return;
}
const clean = stripSyncFields(data);
const pulled = { spaces: [], containers: [] };
switch (data.type) {
case "space":
pulled.spaces.push(clean);
break;
case "container":
pulled.containers.push(clean);
break;
}
await lazy.ZenSyncStore.applyIncomingBatch(pulled, {
spaces: [],
containers: [],
});
} finally {
this.engine._tracker.ignoreAll = false;
}
}
async remove() {
// No-op: never delete user data on wipe
}
async wipe() {
// No-op: never delete user data on wipe
}
changeItemID() {
// No-op
}
}
/**
* Sync tracker that watches workspace and contextual identity observers and
* marks the corresponding record IDs as changed.
*/
class ZenWorkspacesTracker extends Tracker {
#changedIDs = {};
#ignoreAll = false;
get ignoreAll() {
return this.#ignoreAll;
}
set ignoreAll(value) {
this.#ignoreAll = value;
}
onStart() {
Services.obs.addObserver(this, "zen-workspace-item-changed");
Services.obs.addObserver(this, "contextual-identity-created");
Services.obs.addObserver(this, "contextual-identity-updated");
Services.obs.addObserver(this, "contextual-identity-deleted");
}
onStop() {
Services.obs.removeObserver(this, "zen-workspace-item-changed");
Services.obs.removeObserver(this, "contextual-identity-created");
Services.obs.removeObserver(this, "contextual-identity-updated");
Services.obs.removeObserver(this, "contextual-identity-deleted");
}
observe(subject, topic, _data) {
if (this.#ignoreAll) {
return;
}
if (topic === "zen-workspace-item-changed") {
const type = subject?.wrappedJSObject?.type;
const id = subject?.wrappedJSObject?.id;
if (type && id) {
this._trackChange({ type, id });
}
} else if (topic.startsWith("contextual-identity-")) {
const id = subject?.wrappedJSObject?.userContextId;
if (id && normalizeUserContextId(id) !== null) {
this._trackChange({ type: "container", id });
}
}
}
_trackChange(data) {
if (data.type && data.id) {
const id = createRecordId(data.type, data.id);
this.#changedIDs[id] = Date.now() / 1000;
this.score += SCORE_INCREMENT_XLARGE;
}
}
async getChangedIDs() {
return { ...this.#changedIDs };
}
async addChangedID(id, when) {
this.#changedIDs[id] = when;
return true;
}
async removeChangedID(...ids) {
for (const id of ids) {
delete this.#changedIDs[id];
}
return true;
}
clearChangedIDs() {
this.#changedIDs = {};
}
}
/**
* Sync engine entrypoint that wires the Workspaces record, store, and tracker
* implementations into Firefox Sync.
*/
export class ZenWorkspacesEngine extends SyncEngine {
static get name() {
return "Spaces";
}
constructor(service) {
super("Spaces", service);
}
get _storeObj() {
return ZenWorkspacesStore;
}
get _trackerObj() {
return ZenWorkspacesTracker;
}
get _recordObj() {
return ZenWorkspacesRecord;
}
get version() {
return 2;
}
get syncPriority() {
return 8;
}
get allowSkippedRecord() {
return false;
}
}

8
src/zen/sync/moz.build Normal file
View File

@@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXTRA_JS_MODULES.zen += [
"ZenSyncManager.sys.mjs",
"ZenWorkspacesSync.sys.mjs",
]

View File

@@ -1156,10 +1156,6 @@
pointer-events: none; pointer-events: none;
position: fixed; /* Fix position to prevent scrolling */ position: fixed; /* Fix position to prevent scrolling */
} }
&[cloned] {
pointer-events: none;
}
} }
.tabbrowser-tab[zen-essential="true"], .tabbrowser-tab[zen-essential="true"],

View File

@@ -27,6 +27,10 @@ ChromeUtils.defineESModuleGetters(lazy, {
UrlUtils: "resource://gre/modules/UrlUtils.sys.mjs", UrlUtils: "resource://gre/modules/UrlUtils.sys.mjs",
}); });
ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
return new Localization(["browser/zen-command-palette.ftl"], true);
});
XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter(
lazy, lazy,
"enabledPref", "enabledPref",
@@ -182,7 +186,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
.workspaceElement(workspace.uuid) .workspaceElement(workspace.uuid)
?.style.getPropertyValue("--zen-primary-color"); ?.style.getPropertyValue("--zen-primary-color");
actions.push({ actions.push({
label: "Focus on", label: lazy.l10n.formatValueSync("zen-action-focus-on"),
extraPayload: { extraPayload: {
workspaceId: workspace.uuid, workspaceId: workspace.uuid,
prettyName: workspace.name, prettyName: workspace.name,
@@ -215,7 +219,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
.map(addon => { .map(addon => {
return { return {
icon: "chrome://browser/skin/zen-icons/extension.svg", icon: "chrome://browser/skin/zen-icons/extension.svg",
label: "Extension", label: lazy.l10n.formatValueSync("zen-action-extension"),
commandId: `zen:extension-${addon.id}`, commandId: `zen:extension-${addon.id}`,
extraPayload: { extraPayload: {
extensionId: addon.id, extensionId: addon.id,

View File

@@ -13,58 +13,62 @@ XPCOMUtils.defineLazyPreferenceGetter(
2 2
); );
ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
return new Localization(["browser/zen-command-palette.ftl"], true);
});
function isNotEmptyTab(window) { function isNotEmptyTab(window) {
return !window.gBrowser.selectedTab.hasAttribute("zen-empty-tab"); return !window.gBrowser.selectedTab.hasAttribute("zen-empty-tab");
} }
const globalActionsTemplate = [ const globalActionsTemplate = [
{ {
label: "Toggle Compact Mode", l10nId: "zen-action-toggle-compact-mode",
command: "cmd_zenCompactModeToggle", command: "cmd_zenCompactModeToggle",
icon: "chrome://browser/skin/zen-icons/sidebar.svg", icon: "chrome://browser/skin/zen-icons/sidebar.svg",
}, },
{ {
label: "Open Theme Picker", l10nId: "zen-action-open-theme-picker",
command: "cmd_zenOpenZenThemePicker", command: "cmd_zenOpenZenThemePicker",
icon: "chrome://browser/skin/zen-icons/edit-theme.svg", icon: "chrome://browser/skin/zen-icons/edit-theme.svg",
}, },
{ {
label: "New Split View", l10nId: "zen-action-new-split-view",
command: "cmd_zenNewEmptySplit", command: "cmd_zenNewEmptySplit",
icon: "chrome://browser/skin/zen-icons/split.svg", icon: "chrome://browser/skin/zen-icons/split.svg",
}, },
{ {
label: "New Folder", l10nId: "zen-action-new-folder",
command: "cmd_zenOpenFolderCreation", command: "cmd_zenOpenFolderCreation",
icon: "chrome://browser/skin/zen-icons/folder.svg", icon: "chrome://browser/skin/zen-icons/folder.svg",
}, },
{ {
label: "Copy Current URL", l10nId: "zen-action-copy-current-url",
command: "cmd_zenCopyCurrentURL", command: "cmd_zenCopyCurrentURL",
icon: "chrome://browser/skin/zen-icons/link.svg", icon: "chrome://browser/skin/zen-icons/link.svg",
}, },
{ {
label: "Settings", l10nId: "zen-action-settings",
command: window => window.openPreferences(), command: window => window.openPreferences(),
icon: "chrome://browser/skin/zen-icons/settings.svg", icon: "chrome://browser/skin/zen-icons/settings.svg",
}, },
{ {
label: "Open Private Window", l10nId: "zen-action-open-private-window",
command: "Tools:PrivateBrowsing", command: "Tools:PrivateBrowsing",
icon: "chrome://browser/skin/zen-icons/private-window.svg", icon: "chrome://browser/skin/zen-icons/private-window.svg",
}, },
{ {
label: "Open New Window", l10nId: "zen-action-open-new-window",
command: "cmd_newNavigator", command: "cmd_newNavigator",
icon: "chrome://browser/skin/zen-icons/window.svg", icon: "chrome://browser/skin/zen-icons/window.svg",
}, },
{ {
label: "New Blank Window", l10nId: "zen-action-new-blank-window",
command: "cmd_zenNewNavigatorUnsynced", command: "cmd_zenNewNavigatorUnsynced",
icon: "chrome://browser/skin/zen-icons/window.svg", icon: "chrome://browser/skin/zen-icons/window.svg",
}, },
{ {
label: "Pin Tab", l10nId: "zen-action-pin-tab",
command: "cmd_zenTogglePinTab", command: "cmd_zenTogglePinTab",
icon: "chrome://browser/skin/zen-icons/pin.svg", icon: "chrome://browser/skin/zen-icons/pin.svg",
isAvailable: window => { isAvailable: window => {
@@ -73,7 +77,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Unpin Tab", l10nId: "zen-action-unpin-tab",
command: "cmd_zenTogglePinTab", command: "cmd_zenTogglePinTab",
icon: "chrome://browser/skin/zen-icons/unpin.svg", icon: "chrome://browser/skin/zen-icons/unpin.svg",
isAvailable: window => { isAvailable: window => {
@@ -82,12 +86,12 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Open Space Routing", l10nId: "zen-action-open-space-routing",
command: "cmd_zenOpenSpaceRoutingSettings", command: "cmd_zenOpenSpaceRoutingSettings",
icon: "chrome://browser/skin/zen-icons/selectable/airplane.svg", icon: "chrome://browser/skin/zen-icons/selectable/airplane.svg",
}, },
{ {
label: "New Boost", l10nId: "zen-action-new-boost",
icon: "chrome://browser/skin/zen-icons/boost.svg", icon: "chrome://browser/skin/zen-icons/boost.svg",
isAvailable: window => { isAvailable: window => {
if (!isNotEmptyTab(window)) { if (!isNotEmptyTab(window)) {
@@ -130,7 +134,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Next Space", l10nId: "zen-action-next-space",
command: "cmd_zenWorkspaceForward", command: "cmd_zenWorkspaceForward",
icon: "chrome://browser/skin/zen-icons/forward.svg", icon: "chrome://browser/skin/zen-icons/forward.svg",
isAvailable: window => { isAvailable: window => {
@@ -138,7 +142,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Previous Space", l10nId: "zen-action-previous-space",
command: "cmd_zenWorkspaceBackward", command: "cmd_zenWorkspaceBackward",
icon: "chrome://browser/skin/zen-icons/back.svg", icon: "chrome://browser/skin/zen-icons/back.svg",
isAvailable: window => { isAvailable: window => {
@@ -147,7 +151,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Close Tab", l10nId: "zen-action-close-tab",
command: "cmd_close", command: "cmd_close",
icon: "chrome://browser/skin/zen-icons/close.svg", icon: "chrome://browser/skin/zen-icons/close.svg",
isAvailable: window => { isAvailable: window => {
@@ -155,27 +159,27 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Reload Tab", l10nId: "zen-action-reload-tab",
command: "Browser:Reload", command: "Browser:Reload",
icon: "chrome://browser/skin/zen-icons/reload.svg", icon: "chrome://browser/skin/zen-icons/reload.svg",
}, },
{ {
label: "Reload Tab Without Cache", l10nId: "zen-action-reload-tab-without-cache",
command: "Browser:ReloadSkipCache", command: "Browser:ReloadSkipCache",
icon: "chrome://browser/skin/zen-icons/reload.svg", icon: "chrome://browser/skin/zen-icons/reload.svg",
}, },
{ {
label: "Next Tab", l10nId: "zen-action-next-tab",
command: "Browser:NextTab", command: "Browser:NextTab",
icon: "chrome://browser/skin/zen-icons/forward.svg", icon: "chrome://browser/skin/zen-icons/forward.svg",
}, },
{ {
label: "Previous Tab", l10nId: "zen-action-previous-tab",
command: "Browser:PrevTab", command: "Browser:PrevTab",
icon: "chrome://browser/skin/zen-icons/back.svg", icon: "chrome://browser/skin/zen-icons/back.svg",
}, },
{ {
label: "Capture Screenshot", l10nId: "zen-action-capture-screenshot",
command: "Browser:Screenshot", command: "Browser:Screenshot",
icon: "chrome://browser/skin/zen-icons/screenshot.svg", icon: "chrome://browser/skin/zen-icons/screenshot.svg",
isAvailable: window => { isAvailable: window => {
@@ -183,12 +187,12 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Toggle Tabs on right", l10nId: "zen-action-toggle-tabs-on-right",
command: "cmd_zenToggleTabsOnRight", command: "cmd_zenToggleTabsOnRight",
icon: "chrome://browser/skin/zen-icons/sidebars-right.svg", icon: "chrome://browser/skin/zen-icons/sidebars-right.svg",
}, },
{ {
label: "Add to Essentials", l10nId: "zen-action-add-to-essentials",
command: window => command: window =>
window.gZenPinnedTabManager.addToEssentials(window.gBrowser.selectedTab), window.gZenPinnedTabManager.addToEssentials(window.gBrowser.selectedTab),
isAvailable: window => { isAvailable: window => {
@@ -201,7 +205,7 @@ const globalActionsTemplate = [
icon: "chrome://browser/skin/zen-icons/essential-add.svg", icon: "chrome://browser/skin/zen-icons/essential-add.svg",
}, },
{ {
label: "Remove from Essentials", l10nId: "zen-action-remove-from-essentials",
command: window => command: window =>
window.gZenPinnedTabManager.removeEssentials(window.gBrowser.selectedTab), window.gZenPinnedTabManager.removeEssentials(window.gBrowser.selectedTab),
isAvailable: window => isAvailable: window =>
@@ -209,7 +213,7 @@ const globalActionsTemplate = [
icon: "chrome://browser/skin/zen-icons/essential-remove.svg", icon: "chrome://browser/skin/zen-icons/essential-remove.svg",
}, },
{ {
label: "Find in Page", l10nId: "zen-action-find-in-page",
command: "cmd_find", command: "cmd_find",
icon: "chrome://browser/skin/zen-icons/search-page.svg", icon: "chrome://browser/skin/zen-icons/search-page.svg",
isAvailable: window => { isAvailable: window => {
@@ -217,12 +221,12 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Manage Extensions", l10nId: "zen-action-manage-extensions",
command: "Tools:Addons", command: "Tools:Addons",
icon: "chrome://browser/skin/zen-icons/extension.svg", icon: "chrome://browser/skin/zen-icons/extension.svg",
}, },
{ {
label: "Switch to Automatic Appearance", l10nId: "zen-action-switch-to-automatic-appearance",
command: () => Services.prefs.setIntPref("zen.view.window.scheme", 2), command: () => Services.prefs.setIntPref("zen.view.window.scheme", 2),
icon: "chrome://browser/skin/zen-icons/sparkles.svg", icon: "chrome://browser/skin/zen-icons/sparkles.svg",
isAvailable: () => { isAvailable: () => {
@@ -230,7 +234,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Switch to Light Mode", l10nId: "zen-action-switch-to-light-mode",
command: () => Services.prefs.setIntPref("zen.view.window.scheme", 1), command: () => Services.prefs.setIntPref("zen.view.window.scheme", 1),
icon: "chrome://browser/skin/zen-icons/face-sun.svg", icon: "chrome://browser/skin/zen-icons/face-sun.svg",
isAvailable: () => { isAvailable: () => {
@@ -238,7 +242,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Switch to Dark Mode", l10nId: "zen-action-switch-to-dark-mode",
command: () => Services.prefs.setIntPref("zen.view.window.scheme", 0), command: () => Services.prefs.setIntPref("zen.view.window.scheme", 0),
icon: "chrome://browser/skin/zen-icons/moon-stars.svg", icon: "chrome://browser/skin/zen-icons/moon-stars.svg",
isAvailable: () => { isAvailable: () => {
@@ -246,7 +250,7 @@ const globalActionsTemplate = [
}, },
}, },
{ {
label: "Print", l10nId: "zen-action-print",
command: "cmd_print", command: "cmd_print",
icon: "chrome://browser/skin/zen-icons/print.svg", icon: "chrome://browser/skin/zen-icons/print.svg",
isAvailable: window => { isAvailable: window => {
@@ -266,7 +270,10 @@ export const globalActions = globalActionsTemplate.map(action => ({
commandId: commandId:
typeof action.command === "string" typeof action.command === "string"
? action.command ? action.command
: `zen:global-action-${action.label.toLowerCase().replace(/\s+/g, "-")}`, : `zen:global-action-${action.l10nId.replace("zen-action-", "")}`,
extraPayload: {}, extraPayload: {},
...action, ...action,
get label() {
return lazy.l10n.formatValueSync(action.l10nId);
},
})); }));

View File

@@ -5,8 +5,8 @@
"binaryName": "zen", "binaryName": "zen",
"version": { "version": {
"product": "firefox", "product": "firefox",
"version": "152.0.3", "version": "152.0.6",
"candidate": "152.0.3", "candidate": "152.0.5",
"candidateBuild": 1 "candidateBuild": 1
}, },
"buildOptions": { "buildOptions": {
@@ -20,7 +20,7 @@
"brandShortName": "Zen", "brandShortName": "Zen",
"brandFullName": "Zen Browser", "brandFullName": "Zen Browser",
"release": { "release": {
"displayVersion": "1.21.4b", "displayVersion": "1.21.6b",
"github": { "github": {
"repo": "zen-browser/desktop" "repo": "zen-browser/desktop"
}, },

View File

@@ -346,6 +346,17 @@ fn expand_pref_values(prefs: &mut [Preference]) {
} }
pref.value = new_value.clone(); pref.value = new_value.clone();
} }
// Also change the condition if it contains placeholders
if let Some(condition) = &pref.condition {
let mut new_condition = condition.clone();
for (key, value) in &env_values {
let placeholder = format!("@{}@", key);
if new_condition.contains(&placeholder) {
new_condition = new_condition.replace(&placeholder, if *value { "1" } else { "0" });
}
pref.condition = Some(new_condition.clone());
}
}
} }
} }