From 8b11a60fc33cae4c4fcd0bdfe1e31de306d6dcac Mon Sep 17 00:00:00 2001 From: Minigugus <43109623+Minigugus@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:42:56 +0100 Subject: [PATCH 1/2] Added backup to/restore from file feature --- src/js/background/backgroundLogic.js | 64 ++++++++++++++++++++++++++++ src/js/background/messageHandler.js | 6 +++ src/js/options.js | 57 ++++++++++++++++++++++++- src/options.html | 18 ++++++++ 4 files changed, 144 insertions(+), 1 deletion(-) diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index 7cf5216..48b12bd 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -267,6 +267,70 @@ const backgroundLogic = { return browser.tabs.remove(tabIds); }, + async backupIdentitiesState() { + const identities = await browser.contextualIdentities.query({}); + return Promise.all( + identities.map(async ({ cookieStoreId, color, icon, name }) => { + const userContextId = this.getUserContextIdFromCookieStoreId(cookieStoreId); + const sitesByContainer = await assignManager.storageArea.getAssignedSites(userContextId); + const sites = Object.values(sitesByContainer).map(({ neverAsk, hostname }) => ({ neverAsk, hostname })); + return ({ color, icon, name, sites }); + }) + ); + }, + + async restoreIdentitiesState(identities) { + const backup = await browser.contextualIdentities.query({}); + const incomplete = []; + let allSucceed = true; + const identitiesPromise = identities.map(async ({ color, icon, name, sites }) => { + try { + if (typeof color !== "string" || typeof icon !== "string" || typeof name !== "string" || !Array.isArray((sites))) + throw new Error("Corrupted container backup"); + const identity = await browser.contextualIdentities.create({ color, icon, name }); + try { + await identityState.storageArea.get(identity.cookieStoreId); + const userContextId = this.getUserContextIdFromCookieStoreId(identity.cookieStoreId); + for (const { neverAsk, hostname } of sites) { + if (typeof neverAsk !== "boolean" || typeof hostname !== "string" || hostname === "") + throw new Error("Corrupted site association"); + const pageUrl = `http://${hostname}`; // protocol doesn't really matter here + await assignManager.storageArea.set(pageUrl, { + neverAsk, + userContextId + }); + } + } catch (err) { + incomplete.push(name); // site association damaged + } + return identity; + } catch (err) { + allSucceed = false; + return null; + } + }); + const created = await Promise.all(identitiesPromise); + if (!allSucceed) { // Importation failed, restore previous state + await Promise.all( + created.map(async (identityOrNull) => { + if (identityOrNull) { + await identityState.storageArea.remove(identityOrNull.cookieStoreId); + await browser.contextualIdentities.remove(identityOrNull.cookieStoreId); + } + }) + ); + throw new Error("Some containers couldn't be created"); + } else { // Importation succeed, remove old identities + await Promise.all( + backup.map(async (identity) => { + await identityState.storageArea.remove(identity.cookieStoreId); + await browser.contextualIdentities.remove(identity.cookieStoreId); + }) + ); + } + return { created: created.length, incomplete }; + }, + async queryIdentitiesState(windowId) { const identities = await browser.contextualIdentities.query({}); const identitiesOutput = {}; diff --git a/src/js/background/messageHandler.js b/src/js/background/messageHandler.js index 5d644b6..f9ddd22 100644 --- a/src/js/background/messageHandler.js +++ b/src/js/background/messageHandler.js @@ -72,6 +72,12 @@ const messageHandler = { windowId: m.windowId }); break; + case "backupIdentitiesState": + response = backgroundLogic.backupIdentitiesState(); + break; + case "restoreIdentitiesState": + response = backgroundLogic.restoreIdentitiesState(m.identities); + break; case "queryIdentitiesState": response = backgroundLogic.queryIdentitiesState(m.message.windowId); break; diff --git a/src/js/options.js b/src/js/options.js index 726827b..9259f31 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -53,6 +53,56 @@ async function enableDisableReplaceTab() { await browser.storage.local.set({replaceTabEnabled: !!checkbox.checked}); } +async function backupContainers() { + const backupLink = document.getElementById("containers-save-link"); + const backupResult = document.getElementById("containers-save-result"); + try { + const content = JSON.stringify( + await browser.runtime.sendMessage({ + method: "backupIdentitiesState" + }) + ); + backupLink.href = `data:application/json;base64,${btoa(content)}`; + backupLink.download = `containers-backup-${(new Date()).toISOString()}.json`; + backupLink.click(); + backupResult.textContent = ""; + } catch (err) { + backupResult.textContent = browser.i18n.getMessage("backupFailure", [String(err.message || err)]); + backupResult.style.color = "red"; + } +} + +async function restoreContainers(event) { + const restoreInput = event.currentTarget; + const restoreResult = document.getElementById("containers-restore-result"); + event.preventDefault(); + if (restoreInput.files.length) { + const reader = new FileReader(); + reader.onloadend = async () => { + try { + const identitiesState = JSON.parse(reader.result); + const { created: restoredCount, incomplete } = await browser.runtime.sendMessage({ + method: "restoreIdentitiesState", + identities: identitiesState + }); + if (incomplete.length === 0) { + restoreResult.textContent = browser.i18n.getMessage("containersRestored", [String(restoredCount)]); + restoreResult.style.color = "green"; + } else { + restoreResult.textContent = browser.i18n.getMessage("containersPartiallyRestored", [String(restoredCount), String(incomplete.join(", "))]); + restoreResult.style.color = "orange"; + } + } catch (err) { + console.error("Cannot restore containers list: %s", err.message || err); + restoreResult.textContent = browser.i18n.getMessage("containersRestorationFailed"); + restoreResult.style.color = "red"; + } + }; + reader.readAsText(restoreInput.files.item(0)); + } + restoreInput.value = ""; +} + async function setupOptions() { const { syncEnabled } = await browser.storage.local.get("syncEnabled"); const { replaceTabEnabled } = await browser.storage.local.get("replaceTabEnabled"); @@ -114,6 +164,7 @@ browser.permissions.onRemoved.addListener(resetPermissionsUi); document.addEventListener("DOMContentLoaded", setupOptions); document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync); document.querySelector("#replaceTabCheck").addEventListener( "change", enableDisableReplaceTab); +document.querySelector("#containersRestoreInput").addEventListener( "change", restoreContainers); maybeShowPermissionsWarningIcon(); for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { document.querySelector("#open_container_"+i) @@ -121,8 +172,12 @@ for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { } document.querySelectorAll("[data-btn-id]").forEach(btn => { - btn.addEventListener("click", () => { + btn.addEventListener("click", e => { switch (btn.dataset.btnId) { + case "containers-save-button": + e.preventDefault(); + backupContainers(); + break; case "reset-onboarding": resetOnboarding(); break; diff --git a/src/options.html b/src/options.html index 607c10a..5030f87 100644 --- a/src/options.html +++ b/src/options.html @@ -59,6 +59,24 @@
+ + +
From b8cf955e1f3024cca355a47bc45361583f4477e2 Mon Sep 17 00:00:00 2001 From: Minigugus <43109623+Minigugus@users.noreply.github.com> Date: Sun, 12 Jun 2022 11:37:21 +0200 Subject: [PATCH 2/2] Add "isIsolated" to JSON backups --- src/js/background/backgroundLogic.js | 47 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index 309cc1e..de59166 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -277,9 +277,21 @@ const backgroundLogic = { return Promise.all( identities.map(async ({ cookieStoreId, color, icon, name }) => { const userContextId = this.getUserContextIdFromCookieStoreId(cookieStoreId); - const sitesByContainer = await assignManager.storageArea.getAssignedSites(userContextId); + const [ + { isIsolated }, + sitesByContainer + ] = await Promise.all([ + identityState.storageArea.get(cookieStoreId), + assignManager.storageArea.getAssignedSites(userContextId) + ]); const sites = Object.values(sitesByContainer).map(({ neverAsk, hostname }) => ({ neverAsk, hostname })); - return ({ color, icon, name, sites }); + return ({ + color, + icon, + name, + isolated: isIsolated && true, // either `true` or `undefined` + sites + }); }) ); }, @@ -288,13 +300,19 @@ const backgroundLogic = { const backup = await browser.contextualIdentities.query({}); const incomplete = []; let allSucceed = true; - const identitiesPromise = identities.map(async ({ color, icon, name, sites }) => { + const identitiesPromise = identities.map(async ({ color, icon, name, isolated, sites }) => { try { - if (typeof color !== "string" || typeof icon !== "string" || typeof name !== "string" || !Array.isArray((sites))) + if ( + typeof color !== "string" || + typeof icon !== "string" || + typeof name !== "string" || + (isolated !== true && isolated !== undefined) || + !Array.isArray((sites)) + ) throw new Error("Corrupted container backup"); const identity = await browser.contextualIdentities.create({ color, icon, name }); try { - await identityState.storageArea.get(identity.cookieStoreId); + await identityState.storageArea.get(identity.cookieStoreId); // to create identity state const userContextId = this.getUserContextIdFromCookieStoreId(identity.cookieStoreId); for (const { neverAsk, hostname } of sites) { if (typeof neverAsk !== "boolean" || typeof hostname !== "string" || hostname === "") @@ -305,6 +323,8 @@ const backgroundLogic = { userContextId }); } + if (isolated) + await identityState.storageArea.set(identity.cookieStoreId, { isIsolated: "locked" }); } catch (err) { incomplete.push(name); // site association damaged } @@ -314,6 +334,7 @@ const backgroundLogic = { return null; } }); + const created = await Promise.all(identitiesPromise); if (!allSucceed) { // Importation failed, restore previous state await Promise.all( @@ -325,14 +346,16 @@ const backgroundLogic = { }) ); throw new Error("Some containers couldn't be created"); - } else { // Importation succeed, remove old identities - await Promise.all( - backup.map(async (identity) => { - await identityState.storageArea.remove(identity.cookieStoreId); - await browser.contextualIdentities.remove(identity.cookieStoreId); - }) - ); } + + // Importation succeed, remove old identities + await Promise.all( + backup.map(async (identity) => { + await identityState.storageArea.remove(identity.cookieStoreId); + await browser.contextualIdentities.remove(identity.cookieStoreId); + }) + ); + return { created: created.length, incomplete }; },