mirror of
https://github.com/zen-browser/desktop.git
synced 2026-07-10 19:09:30 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7904f3264a | ||
|
|
dbd090a576 | ||
|
|
ddc10c7e42 |
@@ -1,12 +0,0 @@
|
|||||||
# 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,6 +32,9 @@
|
|||||||
- 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
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.spaces", type: "bool" },
|
+ { id: "services.sync.engine.workspaces", 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.spaces"
|
+ preference="services.sync.engine.workspaces"
|
||||||
+ />
|
+ />
|
||||||
+ </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.spaces">
|
+ <html:div engine_preference="services.sync.engine.workspaces">
|
||||||
+ <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,167 +0,0 @@
|
|||||||
diff --git a/gfx/webrender_bindings/WebRenderAPI.cpp b/gfx/webrender_bindings/WebRenderAPI.cpp
|
|
||||||
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
|
|
||||||
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
|
|
||||||
@@ -298,11 +298,13 @@
|
|
||||||
panic_on_gl_error, picTileWidth, picTileHeight,
|
|
||||||
gfx::gfxVars::WebRenderRequiresHardwareDriver(),
|
|
||||||
StaticPrefs::gfx_webrender_low_quality_pinch_zoom_AtStartup(),
|
|
||||||
StaticPrefs::gfx_webrender_max_shared_surface_size_AtStartup(),
|
|
||||||
StaticPrefs::gfx_webrender_enable_subpixel_aa_AtStartup(),
|
|
||||||
- compositor->ShouldUseLayerCompositor())) {
|
|
||||||
+ compositor->ShouldUseLayerCompositor(),
|
|
||||||
+ StaticPrefs::
|
|
||||||
+ gfx_webrender_opaque_backdrop_fallback_AtStartup())) {
|
|
||||||
// wr_window_new puts a message into gfxCriticalNote if it returns
|
|
||||||
// false
|
|
||||||
MOZ_ASSERT(errorMessage);
|
|
||||||
error.AssignASCII(errorMessage);
|
|
||||||
wr_api_free_error_msg(errorMessage);
|
|
||||||
diff --git a/gfx/webrender_bindings/src/bindings.rs b/gfx/webrender_bindings/src/bindings.rs
|
|
||||||
--- a/gfx/webrender_bindings/src/bindings.rs
|
|
||||||
+++ b/gfx/webrender_bindings/src/bindings.rs
|
|
||||||
@@ -1998,10 +1998,11 @@
|
|
||||||
reject_software_rasterizer: bool,
|
|
||||||
low_quality_pinch_zoom: bool,
|
|
||||||
max_shared_surface_size: i32,
|
|
||||||
enable_subpixel_aa: bool,
|
|
||||||
use_layer_compositor: bool,
|
|
||||||
+ opaque_backdrop_fallback: bool,
|
|
||||||
) -> bool {
|
|
||||||
assert!(unsafe { is_in_render_thread() });
|
|
||||||
|
|
||||||
// Ensure the WR profiler callbacks are hooked up to the Gecko profiler.
|
|
||||||
set_profiler_hooks(Some(&PROFILER_HOOKS));
|
|
||||||
@@ -2164,10 +2165,11 @@
|
|
||||||
texture_cache_config,
|
|
||||||
reject_software_rasterizer,
|
|
||||||
low_quality_pinch_zoom,
|
|
||||||
max_shared_surface_size,
|
|
||||||
enable_dithering,
|
|
||||||
+ opaque_backdrop_fallback,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let window_size = DeviceIntSize::new(window_width, window_height);
|
|
||||||
let notifier = Box::new(CppNotifier { window_id });
|
|
||||||
diff --git a/gfx/wr/webrender/src/device/gl.rs b/gfx/wr/webrender/src/device/gl.rs
|
|
||||||
--- a/gfx/wr/webrender/src/device/gl.rs
|
|
||||||
+++ b/gfx/wr/webrender/src/device/gl.rs
|
|
||||||
@@ -3982,10 +3982,14 @@
|
|
||||||
|
|
||||||
pub fn disable_color_write(&self) {
|
|
||||||
self.gl.color_mask(false, false, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
+ pub fn set_color_mask(&self, r: bool, g: bool, b: bool, a: bool) {
|
|
||||||
+ self.gl.color_mask(r, g, b, a);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
pub fn set_blend(&mut self, enable: bool) {
|
|
||||||
if enable {
|
|
||||||
self.gl.enable(gl::BLEND);
|
|
||||||
} else {
|
|
||||||
self.gl.disable(gl::BLEND);
|
|
||||||
diff --git a/gfx/wr/webrender/src/renderer/init.rs b/gfx/wr/webrender/src/renderer/init.rs
|
|
||||||
--- a/gfx/wr/webrender/src/renderer/init.rs
|
|
||||||
+++ b/gfx/wr/webrender/src/renderer/init.rs
|
|
||||||
@@ -204,6 +204,8 @@
|
|
||||||
pub low_quality_pinch_zoom: bool,
|
|
||||||
pub max_shared_surface_size: i32,
|
|
||||||
/// If true, open a debug socket to listen for remote debugger.
|
|
||||||
/// Relies on `debugger` cargo feature being enabled.
|
|
||||||
pub enable_debugger: bool,
|
|
||||||
+ /// See explanation of `gfx.webrender.opaque-backdrop-fallback`.
|
|
||||||
+ pub opaque_backdrop_fallback: bool,
|
|
||||||
}
|
|
||||||
@@ -277,9 +279,10 @@
|
|
||||||
enable_instancing: true,
|
|
||||||
reject_software_rasterizer: false,
|
|
||||||
low_quality_pinch_zoom: false,
|
|
||||||
max_shared_surface_size: 2048,
|
|
||||||
enable_debugger: true,
|
|
||||||
+ opaque_backdrop_fallback: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -802,10 +805,11 @@
|
|
||||||
allocated_native_surfaces: FastHashSet::default(),
|
|
||||||
debug_overlay_state: DebugOverlayState::new(),
|
|
||||||
buffer_damage_tracker: BufferDamageTracker::default(),
|
|
||||||
max_primitive_instance_count,
|
|
||||||
enable_instancing: options.enable_instancing,
|
|
||||||
+ opaque_backdrop_fallback: options.opaque_backdrop_fallback,
|
|
||||||
consecutive_oom_frames: 0,
|
|
||||||
target_frame_publish_id: None,
|
|
||||||
pending_result_msg: None,
|
|
||||||
layer_compositor_frame_state_in_prev_frame: None,
|
|
||||||
external_composite_debug_items: Vec::new(),
|
|
||||||
diff --git a/gfx/wr/webrender/src/renderer/mod.rs b/gfx/wr/webrender/src/renderer/mod.rs
|
|
||||||
--- a/gfx/wr/webrender/src/renderer/mod.rs
|
|
||||||
+++ b/gfx/wr/webrender/src/renderer/mod.rs
|
|
||||||
@@ -867,10 +867,12 @@
|
|
||||||
buffer_damage_tracker: BufferDamageTracker,
|
|
||||||
|
|
||||||
max_primitive_instance_count: usize,
|
|
||||||
enable_instancing: bool,
|
|
||||||
|
|
||||||
+ opaque_backdrop_fallback: bool,
|
|
||||||
+
|
|
||||||
/// Count consecutive oom frames to detectif we are stuck unable to render
|
|
||||||
/// in a loop.
|
|
||||||
consecutive_oom_frames: u32,
|
|
||||||
|
|
||||||
/// update() defers processing of ResultMsg, if frame_publish_id of
|
|
||||||
@@ -2787,18 +2789,29 @@
|
|
||||||
let read_target = ReadTarget::from_texture(cache_texture);
|
|
||||||
|
|
||||||
// Should always be drawing to picture cache tiles or off-screen surface!
|
|
||||||
debug_assert!(!draw_target.is_default());
|
|
||||||
let device_to_framebuffer = Scale::new(1i32);
|
|
||||||
+ let dest_fb_rect = dest * device_to_framebuffer;
|
|
||||||
|
|
||||||
self.device.blit_render_target(
|
|
||||||
read_target,
|
|
||||||
src * device_to_framebuffer,
|
|
||||||
draw_target,
|
|
||||||
- dest * device_to_framebuffer,
|
|
||||||
+ dest_fb_rect,
|
|
||||||
TextureFilter::Linear,
|
|
||||||
);
|
|
||||||
+
|
|
||||||
+ if self.opaque_backdrop_fallback {
|
|
||||||
+ self.device.set_color_mask(false, false, false, true);
|
|
||||||
+ self.device.clear_target(
|
|
||||||
+ Some([0.0, 0.0, 0.0, 1.0]),
|
|
||||||
+ None,
|
|
||||||
+ Some(dest_fb_rect),
|
|
||||||
+ );
|
|
||||||
+ self.device.set_color_mask(true, true, true, true);
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_picture_cache_target(
|
|
||||||
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
|
|
||||||
--- a/modules/libpref/init/StaticPrefList.yaml
|
|
||||||
+++ b/modules/libpref/init/StaticPrefList.yaml
|
|
||||||
@@ -8439,10 +8439,17 @@
|
|
||||||
#else
|
|
||||||
value: false
|
|
||||||
#endif
|
|
||||||
mirror: once
|
|
||||||
|
|
||||||
+# Make backdrop-filter treat its captured backdrop as if it had been
|
|
||||||
+# composited over an opaque-black background. (See bug 2036640)
|
|
||||||
+- name: gfx.webrender.opaque-backdrop-fallback
|
|
||||||
+ type: bool
|
|
||||||
+ value: true
|
|
||||||
+ mirror: once
|
|
||||||
+
|
|
||||||
# Disable wait of GPU execution completion
|
|
||||||
- name: gfx.webrender.wait-gpu-finished.disabled
|
|
||||||
type: bool
|
|
||||||
value: false
|
|
||||||
mirror: once
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -20,5 +20,4 @@ DIRS += [
|
|||||||
"share",
|
"share",
|
||||||
"spaces",
|
"spaces",
|
||||||
"space-routing",
|
"space-routing",
|
||||||
"sync",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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",
|
||||||
@@ -610,7 +609,6 @@ 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;
|
||||||
@@ -624,15 +622,6 @@ 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
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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", () => {
|
||||||
@@ -149,7 +148,7 @@ class nsZenWorkspaces {
|
|||||||
if (!this.privateWindowOrDisabled) {
|
if (!this.privateWindowOrDisabled) {
|
||||||
const observerFunction = async () => {
|
const observerFunction = async () => {
|
||||||
delete this._workspaceBookmarksCache;
|
delete this._workspaceBookmarksCache;
|
||||||
await this.#initializeWorkspaceBookmarks();
|
await this.workspaceBookmarks();
|
||||||
this._invalidateBookmarkContainers();
|
this._invalidateBookmarkContainers();
|
||||||
};
|
};
|
||||||
Services.obs.addObserver(observerFunction, "workspace-bookmarks-updated");
|
Services.obs.addObserver(observerFunction, "workspace-bookmarks-updated");
|
||||||
@@ -169,63 +168,6 @@ 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(() => {
|
||||||
@@ -749,17 +691,17 @@ class nsZenWorkspaces {
|
|||||||
return spacesForSS;
|
return spacesForSS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #initializeWorkspaceBookmarks() {
|
async workspaceBookmarks() {
|
||||||
if (this.privateWindowOrDisabled) {
|
if (this.privateWindowOrDisabled) {
|
||||||
this._workspaceBookmarksCache = {
|
this._workspaceBookmarksCache = {
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
lastChangeTimestamp: 0,
|
lastChangeTimestamp: 0,
|
||||||
};
|
};
|
||||||
return;
|
return this._workspaceBookmarksCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._workspaceBookmarksCache) {
|
if (this._workspaceBookmarksCache) {
|
||||||
return;
|
return this._workspaceBookmarksCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [bookmarks, lastChangeTimestamp] = await Promise.all([
|
const [bookmarks, lastChangeTimestamp] = await Promise.all([
|
||||||
@@ -768,6 +710,8 @@ class nsZenWorkspaces {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
|
this._workspaceBookmarksCache = { bookmarks, lastChangeTimestamp };
|
||||||
|
|
||||||
|
return this._workspaceCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreWorkspacesFromSessionStore(aWinData = {}) {
|
restoreWorkspacesFromSessionStore(aWinData = {}) {
|
||||||
@@ -827,7 +771,7 @@ class nsZenWorkspaces {
|
|||||||
return (async () => {
|
return (async () => {
|
||||||
await this.#waitForPromises();
|
await this.#waitForPromises();
|
||||||
this.#afterLoadInit();
|
this.#afterLoadInit();
|
||||||
await this.#initializeWorkspaceBookmarks();
|
await this.workspaceBookmarks();
|
||||||
await this.changeWorkspace(activeWorkspace, { onInit: true });
|
await this.changeWorkspace(activeWorkspace, { onInit: true });
|
||||||
this.#fixTabPositions();
|
this.#fixTabPositions();
|
||||||
this.onWindowResize();
|
this.onWindowResize();
|
||||||
@@ -853,13 +797,6 @@ 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;
|
||||||
@@ -1277,19 +1214,12 @@ 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(
|
||||||
@@ -1421,50 +1351,30 @@ class nsZenWorkspaces {
|
|||||||
if (this.privateWindowOrDisabled) {
|
if (this.privateWindowOrDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const workspaces = this.getWorkspaces();
|
const workspaces = this._workspaceCache;
|
||||||
// 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._workspaceCache = workspaces;
|
this.#propagateWorkspaceData();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1864,11 +1774,6 @@ class nsZenWorkspaces {
|
|||||||
}
|
}
|
||||||
const newTransform = diff * 100;
|
const newTransform = diff * 100;
|
||||||
element.style.transform = `translateX(${newTransform + offsetPixels / 2}%)`;
|
element.style.transform = `translateX(${newTransform + offsetPixels / 2}%)`;
|
||||||
// A special case for two spaces
|
|
||||||
if (spaceLen === 2 && diff !== 0) {
|
|
||||||
const side = offsetPixels > 0 ? -100 : 100;
|
|
||||||
element.style.transform = `translateX(${side + offsetPixels / 2}%)`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Hide other essentials with different containerTabId
|
// Hide other essentials with different containerTabId
|
||||||
for (const container of otherContainersEssentials) {
|
for (const container of otherContainersEssentials) {
|
||||||
@@ -2099,12 +2004,6 @@ class nsZenWorkspaces {
|
|||||||
offset += spaceLen;
|
offset += spaceLen;
|
||||||
}
|
}
|
||||||
offset = offset * 100;
|
offset = offset * 100;
|
||||||
// A special case for two spaces
|
|
||||||
if (spaceLen === 2 && offset !== 0) {
|
|
||||||
const currentTransform =
|
|
||||||
parseFloat(element.style.transform.split("(")[1]) || 0;
|
|
||||||
offset = currentTransform >= 0 ? 100 : -100;
|
|
||||||
}
|
|
||||||
const newTransform = `translateX(${offset}%)`;
|
const newTransform = `translateX(${offset}%)`;
|
||||||
// Only animate the workspace that is coming in, to avoid having multiple workspaces
|
// Only animate the workspace that is coming in, to avoid having multiple workspaces
|
||||||
// animating off-screen at the same time which can cause performance issues. With an off
|
// animating off-screen at the same time which can cause performance issues. With an off
|
||||||
@@ -2181,17 +2080,6 @@ class nsZenWorkspaces {
|
|||||||
existingOffset = currentTransform || (isGoingLeft ? -100 : 100);
|
existingOffset = currentTransform || (isGoingLeft ? -100 : 100);
|
||||||
newOffset = 0;
|
newOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spaceLen === 2) {
|
|
||||||
if (containsPrev && !containsNew) {
|
|
||||||
existingOffset = currentTransform;
|
|
||||||
newOffset = currentTransform >= 0 ? 100 : -100;
|
|
||||||
} else {
|
|
||||||
existingOffset = currentTransform >= 0 ? 100 : -100;
|
|
||||||
newOffset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newTransform = `translateX(${newOffset}%)`;
|
const newTransform = `translateX(${newOffset}%)`;
|
||||||
let existingTransform = `translateX(${existingOffset}%)`;
|
let existingTransform = `translateX(${existingOffset}%)`;
|
||||||
if (shouldAnimate) {
|
if (shouldAnimate) {
|
||||||
@@ -2450,7 +2338,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-essential")
|
!tab.hasAttribute("zen-workspace-id")
|
||||||
) {
|
) {
|
||||||
tab.setAttribute("zen-workspace-id", workspace.uuid);
|
tab.setAttribute("zen-workspace-id", workspace.uuid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
/* 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();
|
|
||||||
@@ -1,404 +0,0 @@
|
|||||||
/* 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.ZenWorkspaces";
|
|
||||||
}
|
|
||||||
|
|
||||||
ZenWorkspacesRecord.prototype.type = "workspaces";
|
|
||||||
|
|
||||||
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 Workspaces 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 "Workspaces";
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(service) {
|
|
||||||
super("Workspaces", 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
# 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",
|
|
||||||
]
|
|
||||||
@@ -346,17 +346,6 @@ 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