diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index 096ebf1..5c3f33e 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -9,8 +9,9 @@ const assignManager = { area: browser.storage.local, exemptedTabs: {}, - getSiteStoreKey(pageUrl) { - const url = new window.URL(pageUrl); + getSiteStoreKey(pageUrlorUrlKey) { + if (pageUrlorUrlKey.includes("siteContainerMap@@_")) return pageUrlorUrlKey; + const url = new window.URL(pageUrlorUrlKey); const storagePrefix = "siteContainerMap@@_"; if (url.port === "80" || url.port === "443") { return `${storagePrefix}${url.hostname}`; @@ -19,29 +20,29 @@ const assignManager = { } }, - setExempted(pageUrl, tabId) { - const siteStoreKey = this.getSiteStoreKey(pageUrl); + setExempted(pageUrlorUrlKey, tabId) { + const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey); if (!(siteStoreKey in this.exemptedTabs)) { this.exemptedTabs[siteStoreKey] = []; } this.exemptedTabs[siteStoreKey].push(tabId); }, - removeExempted(pageUrl) { - const siteStoreKey = this.getSiteStoreKey(pageUrl); + removeExempted(pageUrlorUrlKey) { + const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey); this.exemptedTabs[siteStoreKey] = []; }, - isExempted(pageUrl, tabId) { - const siteStoreKey = this.getSiteStoreKey(pageUrl); + isExempted(pageUrlorUrlKey, tabId) { + const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey); if (!(siteStoreKey in this.exemptedTabs)) { return false; } return this.exemptedTabs[siteStoreKey].includes(tabId); }, - get(pageUrl) { - const siteStoreKey = this.getSiteStoreKey(pageUrl); + get(pageUrlorUrlKey) { + const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey); return this.getByUrlKey(siteStoreKey); }, @@ -58,24 +59,26 @@ const assignManager = { }); }, - async set(pageUrl, data, exemptedTabIds) { - const siteStoreKey = this.getSiteStoreKey(pageUrl); + async set(pageUrlorUrlKey, data, exemptedTabIds, backup = true) { + const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey); if (exemptedTabIds) { exemptedTabIds.forEach((tabId) => { - this.setExempted(pageUrl, tabId); + this.setExempted(pageUrlorUrlKey, tabId); }); } + data.identityMacAddonUUID = + await identityState.lookupMACaddonUUID(data.userContextId); await this.area.set({ [siteStoreKey]: data }); - await sync.storageArea.backup({undelete: siteStoreKey}); + if (backup) await sync.storageArea.backup({undelete: siteStoreKey}); return; }, - async remove(pageUrl) { - const siteStoreKey = this.getSiteStoreKey(pageUrl); + async remove(pageUrlorUrlKey) { + const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey); // When we remove an assignment we should clear all the exemptions - this.removeExempted(pageUrl); + this.removeExempted(pageUrlorUrlKey); await this.area.remove([siteStoreKey]); await sync.storageArea.backup({siteStoreKey}); return; @@ -108,12 +111,14 @@ const assignManager = { return sites; }, + // TODOkmw: Change site assignment UUID + /* * Looks for abandoned site assignments. If there is no identity with * the site assignment's userContextId (cookieStoreId), then the assignment * is removed. */ - async cleanup() { + async upgradeData() { const identitiesList = await browser.contextualIdentities.query({}); const macConfigs = await this.area.get(); for(const configKey of Object.keys(macConfigs)) { @@ -127,6 +132,15 @@ const assignManager = { await this.remove(configKey.replace(/^siteContainerMap@@_/, "https://")); continue; } + const updatedSideAssignment = macConfigs[configKey]; + if (!macConfigs[configKey].identityMacAddonUUID) { + await this.set( + configKey.replace(/^siteContainerMap@@_/, "https://"), + updatedSideAssignment, + false, + false + ); + } } } diff --git a/src/js/background/identityState.js b/src/js/background/identityState.js index f4fb282..d260ca5 100644 --- a/src/js/background/identityState.js +++ b/src/js/background/identityState.js @@ -41,11 +41,11 @@ const identityState = { }, /* - * Looks for abandoned identities keys in local storage, and makes sure all + * Looks for abandoned identity keys in local storage, and makes sure all * identities registered in the browser are also in local storage. (this * appears to not always be the case based on how this.get() is written) */ - async cleanup() { + async upgradeData() { const identitiesList = await browser.contextualIdentities.query({}); const macConfigs = await this.area.get(); for(const configKey of Object.keys(macConfigs)) { @@ -120,10 +120,13 @@ const identityState = { }, async lookupMACaddonUUID(cookieStoreId) { - console.log(cookieStoreId); + // This stays a lookup, because if the cookieStoreId doesn't + // exist, this.get() will create it, which is not what we want. + const cookieStoreIdKey = cookieStoreId.includes("firefox-container-") ? + cookieStoreId : "firefox-container-" + cookieStoreId; const macConfigs = await this.storageArea.area.get(); for(const configKey of Object.keys(macConfigs)) { - if (configKey === "identitiesState@@_" + cookieStoreId) { + if (configKey === "identitiesState@@_" + cookieStoreIdKey) { return macConfigs[configKey].macAddonUUID; } } diff --git a/src/js/background/sync.js b/src/js/background/sync.js index 5a418eb..f4959eb 100644 --- a/src/js/background/sync.js +++ b/src/js/background/sync.js @@ -150,7 +150,7 @@ const sync = { async errorHandledRunSync () { sync.runSync().catch(async (error)=> { console.error("Error from runSync", error); - sync.checkForListenersMaybeAdd(); + await sync.checkForListenersMaybeAdd(); }); }, @@ -161,13 +161,13 @@ const sync = { ); const hasCIListener = await hasContextualIdentityListeners(); - + if (!hasCIListener) { - addContextualIdentityListeners(); + await addContextualIdentityListeners(); } if (!hasStorageListener) { - browser.storage.onChanged.addListener( + await browser.storage.onChanged.addListener( sync.storageArea.onChangedListener); } }, @@ -177,15 +177,15 @@ const sync = { await browser.storage.onChanged.hasListener( sync.storageArea.onChangedListener ); - + const hasCIListener = await hasContextualIdentityListeners(); if (hasCIListener) { - removeContextualIdentityListeners(); + await removeContextualIdentityListeners(); } if (hasStorageListener) { - browser.storage.onChanged.removeListener( + await browser.storage.onChanged.removeListener( sync.storageArea.onChangedListener); } }, @@ -200,8 +200,8 @@ const sync = { await sync.checkForListenersMaybeRemove(); console.log("runSync"); - await identityState.storageArea.cleanup(); - await assignManager.storageArea.cleanup(); + await identityState.storageArea.upgradeData(); + await assignManager.storageArea.upgradeData(); const hasSyncStorage = await sync.storageArea.hasSyncStorage(); const dataIsReliable = await sync.storageArea.dataIsReliable(); @@ -275,6 +275,15 @@ async function reconcileIdentities(){ } // if no macAddonUUID, there is a problem with the sync info and it needs to be ignored. } + + await updateSiteAssignmentUUIDs(); + + async function updateSiteAssignmentUUIDs(){ + const sites = assignManager.storageArea.getAssignedSites(); + for (const siteKey of Object.keys(sites)) { + await assignManager.storageArea.set(siteKey, sites[siteKey]); + } + } } async function ifNamesMatch(syncIdentity, localMatch) { @@ -302,10 +311,12 @@ async function ifNamesMatch(syncIdentity, localMatch) { localMatch.cookieStoreId, syncIdentity.macAddonUUID ); + + // TODOkmw: update any site assignment UUIDs } async function ifUUIDMatch(syncIdentity, localCookieStoreID) { - // if there's a local uuid, it's the same container. Sync is truth + // if there's an identical local uuid, it's the same container. Sync is truth const identityInfo = { name: syncIdentity.name, color: syncIdentity.color, @@ -376,63 +387,46 @@ async function reconcileSiteAssignments() { .remove(siteStoreKey.replace(/^siteContainerMap@@_/, "https://")); } } - const cookieStoreIDmap = - await sync.storageArea.getCookieStoreIDMap(); for(const urlKey of Object.keys(assignedSitesFromSync)) { const assignedSite = assignedSitesFromSync[urlKey]; try{ - const syncUUID = - await lookupSyncSiteAssigmentIdentityUUID( - assignedSite, cookieStoreIDmap, urlKey - ); - if (syncUUID) { + const syncUUID = assignedSite.identityMacAddonUUID; + console.log("syncUUID, ", syncUUID); + if (assignedSite.identityMacAddonUUID) { // Sync is truth. // Not even looking it up. Just overwrite - console.log("new assignment ", assignedSite, ": ", - assignedSite.userContextId); - const newUUID = cookieStoreIDmap[ - "firefox-container-" + assignedSite.userContextId - ]; - - await setAssignmentWithUUID(newUUID, assignedSite, urlKey); + if (SYNC_DEBUG){ + const isInStorage = await assignManager.storageArea.getByUrlKey(urlKey); + if (!isInStorage) + console.log("new assignment ", assignedSite); + } + await setAssignmentWithUUID(assignedSite, urlKey); continue; } - } catch (error) { // this is probably old or incorrect site info in Sync // skip and move on. - //console.error("Error assigning site", error); - } } - - async function lookupSyncSiteAssigmentIdentityUUID( - assignedSite, - cookieStoreIDmap, - urlKey - ){ - const syncCookieStoreId = - "firefox-container-" + assignedSite.userContextId; - const uuid = cookieStoreIDmap[syncCookieStoreId]; - if (!uuid) throw new Error (`No syncUUID found for : ${urlKey}`); - return uuid; - } } -async function setAssignmentWithUUID (newUUID, assignedSite, urlKey) { - const cookieStoreId = await identityState.lookupCookieStoreId(newUUID); +async function setAssignmentWithUUID (assignedSite, urlKey) { + const uuid = assignedSite.identityMacAddonUUID; + const cookieStoreId = await identityState.lookupCookieStoreId(uuid); if (cookieStoreId) { assignedSite.userContextId = cookieStoreId .replace(/^firefox-container-/, ""); await assignManager.storageArea.set( urlKey.replace(/^siteContainerMap@@_/, "https://"), - assignedSite + assignedSite, + false, + false ); return; } - throw new Error (`No cookieStoreId found for: ${newUUID}, ${urlKey}`); + throw new Error (`No cookieStoreId found for: ${uuid}, ${urlKey}`); } const syncCIListenerList = [ @@ -441,18 +435,18 @@ const syncCIListenerList = [ sync.storageArea.backup ]; -function addContextualIdentityListeners(listenerList) { +async function addContextualIdentityListeners(listenerList) { if(!listenerList) listenerList = syncCIListenerList; - browser.contextualIdentities.onCreated.addListener(listenerList[0]); - browser.contextualIdentities.onRemoved.addListener(listenerList[1]); - browser.contextualIdentities.onUpdated.addListener(listenerList[2]); + await browser.contextualIdentities.onCreated.addListener(listenerList[0]); + await browser.contextualIdentities.onRemoved.addListener(listenerList[1]); + await browser.contextualIdentities.onUpdated.addListener(listenerList[2]); } -function removeContextualIdentityListeners(listenerList) { +async function removeContextualIdentityListeners(listenerList) { if(!listenerList) listenerList = syncCIListenerList; - browser.contextualIdentities.onCreated.removeListener(listenerList[0]); - browser.contextualIdentities.onRemoved.removeListener(listenerList[1]); - browser.contextualIdentities.onUpdated.removeListener(listenerList[2]); + await browser.contextualIdentities.onCreated.removeListener(listenerList[0]); + await browser.contextualIdentities.onRemoved.removeListener(listenerList[1]); + await browser.contextualIdentities.onUpdated.removeListener(listenerList[2]); } async function hasContextualIdentityListeners(listenerList) { diff --git a/src/js/background/test.js b/src/js/background/test.js index 1634f3f..0f1c58f 100644 --- a/src/js/background/test.js +++ b/src/js/background/test.js @@ -1,6 +1,7 @@ browser.tests = { async runAll() { await this.testIdentityStateCleanup(); + await this.testAssignManagerCleanup(); await this.testReconcileSiteAssignments(); await this.testInitialSync(); await this.test2(); @@ -28,7 +29,7 @@ browser.tests = { }); console.log("local storage set: ", await browser.storage.local.get()); - await identityState.storageArea.cleanup(); + await identityState.storageArea.upgradeData(); const macConfigs = await browser.storage.local.get(); const identities = []; @@ -42,71 +43,106 @@ browser.tests = { for (const identity of identities) { console.assert(!!identity.macAddonUUID, `${identity.name} should have a uuid`); } - console.log("Finished!"); + console.log("!!!Finished!!!"); }, async testAssignManagerCleanup() { await browser.tests.stopSyncListeners(); console.log("Testing the cleanup of local storage"); - await this.setState({}, LOCAL_DATA, TEST_CONTAINERS, []); + await this.setState({}, LOCAL_DATA, TEST_CONTAINERS, TEST_ASSIGNMENTS); await browser.storage.local.set({ "siteContainerMap@@_www.goop.com": { - "userContextId": "5", + "userContextId": "6", "neverAsk": true, "hostname": "www.goop.com" } }); console.log("local storage set: ", await browser.storage.local.get()); - await assignManager.storageArea.cleanup(); - - await browser.storage.local.set({ - "identitiesState@@_firefox-container-5": { - "hiddenTabs": [] - } - }); + await identityState.storageArea.upgradeData(); + await assignManager.storageArea.upgradeData(); const macConfigs = await browser.storage.local.get(); const assignments = []; for(const configKey of Object.keys(macConfigs)) { if (configKey.includes("siteContainerMap@@_")) { - assignments.push(configKey); + macConfigs[configKey].configKey = configKey; + assignments.push(macConfigs[configKey]); } } - console.assert(assignments.length === 0, "There should be 0 identity entries"); - - console.log("Finished!"); + console.assert(assignments.length === 5, "There should be 5 identity entries"); + for (const assignment of assignments) { + console.log(assignment); + console.assert(!!assignment.identityMacAddonUUID, `${assignment.configKey} should have a uuid`); + } + console.log("!!!Finished!!!"); }, async testReconcileSiteAssignments() { await browser.tests.stopSyncListeners(); console.log("Testing reconciling Site Assignments"); - const newSyncData = DUPE_TEST_SYNC; - // add some bad data. - newSyncData.assignedSites["siteContainerMap@@_www.linkedin.com"] = { - "userContextId": "1", - "neverAsk": true, - "hostname": "www.linkedin.com" - }; - - newSyncData.deletedSiteList = ["siteContainerMap@@_www.google.com"]; - await this.setState( - newSyncData, + DUPE_TEST_SYNC, LOCAL_DATA, TEST_CONTAINERS, SITE_ASSIGNMENT_TEST ); + // add 200ok (bad data). + await browser.storage.sync.set({ + "assignedSites": { + "siteContainerMap@@_developer.mozilla.org": { + "userContextId": "588", + "neverAsk": true, + "identityMacAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e", + "hostname": "developer.mozilla.org" + }, + "siteContainerMap@@_reddit.com": { + "userContextId": "592", + "neverAsk": true, + "identityMacAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7", + "hostname": "reddit.com" + }, + "siteContainerMap@@_twitter.com": { + "userContextId": "589", + "neverAsk": true, + "identityMacAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187", + "hostname": "twitter.com" + }, + "siteContainerMap@@_www.facebook.com": { + "userContextId": "590", + "neverAsk": true, + "identityMacAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4", + "hostname": "www.facebook.com" + }, + "siteContainerMap@@_www.linkedin.com": { + "userContextId": "591", + "neverAsk": true, + "identityMacAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1", + "hostname": "www.linkedin.com" + }, + "siteContainerMap@@_200ok.us": { + "userContextId": "1", + "neverAsk": true, + "identityMacAddonUUID": "b5f5f794-b37e-4cec-9f4e-6490df620336", + "hostname": "www.linkedin.com" + } + } + }); + + await browser.storage.sync.set({ + deletedSiteList: ["siteContainerMap@@_www.google.com"] + }); + await sync.runSync(); const assignedSites = await assignManager.storageArea.getAssignedSites(); - console.assert(Object.keys(assignedSites).length === 5, "There should be 5 assigned sites"); - console.log("Finished!"); + console.assert(Object.keys(assignedSites).length === 6, "There should be 6 assigned sites"); + console.log("!!!Finished!!!"); }, async testInitialSync() { @@ -121,19 +157,12 @@ browser.tests = { const getAssignedSites = await assignManager.storageArea.getAssignedSites(); const identities = await browser.contextualIdentities.query({}); - const localCookieStoreIDmap = - await identityState.getCookieStoreIDuuidMap(); console.assert( Object.keys(getSync.cookieStoreIDmap).length === 5, "cookieStoreIDmap should have 5 entries" ); - console.assert( - Object.keys(localCookieStoreIDmap).length === 6, - "localCookieStoreIDmap should have 6 entries" - ); - console.assert( identities.length === 5, "There should be 5 identities" @@ -143,7 +172,7 @@ browser.tests = { Object.keys(getAssignedSites).length === 0, "There should be no site assignments" ); - console.log("Finished!"); + console.log("!!!Finished!!!"); }, async test2() { @@ -160,19 +189,11 @@ browser.tests = { const identities = await browser.contextualIdentities.query({}); - const localCookieStoreIDmap = - await identityState.getCookieStoreIDuuidMap(); - console.assert( Object.keys(getSync.cookieStoreIDmap).length === 6, "cookieStoreIDmap should have 6 entries" ); - console.assert( - Object.keys(localCookieStoreIDmap).length === 7, - "localCookieStoreIDmap should have 7 entries" - ); - console.assert( identities.length === 6, "There should be 6 identities" @@ -182,7 +203,7 @@ browser.tests = { Object.keys(getAssignedSites).length === 5, "There should be 5 site assignments" ); - console.log("Finished!"); + console.log("!!!Finished!!!"); }, async dupeTest() { @@ -204,19 +225,11 @@ browser.tests = { const identities = await browser.contextualIdentities.query({}); - const localCookieStoreIDmap = - await identityState.getCookieStoreIDuuidMap(); - console.assert( Object.keys(getSync.cookieStoreIDmap).length === 7, "cookieStoreIDmap should have 7 entries" ); - console.assert( - Object.keys(localCookieStoreIDmap).length === 8, - "localCookieStoreIDmap should have 8 entries" - ); - console.assert( identities.length === 7, "There should be 7 identities" @@ -240,7 +253,7 @@ browser.tests = { mozillaContainer.icon === "pet", "Mozilla Container should be pet" ); - console.log("Finished!"); + console.log("!!!Finished!!!"); }, async CIerrorTest() { @@ -262,19 +275,11 @@ browser.tests = { const identities = await browser.contextualIdentities.query({}); - const localCookieStoreIDmap = - await identityState.getCookieStoreIDuuidMap(); - console.assert( Object.keys(getSync.cookieStoreIDmap).length === 7, "cookieStoreIDmap should have 7 entries" ); - console.assert( - Object.keys(localCookieStoreIDmap).length === 8, - "localCookieStoreIDmap should have 8 entries" - ); - console.assert( identities.length === 7, "There should be 7 identities" @@ -298,7 +303,7 @@ browser.tests = { mozillaContainer.icon === "pet", "Mozilla Container should be pet" ); - console.log("Finished!"); + console.log("!!!Finished!!!"); }, lookupIdentityBy(identities, options) { @@ -358,14 +363,14 @@ browser.tests = { } }, - stopSyncListeners() { - browser.storage.onChanged.removeListener(sync.storageArea.onChangedListener); - removeContextualIdentityListeners(); + async stopSyncListeners() { + await browser.storage.onChanged.removeListener(sync.storageArea.onChangedListener); + await removeContextualIdentityListeners(); }, - startListeners() { - browser.storage.onChanged.addListener(sync.storageArea.onChangedListener); - addContextualIdentityListeners(); + async startListeners() { + await browser.storage.onChanged.addListener(sync.storageArea.onChangedListener); + await addContextualIdentityListeners(); }, }; @@ -529,26 +534,31 @@ const DUPE_TEST_SYNC = { "siteContainerMap@@_developer.mozilla.org": { "userContextId": "588", "neverAsk": true, + "identityMacAddonUUID": "d20d7af2-9866-468e-bb43-541efe8c2c2e", "hostname": "developer.mozilla.org" }, "siteContainerMap@@_reddit.com": { "userContextId": "592", "neverAsk": true, + "identityMacAddonUUID": "3dc916fb-8c0a-4538-9758-73ef819a45f7", "hostname": "reddit.com" }, "siteContainerMap@@_twitter.com": { "userContextId": "589", "neverAsk": true, + "identityMacAddonUUID": "cdd73c20-c26a-4c06-9b17-735c1f5e9187", "hostname": "twitter.com" }, "siteContainerMap@@_www.facebook.com": { "userContextId": "590", "neverAsk": true, + "identityMacAddonUUID": "32cc4a9b-05ed-4e54-8e11-732468de62f4", "hostname": "www.facebook.com" }, "siteContainerMap@@_www.linkedin.com": { "userContextId": "591", "neverAsk": true, + "identityMacAddonUUID": "9ff381e3-4c11-420d-8e12-e352a3318be1", "hostname": "www.linkedin.com" } } @@ -682,11 +692,13 @@ const CI_ERROR_TEST_SYNC = { "siteContainerMap@@_bugzilla.mozilla.org": { "userContextId": "11", "neverAsk": true, + "identityMacAddonUUID": "0269558d-6be7-487b-beb1-b720b346d09b", "hostname": "bugzilla.mozilla.org" }, "siteContainerMap@@_www.amazon.com": { "userContextId": "14", "neverAsk": false, + "identityMacAddonUUID": "e48d04cf-6277-4236-8f3d-611287d0caf2", "hostname": "www.amazon.com" } }, diff --git a/test/features/external-webextensions.test.js b/test/features/external-webextensions.test.js index 04ebb99..485005c 100644 --- a/test/features/external-webextensions.test.js +++ b/test/features/external-webextensions.test.js @@ -25,11 +25,9 @@ describe("External Webextensions", () => { const [promise] = background.browser.runtime.onMessageExternal.addListener.yield(message, sender); const answer = await promise; - expect(answer).to.deep.equal({ - hostname: "example.com", - userContextId: "1", - neverAsk: false - }); + expect(answer.userContextId === "1").to.be.true; + expect(answer.neverAsk === false).to.be.true; + expect(answer.hasOwnProperty("identityMacAddonUUID")).to.be.true; }); });