From 5ae7a395a174ca2d3a4a285e9d22b2a7b5f2b5a7 Mon Sep 17 00:00:00 2001 From: Kendall Werts Date: Thu, 9 Jan 2020 16:36:26 -0600 Subject: [PATCH] added some checks on sync and some error handling for bad site assignment info --- src/js/background/assignManager.js | 25 +++ src/js/background/sync.js | 242 +++++++++++++++++------------ src/js/background/test.js | 173 +++++++++++++-------- src/js/popup.js | 8 +- 4 files changed, 285 insertions(+), 163 deletions(-) diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index 2f4656e..096ebf1 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -107,6 +107,31 @@ const assignManager = { } return sites; }, + + /* + * Looks for abandoned site assignments. If there is no identity with + * the site assignment's userContextId (cookieStoreId), then the assignment + * is removed. + */ + async cleanup() { + const identitiesList = await browser.contextualIdentities.query({}); + const macConfigs = await this.area.get(); + for(const configKey of Object.keys(macConfigs)) { + if (configKey.includes("siteContainerMap@@_")) { + const cookieStoreId = + "firefox-container-" + macConfigs[configKey].userContextId; + const match = identitiesList.find( + localIdentity => localIdentity.cookieStoreId === cookieStoreId + ); + if (!match) { + await this.remove(configKey.replace(/^siteContainerMap@@_/, "https://")); + continue; + } + } + } + + } + }, _neverAsk(m) { diff --git a/src/js/background/sync.js b/src/js/background/sync.js index dbb8f11..b56a634 100644 --- a/src/js/background/sync.js +++ b/src/js/background/sync.js @@ -51,9 +51,7 @@ const sync = { async backup(options) { // remove listeners to avoid an infinite loop! - await browser.storage.onChanged.removeListener( - sync.storageArea.onChangedListener); - await removeContextualIdentityListeners(); + await sync.checkForListenersMaybeRemove(); await updateSyncIdentities(); await updateCookieStoreIdMap(); @@ -71,9 +69,8 @@ const sync = { console.log("inLocal:", localStorage); console.log("idents: ", await browser.contextualIdentities.query({})); } - browser.storage.onChanged.addListener( - sync.storageArea.onChangedListener); - addContextualIdentityListeners(); + + await sync.checkForListenersMaybeAdd(); async function updateSyncIdentities() { const identities = await browser.contextualIdentities.query({}); @@ -93,9 +90,8 @@ const sync = { } async function updateDeletedIdentityList(deletedIdentityUUID) { - let { deletedIdentityList } = - await sync.storageArea.get("deletedIdentityList"); - if (!deletedIdentityList) deletedIdentityList = []; + const deletedIdentityList = + await sync.storageArea.getDeletedIdentityList(); if ( deletedIdentityList.find(element => element === deletedIdentityUUID) ) return; @@ -104,74 +100,91 @@ const sync = { } async function addToDeletedSitesList(siteStoreKey) { - let { deletedSiteList } = - await sync.storageArea.get("deletedSiteList"); - if (!deletedSiteList) deletedSiteList = []; + const deletedSiteList = + await sync.storageArea.getDeletedSiteList(); if (deletedSiteList.find(element => element === siteStoreKey)) return; deletedSiteList.push(siteStoreKey); await sync.storageArea.set({ deletedSiteList }); } async function removeFromDeletedSitesList(siteStoreKey) { - let { deletedSiteList } = - await sync.storageArea.get("deletedSiteList"); - if (!deletedSiteList) return; - deletedSiteList = deletedSiteList + const deletedSiteList = + await sync.storageArea.getDeletedSiteList(); + const newDeletedSiteList = deletedSiteList .filter(element => element !== siteStoreKey); - await sync.storageArea.set({ deletedSiteList }); + await sync.storageArea.set({ deletedSiteList: newDeletedSiteList }); } }, - /* - * Ensures all sync info matches. But maybe we shouldn't even use - * sync info that doesn't match. - */ - async cleanup() { - console.log("cleanupSync"); - browser.storage.onChanged.removeListener( - sync.storageArea.onChangedListener); - const identitiesList = - await sync.storageArea.getIdentities(); - const cookieStoreIDmap = - await sync.storageArea.getCookieStoreIDMap(); - for(const cookieStoreId of Object.keys(cookieStoreIDmap)) { - const match = identitiesList - .find(syncIdentity => - syncIdentity.cookieStoreId === cookieStoreId - ); - if (!match) { - delete cookieStoreIDmap[cookieStoreId]; - await sync.storageArea.set({ cookieStoreIDmap }); - console.log("removed ", cookieStoreId, " from sync list"); - } - } - await browser.storage.onChanged.addListener( - sync.storageArea.onChangedListener); - }, - onChangedListener(changes, areaName) { - if (areaName === "sync") sync.runSync(); + if (areaName === "sync") sync.errorHandledRunSync(); }, async addToDeletedList(changeInfo) { const identity = changeInfo.contextualIdentity; - console.log("addToDeletedList", identity.cookieStoreId); const deletedUUID = await identityState.lookupMACaddonUUID(identity.cookieStoreId); await identityState.storageArea.remove(identity.cookieStoreId); - console.log(deletedUUID); sync.storageArea.backup({uuid: deletedUUID}); + }, + + async checkSyncForMismatches() { + const cookieStoreIDmap = await this.getCookieStoreIDMap(); + const identities = await this.getIdentities(); + for (const cookieStoreId of Object.keys(cookieStoreIDmap)) { + const match = identities.find(identity => + identity.cookieStoreId === cookieStoreId + ); + if (!match) return false; + // if one has no match, this is bad data. + console.log("Bad Data, skipping"); + } + return !(Object.entries(cookieStoreIDmap).length === 0); } }, init() { - const errorHandledRunSync = () => { - this.runSync().catch((error)=> { - console.error("Error from runSync", error); - }); - }; - browser.runtime.onInstalled.addListener(errorHandledRunSync); - browser.runtime.onStartup.addListener(errorHandledRunSync); + browser.runtime.onInstalled.addListener(this.errorHandledRunSync); + browser.runtime.onStartup.addListener(this.errorHandledRunSync); + }, + + async errorHandledRunSync () { + sync.runSync().catch(async (error)=> { + console.error("Error from runSync", error); + sync.checkForListenersMaybeAdd(); + }); + }, + + async checkForListenersMaybeAdd() { + const hasStorageListener = + await browser.storage.onChanged.hasListener( + sync.storageArea.onChangedListener + ); + + if (! await hasContextualIdentityListeners()) { + addContextualIdentityListeners(); + } + + if (! hasStorageListener) { + browser.storage.onChanged.addListener( + sync.storageArea.onChangedListener); + } + }, + + async checkForListenersMaybeRemove() { + const hasStorageListener = + await browser.storage.onChanged.hasListener( + sync.storageArea.onChangedListener + ); + + if (await hasContextualIdentityListeners()) { + removeContextualIdentityListeners(); + } + + if (hasStorageListener) { + browser.storage.onChanged.removeListener( + sync.storageArea.onChangedListener); + } }, async runSync() { @@ -182,21 +195,19 @@ const sync = { console.log("inLocal: ", localInfo); console.log("indents: ", await browser.contextualIdentities.query({})); } - browser.storage.onChanged.removeListener( - sync.storageArea.onChangedListener); - removeContextualIdentityListeners(); + await sync.checkForListenersMaybeRemove(); console.log("runSync"); - await identityState.storageArea.cleanup(); - if (await sync.storageArea.hasSyncStorage()){ - await sync.storageArea.cleanup(); - console.log("storage found, attempting to restore ..."); - await restore(); - return; - } - console.log("no sync storage, backing up..."); + await identityState.storageArea.cleanup(); + await assignManager.storageArea.cleanup(); + + const hasSyncStorage = await sync.storageArea.hasSyncStorage(); + const dataIsReliable = await sync.storageArea.checkSyncForMismatches(); + if (hasSyncStorage && dataIsReliable) await restore(); + await sync.storageArea.backup(); - return; }, + return; + }, }; sync.init(); @@ -205,7 +216,7 @@ async function restore() { console.log("restore"); await reconcileIdentities(); await reconcileSiteAssignments(); - await sync.storageArea.backup(); + return; } /* @@ -224,7 +235,13 @@ async function reconcileIdentities(){ const deletedCookieStoreId = await identityState.lookupCookieStoreId(deletedUUID); if (deletedCookieStoreId){ - await browser.contextualIdentities.remove(deletedCookieStoreId); + try{ + await browser.contextualIdentities.remove(deletedCookieStoreId); + } catch (error) { + // if the identity we are deleting is not there, that's fine. + console.error("Error deleting contextualIdentity", deletedCookieStoreId); + continue; + } } } @@ -291,23 +308,32 @@ async function ifUUIDMatch(syncIdentity, localCookieStoreID) { icon: syncIdentity.icon }; if (SYNC_DEBUG) { - const getIdent = + try { + const getIdent = await browser.contextualIdentities.get(localCookieStoreID); - if (getIdent.name !== identityInfo.name) { - console.log(getIdent.name, "Change name: ", identityInfo.name); - } - if (getIdent.color !== identityInfo.color) { - console.log(getIdent.name, "Change color: ", identityInfo.color); - } - if (getIdent.icon !== identityInfo.icon) { - console.log(getIdent.name, "Change icon: ", identityInfo.icon); + if (getIdent.name !== identityInfo.name) { + console.log(getIdent.name, "Change name: ", identityInfo.name); + } + if (getIdent.color !== identityInfo.color) { + console.log(getIdent.name, "Change color: ", identityInfo.color); + } + if (getIdent.icon !== identityInfo.icon) { + console.log(getIdent.name, "Change icon: ", identityInfo.icon); + } + } catch (error) { + //if this fails, there is probably differing sync info. + console.error("Error getting info on CI", error); } } - + try { // update the local container with the sync data - await browser.contextualIdentities - .update(localCookieStoreID, identityInfo); - return; + await browser.contextualIdentities + .update(localCookieStoreID, identityInfo); + return; + } catch (error) { + // If this fails, sync info is off. + console.error("Error udpating CI", error); + } } async function ifNoMatch(syncIdentity){ @@ -351,34 +377,43 @@ async function reconcileSiteAssignments() { for(const urlKey of Object.keys(assignedSitesFromSync)) { const assignedSite = assignedSitesFromSync[urlKey]; - const syncUUID = - await lookupSyncSiteAssigmentIdentityUUID( - assignedSite, cookieStoreIDmap, urlKey - ); - if (syncUUID) { + try{ + const syncUUID = + await lookupSyncSiteAssigmentIdentityUUID( + assignedSite, cookieStoreIDmap, urlKey + ); + if (syncUUID) { // 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); - continue; - } + console.log("new assignment ", assignedSite, ": ", + assignedSite.userContextId); + const newUUID = cookieStoreIDmap[ + "firefox-container-" + assignedSite.userContextId + ]; - // if there's no syncUUID, something is wrong, since these site - // assignments are from sync - throw new Error("Sync storage not aligned"); + await setAssignmentWithUUID(newUUID, 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; - return cookieStoreIDmap[syncCookieStoreId]; + const uuid = cookieStoreIDmap[syncCookieStoreId]; + if (!uuid) throw new Error (`No syncUUID found for : ${urlKey}`); + return uuid; } } @@ -414,4 +449,13 @@ function removeContextualIdentityListeners(listenerList) { browser.contextualIdentities.onCreated.removeListener(listenerList[0]); browser.contextualIdentities.onRemoved.removeListener(listenerList[1]); browser.contextualIdentities.onUpdated.removeListener(listenerList[2]); -} \ No newline at end of file +} + +async function hasContextualIdentityListeners(listenerList) { + if(!listenerList) listenerList = syncCIListenerList; + return ( + await browser.contextualIdentities.onCreated.hasListener(listenerList[0]) && + await browser.contextualIdentities.onRemoved.hasListener(listenerList[1]) && + await browser.contextualIdentities.onUpdated.hasListener(listenerList[2]) + ); +} diff --git a/src/js/background/test.js b/src/js/background/test.js index b1d19b1..bc05fbe 100644 --- a/src/js/background/test.js +++ b/src/js/background/test.js @@ -1,16 +1,32 @@ browser.tests = { async runAll() { await this.testIdentityStateCleanup(); - await this.test1(); + await this.testReconcileSiteAssignments(); + await this.testInitialSync(); await this.test2(); await this.dupeTest(); }, + // more unit tests + // if site assignment has no valid cookieStoreId, delete on local + + async resetForManualTests() { + await browser.tests.stopSyncListeners(); + console.log("reset"); + await this.setState({}, LOCAL_DATA, TEST_CONTAINERS, []); + }, async testIdentityStateCleanup() { 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, []); + + await browser.storage.local.set({ + "identitiesState@@_firefox-container-5": { + "hiddenTabs": [] + } + }); + console.log("local storage set: ", await browser.storage.local.get()); await identityState.storageArea.cleanup(); @@ -28,11 +44,76 @@ browser.tests = { } console.log("Finished!"); }, - async test1() { + + async testAssignManagerCleanup() { + await browser.tests.stopSyncListeners(); + console.log("Testing the cleanup of local storage"); + + await this.setState({}, LOCAL_DATA, TEST_CONTAINERS, []); + + await browser.storage.local.set({ + "siteContainerMap@@_www.goop.com": { + "userContextId": "5", + "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": [] + } + }); + + const macConfigs = await browser.storage.local.get(); + const assignments = []; + for(const configKey of Object.keys(macConfigs)) { + if (configKey.includes("siteContainerMap@@_")) { + assignments.push(configKey); + } + } + + console.assert(assignments.length === 0, "There should be 0 identity entries"); + + 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, + LOCAL_DATA, + TEST_CONTAINERS, + SITE_ASSIGNMENT_TEST + ); + + 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!"); + }, + + async testInitialSync() { await browser.tests.stopSyncListeners(); console.log("Testing new install with no sync"); - await this.setState({}, LOCAL_DATA, TEST_CONTAINERS); + await this.setState({}, LOCAL_DATA, TEST_CONTAINERS, []); await sync.runSync(); @@ -106,7 +187,7 @@ browser.tests = { async dupeTest() { await browser.tests.stopSyncListeners(); - console.log("Test state from duped sync"); + console.log("Test state from sync that duped everything initially"); await this.setState( DUPE_TEST_SYNC, @@ -176,30 +257,42 @@ browser.tests = { } return false; }, - async setState(syncData, localData, indentityData, assignmentData){ + + /* + * Sets the state of sync storage, local storage, and the identities. + * SyncDat and localData (without identities or site assignments) get + * set to sync and local storage respectively. IdentityData creates + * new identities (after all have been removed), and assignmentData + * is used along with the new identities' cookieStoreIds to create + * site assignments in local storage. + */ + async setState(syncData, localData, identityData, assignmentData){ await this.removeAllContainers(); await browser.storage.sync.clear(); await browser.storage.sync.set(syncData); await browser.storage.local.clear(); - for (let i=0; i < indentityData.length; i++) { + await browser.storage.local.set(localData); + for (let i=0; i < identityData.length; i++) { //build identities const newIdentity = - await browser.contextualIdentities.create(indentityData[i]); + await browser.contextualIdentities.create(identityData[i]); // fill identies with site assignments if (assignmentData && assignmentData[i]) { - localData[assignmentData[i]] = { + const data = { "userContextId": String( newIdentity.cookieStoreId.replace(/^firefox-container-/, "") ), "neverAsk": true }; + + await browser.storage.local.set({[assignmentData[i]]: data}); } } - await browser.storage.local.set(localData); console.log("local storage set: ", await browser.storage.local.get()); return; }, + async removeAllContainers() { const identities = await browser.contextualIdentities.query({}); for (const identity of identities) { @@ -254,6 +347,7 @@ const TEST_ASSIGNMENTS = [ "siteContainerMap@@_www.linkedin.com", "siteContainerMap@@_reddit.com" ]; + const LOCAL_DATA = { "browserActionBadgesClicked": [ "6.1.1" ], "containerTabsOpened": 7, @@ -313,23 +407,6 @@ const SYNC_DATA = { }, "assignedSites": {} }; -browser.resetMAC2 = async function () { - // for debugging and testing: remove all containers except the default 4 and the first one created - browser.tests.stopSyncListeners(); - - // sync state after FF1 (default + 1) - await browser.storage.sync.clear(); - const syncData = {"cookieStoreIDmap":{"firefox-container-1":"4dc76734-5b71-4f2e-85d0-1cb199ae3821","firefox-container-2":"30308b8d-393c-4375-b9a1-afc59f0dea79","firefox-container-3":"7419c94d-85d7-4d76-94c0-bacef1de722f","firefox-container-4":"2b9fe881-e552-4df9-8cab-922f4688bb68","firefox-container-6":"db7f622e-682b-4556-968a-6e2542ff3b26"},"assignedSites":{"siteContainerMap@@_twitter.com":{"userContextId":"1","neverAsk":!0},"siteContainerMap@@_www.facebook.com":{"userContextId":"2","neverAsk":!0},"siteContainerMap@@_www.linkedin.com":{"userContextId":"4","neverAsk":!1}},"identities":[{"name":"Personal","icon":"fingerprint","iconUrl":"resource://usercontext-content/fingerprint.svg","color":"blue","colorCode":"#37adff","cookieStoreId":"firefox-container-1"},{"name":"Work","icon":"briefcase","iconUrl":"resource://usercontext-content/briefcase.svg","color":"orange","colorCode":"#ff9f00","cookieStoreId":"firefox-container-2"},{"name":"Banking","icon":"dollar","iconUrl":"resource://usercontext-content/dollar.svg","color":"green","colorCode":"#51cd00","cookieStoreId":"firefox-container-3"},{"name":"Shopping","icon":"cart","iconUrl":"resource://usercontext-content/cart.svg","color":"pink","colorCode":"#ff4bda","cookieStoreId":"firefox-container-4"},{"name":"Container #01","icon":"chill","iconUrl":"resource://usercontext-content/chill.svg","color":"green","colorCode":"#51cd00","cookieStoreId":"firefox-container-6"}]}; - browser.storage.sync.set(syncData); - - // FF2 (intial sync w/ default 4 + 1 with some changes) - browser.contextualIdentities.update("firefox-container-2", {color:"purple"}); - browser.contextualIdentities.update("firefox-container-4", {icon:"pet"}); - browser.storage.local.clear(); - const localData = {"browserActionBadgesClicked":["6.1.1"],"containerTabsOpened":7,"identitiesState@@_firefox-container-1":{"hiddenTabs":[]},"identitiesState@@_firefox-container-2":{"hiddenTabs":[]},"identitiesState@@_firefox-container-3":{"hiddenTabs":[]},"identitiesState@@_firefox-container-4":{"hiddenTabs":[]},"identitiesState@@_firefox-container-6":{"hiddenTabs":[]},"identitiesState@@_firefox-default":{"hiddenTabs":[]},"onboarding-stage":5,"siteContainerMap@@_developer.mozilla.org":{"userContextId":"6","neverAsk":!1},"siteContainerMap@@_twitter.com":{"userContextId":"1","neverAsk":!0},"siteContainerMap@@_www.linkedin.com":{"userContextId":"4","neverAsk":!1}}; - browser.storage.local.set(localData); - -}; const DUPE_TEST_SYNC = { "identities": [ @@ -439,6 +516,13 @@ const DUPE_TEST_ASSIGNMENTS = [ "siteContainerMap@@_www.linkedin.com" ]; +const SITE_ASSIGNMENT_TEST = [ + "siteContainerMap@@_developer.mozilla.org", + "siteContainerMap@@_www.facebook.com", + "siteContainerMap@@_www.google.com", + "siteContainerMap@@_bugzilla.mozilla.org" +]; + const DUPE_TEST_IDENTS = [ { "name": "Personal", @@ -471,38 +555,3 @@ const DUPE_TEST_IDENTS = [ "color": "yellow", } ]; -browser.resetMAC3 = async function () { - // for debugging and testing: remove all containers except the default 4 and the first one created - browser.tests.stopSyncListeners(); - - // sync state after FF2 synced - await browser.storage.sync.clear(); - const syncData = {"assignedSites":{"siteContainerMap@@_developer.mozilla.org":{"userContextId":"6","neverAsk":!1,"hostname":"developer.mozilla.org"},"siteContainerMap@@_twitter.com":{"userContextId":"1","neverAsk":!0,"hostname":"twitter.com"},"siteContainerMap@@_www.facebook.com":{"userContextId":"2","neverAsk":!0,"hostname":"www.facebook.com"},"siteContainerMap@@_www.linkedin.com":{"userContextId":"4","neverAsk":!1,"hostname":"www.linkedin.com"},"siteContainerMap@@_reddit.com": {"userContextId": "7","neverAsk": true}},"cookieStoreIDmap":{"firefox-container-1":"4dc76734-5b71-4f2e-85d0-1cb199ae3821","firefox-container-2":"30308b8d-393c-4375-b9a1-afc59f0dea79","firefox-container-3":"7419c94d-85d7-4d76-94c0-bacef1de722f","firefox-container-4":"2b9fe881-e552-4df9-8cab-922f4688bb68","firefox-container-6":"db7f622e-682b-4556-968a-6e2542ff3b26","firefox-container-7":"ceb06672-76c0-48c4-959e-f3a3ee8358b6"},"identities":[{"name":"Personal","icon":"fingerprint","iconUrl":"resource://usercontext-content/fingerprint.svg","color":"blue","colorCode":"#37adff","cookieStoreId":"firefox-container-1"},{"name":"Work","icon":"briefcase","iconUrl":"resource://usercontext-content/briefcase.svg","color":"orange","colorCode":"#ff9f00","cookieStoreId":"firefox-container-2"},{"name":"Banking","icon":"dollar","iconUrl":"resource://usercontext-content/dollar.svg","color":"purple","colorCode":"#af51f5","cookieStoreId":"firefox-container-3"},{"name":"Shopping","icon":"cart","iconUrl":"resource://usercontext-content/cart.svg","color":"pink","colorCode":"#ff4bda","cookieStoreId":"firefox-container-4"},{"name":"Container #01","icon":"chill","iconUrl":"resource://usercontext-content/chill.svg","color":"green","colorCode":"#51cd00","cookieStoreId":"firefox-container-6"},{"name":"Container #02","icon":"vacation","iconUrl":"resource://usercontext-content/vacation.svg","color":"yellow","colorCode":"#ffcb00","cookieStoreId":"firefox-container-7"}]}; - browser.storage.sync.set(syncData); - - // FF1 with updates from FF2 (intial sync w/ default 4 + 1 with some changes) - browser.contextualIdentities.update("firefox-container-3", {color:"purple", icon:"fruit"}); - //browser.contextualIdentities.create({name: "Container #02", icon: "vacation", color: "yellow"}); - browser.storage.local.clear(); - const localData = {"beenSynced":!0,"browserActionBadgesClicked":["6.1.1"],"containerTabsOpened":7,"identitiesState@@_firefox-container-1":{"hiddenTabs":[],"macAddonUUID":"4dc76734-5b71-4f2e-85d0-1cb199ae3821"},"identitiesState@@_firefox-container-2":{"hiddenTabs":[],"macAddonUUID":"30308b8d-393c-4375-b9a1-afc59f0dea79"},"identitiesState@@_firefox-container-3":{"hiddenTabs":[],"macAddonUUID":"7419c94d-85d7-4d76-94c0-bacef1de722f"},"identitiesState@@_firefox-container-4":{"hiddenTabs":[],"macAddonUUID":"2b9fe881-e552-4df9-8cab-922f4688bb68"},"identitiesState@@_firefox-container-6":{"hiddenTabs":[],"macAddonUUID":"db7f622e-682b-4556-968a-6e2542ff3b26"},"identitiesState@@_firefox-default":{"hiddenTabs":[]},"onboarding-stage":5,"siteContainerMap@@_developer.mozilla.org":{"userContextId":"6","neverAsk":!1},"siteContainerMap@@_twitter.com":{"userContextId":"1","neverAsk":!0},"siteContainerMap@@_www.facebook.com":{"userContextId":"2","neverAsk":!0},"siteContainerMap@@_www.linkedin.com":{"userContextId":"4","neverAsk":!1}}; - browser.storage.local.set(localData); - -}; - -browser.resetMAC4 = async function () { - // for debugging and testing: remove all containers except the default 4 and the first one created - browser.tests.stopSyncListeners(); - - // sync state after FF2 synced - await browser.storage.sync.clear(); - const syncData = {"assignedSites":{"siteContainerMap@@_developer.mozilla.org":{"userContextId":"6","neverAsk":!1,"hostname":"developer.mozilla.org"},"siteContainerMap@@_twitter.com":{"userContextId":"1","neverAsk":!0,"hostname":"twitter.com"},"siteContainerMap@@_www.facebook.com":{"userContextId":"2","neverAsk":!0,"hostname":"www.facebook.com"},"siteContainerMap@@_www.linkedin.com":{"userContextId":"4","neverAsk":!1,"hostname":"www.linkedin.com"},"siteContainerMap@@_reddit.com": {"userContextId": "7","neverAsk": true}},"cookieStoreIDmap":{"firefox-container-1":"4dc76734-5b71-4f2e-85d0-1cb199ae3821","firefox-container-2":"30308b8d-393c-4375-b9a1-afc59f0dea79","firefox-container-3":"7419c94d-85d7-4d76-94c0-bacef1de722f","firefox-container-4":"2b9fe881-e552-4df9-8cab-922f4688bb68","firefox-container-6":"db7f622e-682b-4556-968a-6e2542ff3b26","firefox-container-7":"ceb06672-76c0-48c4-959e-f3a3ee8358b6"},"identities":[{"name":"Personal","icon":"fingerprint","iconUrl":"resource://usercontext-content/fingerprint.svg","color":"blue","colorCode":"#37adff","cookieStoreId":"firefox-container-1"},{"name":"Work","icon":"briefcase","iconUrl":"resource://usercontext-content/briefcase.svg","color":"orange","colorCode":"#ff9f00","cookieStoreId":"firefox-container-2"},{"name":"Banking","icon":"dollar","iconUrl":"resource://usercontext-content/dollar.svg","color":"purple","colorCode":"#af51f5","cookieStoreId":"firefox-container-3"},{"name":"Shopping","icon":"cart","iconUrl":"resource://usercontext-content/cart.svg","color":"pink","colorCode":"#ff4bda","cookieStoreId":"firefox-container-4"},{"name":"Container #01","icon":"chill","iconUrl":"resource://usercontext-content/chill.svg","color":"green","colorCode":"#51cd00","cookieStoreId":"firefox-container-6"},{"name":"Container #02","icon":"vacation","iconUrl":"resource://usercontext-content/vacation.svg","color":"yellow","colorCode":"#ffcb00","cookieStoreId":"firefox-container-7"}]}; - browser.storage.sync.set(syncData); - - // FF1 with updates from FF2 (intial sync w/ default 4 + 1 with some changes) - browser.contextualIdentities.update("firefox-container-3", {color:"purple", icon:"fruit"}); - //browser.contextualIdentities.create({name: "Container #02", icon: "vacation", color: "yellow"}); - browser.storage.local.clear(); - const localData = {"beenSynced":!0,"browserActionBadgesClicked":["6.1.1"],"containerTabsOpened":7,"identitiesState@@_firefox-container-1":{"hiddenTabs":[],"macAddonUUID":"4dc76734-5b71-4f2e-85d0-1cb199ae3821"},"identitiesState@@_firefox-container-2":{"hiddenTabs":[],"macAddonUUID":"30308b8d-393c-4375-b9a1-afc59f0dea79"},"identitiesState@@_firefox-container-3":{"hiddenTabs":[],"macAddonUUID":"7419c94d-85d7-4d76-94c0-bacef1de722f"},"identitiesState@@_firefox-container-4":{"hiddenTabs":[],"macAddonUUID":"2b9fe881-e552-4df9-8cab-922f4688bb68"},"identitiesState@@_firefox-container-6":{"hiddenTabs":[],"macAddonUUID":"db7f622e-682b-4556-968a-6e2542ff3b26"},"identitiesState@@_firefox-default":{"hiddenTabs":[]},"onboarding-stage":5,"siteContainerMap@@_developer.mozilla.org":{"userContextId":"6","neverAsk":!1},"siteContainerMap@@_twitter.com":{"userContextId":"1","neverAsk":!0},"siteContainerMap@@_www.facebook.com":{"userContextId":"2","neverAsk":!0},"siteContainerMap@@_www.linkedin.com":{"userContextId":"4","neverAsk":!1}}; - browser.storage.local.set(localData); - -}; \ No newline at end of file diff --git a/src/js/popup.js b/src/js/popup.js index b539816..d4ec8c9 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -724,8 +724,12 @@ Logic.registerPanel(P_CONTAINERS_LIST, { however it allows us to have a tabindex before the first selected item */ const focusHandler = () => { - list.querySelector("tr .clickable").focus(); - document.removeEventListener("focus", focusHandler); + const identityList = list.querySelector("tr .clickable"); + if (identityList) { + // otherwise this throws an error when there are no containers present. + identityList.focus(); + document.removeEventListener("focus", focusHandler); + } }; document.addEventListener("focus", focusHandler); /* If the user mousedown's first then remove the focus handler */