mirror of
https://github.com/zen-browser/desktop.git
synced 2026-07-10 02:49:33 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61e0790144 | ||
|
|
03f251ff4a | ||
|
|
5e17ef46ca | ||
|
|
decd9548dd | ||
|
|
27d290c77d | ||
|
|
5ecbc13eb6 | ||
|
|
58611690b5 | ||
|
|
68c8fe18bc | ||
|
|
3a72290f16 | ||
|
|
79d3d6a993 | ||
|
|
e9c35b0f92 | ||
|
|
647e219639 |
@@ -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.4`! 🚀
|
- [`Release`](https://zen-browser.app/download) - Is currently built using Firefox version `152.0.5`! 🚀
|
||||||
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.4`!
|
- [`Twilight`](https://zen-browser.app/download?twilight) - Is currently built using Firefox version `RC 152.0.5`!
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3985a970489e32fc751e1b80abf4d5534c905e45
|
6bd10168f81fb90f878132d87cf681200afbcd91
|
||||||
@@ -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
|
||||||
|
|||||||
36
locales/en-US/browser/browser/zen-command-palette.ftl
Normal file
36
locales/en-US/browser/browser/zen-command-palette.ftl
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
12
prefs/zen/sync.yaml
Normal file
12
prefs/zen/sync.yaml
Normal 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@"
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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" },
|
||||||
|
];
|
||||||
|
|
||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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..209c0ddc0297adb8340180c58de07ae107790751 100644
|
index 08b5b56e069d038d72c87355920c4ce8a55ed805..e1a3a18081c7a8153f8d12c187bf4dd522944fcb 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 @@
|
||||||
@@ -308,7 +308,7 @@ index 08b5b56e069d038d72c87355920c4ce8a55ed805..209c0ddc0297adb8340180c58de07ae1
|
|||||||
+
|
+
|
||||||
+ 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) {
|
||||||
|
|||||||
@@ -484,11 +484,10 @@ groupbox h2 {
|
|||||||
#helpButton,
|
#helpButton,
|
||||||
#support-firefox,
|
#support-firefox,
|
||||||
#tabGroupSuggestions,
|
#tabGroupSuggestions,
|
||||||
#web-appearance-manage-themes-link,
|
|
||||||
#setting-control-sidebarChatbotFieldset,
|
#setting-control-sidebarChatbotFieldset,
|
||||||
#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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
src/services/sync/modules/service-sys-mjs.patch
Normal file
15
src/services/sync/modules/service-sys-mjs.patch
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1551,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) &&
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ DIRS += [
|
|||||||
"share",
|
"share",
|
||||||
"spaces",
|
"spaces",
|
||||||
"space-routing",
|
"space-routing",
|
||||||
|
"sync",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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");
|
||||||
@@ -691,17 +749,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 +768,6 @@ class nsZenWorkspaces {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
|
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
|
||||||
|
|
||||||
return this._workspaceCache;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreWorkspacesFromSessionStore(aWinData = {}) {
|
restoreWorkspacesFromSessionStore(aWinData = {}) {
|
||||||
@@ -771,7 +827,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 +853,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;
|
||||||
@@ -1214,12 +1277,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 +1421,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 +2017,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 +2137,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 +2207,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 +2428,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 +3087,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);
|
||||||
|
|||||||
160
src/zen/sync/ZenSyncManager.sys.mjs
Normal file
160
src/zen/sync/ZenSyncManager.sys.mjs
Normal 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();
|
||||||
404
src/zen/sync/ZenWorkspacesSync.sys.mjs
Normal file
404
src/zen/sync/ZenWorkspacesSync.sys.mjs
Normal 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
8
src/zen/sync/moz.build
Normal 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",
|
||||||
|
]
|
||||||
@@ -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"],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"binaryName": "zen",
|
"binaryName": "zen",
|
||||||
"version": {
|
"version": {
|
||||||
"product": "firefox",
|
"product": "firefox",
|
||||||
"version": "152.0.4",
|
"version": "152.0.5",
|
||||||
"candidate": "152.0.4",
|
"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.5b",
|
"displayVersion": "1.21.6b",
|
||||||
"github": {
|
"github": {
|
||||||
"repo": "zen-browser/desktop"
|
"repo": "zen-browser/desktop"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user