diff --git a/src/js/background/sync.js b/src/js/background/sync.js index fc6d875..98f6af0 100644 --- a/src/js/background/sync.js +++ b/src/js/background/sync.js @@ -128,13 +128,12 @@ const sync = { await sync.checkForListenersMaybeAdd(); async function updateSyncIdentities() { - const { syncExcludeRegExp } = await browser.storage.local.get("syncExcludeRegExp"); - const excludeRegExp = new RegExp(syncExcludeRegExp, "i"); + const excludeRegExp = await getExcludeRegExp(); const identities = await browser.contextualIdentities.query({}); for (const identity of identities) { // skip excluded identities - if (identity.name.match(excludeRegExp)) { + if (excludeRegExpMatchesIdentity(excludeRegExp, identity)) { continue; } @@ -334,6 +333,7 @@ async function restore() { */ async function reconcileIdentities(){ if (SYNC_DEBUG) console.log("reconcileIdentities"); + const excludeRegExp = await getExcludeRegExp(); // first delete any from the deleted list const deletedIdentityList = @@ -343,6 +343,14 @@ async function reconcileIdentities(){ const deletedCookieStoreId = await identityState.lookupCookieStoreId(deletedUUID); if (deletedCookieStoreId){ + if (excludeRegExp) { + const deletedIdentity = await identityState.get(deletedCookieStoreId); + // skip excluded identities + if (excludeRegExpMatchesIdentity(excludeRegExp, deletedIdentity)) { + continue; + } + } + try{ await browser.contextualIdentities.remove(deletedCookieStoreId); } catch (error) { @@ -357,6 +365,11 @@ async function reconcileIdentities(){ await sync.storageArea.getIdentities(); // find any local dupes created on sync storage and delete from sync storage for (const localIdentity of localIdentities) { + // skip excluded identities + if (excludeRegExpMatchesIdentity(excludeRegExp, localIdentity)) { + continue; + } + const syncIdentitiesOfName = syncIdentitiesRemoveDupes .filter(identity => identity.name === localIdentity.name); if (syncIdentitiesOfName.length > 1) { @@ -370,6 +383,11 @@ async function reconcileIdentities(){ await sync.storageArea.getIdentities(); // now compare all containers for matching names. for (const syncIdentity of syncIdentities) { + // skip excluded identities + if (excludeRegExpMatchesIdentity(excludeRegExp, syncIdentity)) { + continue; + } + if (syncIdentity.macAddonUUID){ const localMatch = localIdentities.find( localIdentity => localIdentity.name === syncIdentity.name @@ -585,3 +603,24 @@ async function setAssignmentWithUUID(assignedSite, urlKey) { } throw new Error (`No cookieStoreId found for: ${uuid}, ${urlKey}`); } + +// Retrieve the sync exclude regexp from local storage. +async function getExcludeRegExp() { + const { syncExcludeRegExp } = await browser.storage.local.get("syncExcludeRegExp"); + if (syncExcludeRegExp) { + return new RegExp(syncExcludeRegExp, "i"); + } else { + return false; + } +} + +// Matching the provided exclude regexp against the provided identity and return +// true if they match. +function excludeRegExpMatchesIdentity(excludeRegExp, identity) { + if (excludeRegExp && identity.name.match(excludeRegExp)) { + if (SYNC_DEBUG) console.log(`Exclude regexp matches identity '${identity.name}'`); + return true; + } else { + return false; + } +} diff --git a/test/features/sync.test.js b/test/features/sync.test.js index 1e4070a..d3273a0 100644 --- a/test/features/sync.test.js +++ b/test/features/sync.test.js @@ -187,6 +187,50 @@ describe("Sync", function() { this.syncHelper.lookupIdentityBy(identities, {name: "Mozilla"}); (mozillaContainer.icon === "pet").should.be.true; }); + + it("excludeEmptyTest", async function() { + await this.syncHelper.stopSyncListeners(); + await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, []); + await this.syncHelper.unsetExcludeRegexp(); + + await this.webExt.background.window.sync.runSync(); + + // We excluded nothing, so all identities should be part of the sync storage. + const identities = await this.syncHelper.getIdentitiesFromSyncStorage(); + TEST_CONTAINERS.length.should.equal(identities.length); + }); + + it("excludeOutgoingTest", async function() { + await this.syncHelper.stopSyncListeners(); + await this.syncHelper.setState({}, LOCAL_DATA, TEST_CONTAINERS, []); + + // Our outgoing container is the last test container. + const outgoingContainerName = TEST_CONTAINERS.at(-1).name; + await this.syncHelper.setExcludeRegexp(`^${outgoingContainerName}$`); + + await this.webExt.background.window.sync.runSync(); + + // We excluded an outgoing container, so its identity should not be part of the sync storage. + const identities = await this.syncHelper.getIdentitiesFromSyncStorage(); + this.syncHelper.lookupIdentityBy(identities, {name: outgoingContainerName}).should.be.false; + }); + + it("excludeIncomingTest", async function() { + await this.syncHelper.stopSyncListeners(); + await this.syncHelper.setState(SYNC_DATA, LOCAL_DATA, TEST_CONTAINERS, []); + + // Our incoming container is the first that's in the sync storage but not in the local storage + const incomingContainerName = Object.values(SYNC_DATA).find((sync_container) => + !TEST_CONTAINERS.find((local_container) => sync_container.name === local_container.name) + ).name; + await this.syncHelper.setExcludeRegexp(`^${incomingContainerName}$`); + + await this.webExt.background.window.sync.runSync(); + + // We excluded an incoming container, so its identity should not be part of the local storage. + const identities = await this.webExt.browser.contextualIdentities.query({}); + this.syncHelper.lookupIdentityBy(identities, {name: incomingContainerName}).should.be.false; + }); }); class SyncTestHelper { @@ -224,7 +268,15 @@ class SyncTestHelper { } return; } + + async setExcludeRegexp(syncExcludeRegExp) { + await this.webExt.browser.storage.local.set({syncExcludeRegExp}); + } + async unsetExcludeRegexp() { + await this.webExt.browser.storage.local.remove("syncExcludeRegExp"); + } + async removeAllContainers() { const identities = await this.webExt.browser.contextualIdentities.query({}); for (const identity of identities) { @@ -232,6 +284,14 @@ class SyncTestHelper { } } + async getIdentitiesFromSyncStorage() { + const storage = await this.webExt.browser.storage.sync.get(); + const identities = Object.fromEntries( + Object.entries(storage).filter(([key]) => key.startsWith("identity@@")) + ); + return Object.values(identities); + } + lookupIdentityBy(identities, options) { for (const identity of identities) { if (options && options.name) {