This commit is contained in:
Minigugus 2025-05-21 10:43:17 +10:00 committed by GitHub
commit 5e5c8a0913
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 167 additions and 1 deletions

View file

@ -335,6 +335,93 @@ const backgroundLogic = {
return browser.tabs.remove(tabIds); 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 [
{ 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,
isolated: isIsolated && true, // either `true` or `undefined`
sites
});
})
);
},
async restoreIdentitiesState(identities) {
const backup = await browser.contextualIdentities.query({});
const incomplete = [];
let allSucceed = true;
const identitiesPromise = identities.map(async ({ color, icon, name, isolated, sites }) => {
try {
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); // to create identity state
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
});
}
if (isolated)
await identityState.storageArea.set(identity.cookieStoreId, { isIsolated: "locked" });
} 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");
}
// 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) { async queryIdentitiesState(windowId) {
const identities = await browser.contextualIdentities.query({}); const identities = await browser.contextualIdentities.query({});
const identitiesOutput = {}; const identitiesOutput = {};

View file

@ -78,6 +78,12 @@ const messageHandler = {
windowId: m.windowId windowId: m.windowId
}); });
break; break;
case "backupIdentitiesState":
response = backgroundLogic.backupIdentitiesState();
break;
case "restoreIdentitiesState":
response = backgroundLogic.restoreIdentitiesState(m.identities);
break;
case "queryIdentitiesState": case "queryIdentitiesState":
response = backgroundLogic.queryIdentitiesState(m.message.windowId); response = backgroundLogic.queryIdentitiesState(m.message.windowId);
break; break;

View file

@ -53,6 +53,56 @@ async function enableDisableReplaceTab() {
await browser.storage.local.set({replaceTabEnabled: !!checkbox.checked}); 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 changeTheme(event) { async function changeTheme(event) {
const theme = event.currentTarget; const theme = event.currentTarget;
await browser.storage.local.set({currentTheme: theme.value}); await browser.storage.local.set({currentTheme: theme.value});
@ -123,6 +173,7 @@ browser.permissions.onRemoved.addListener(resetPermissionsUi);
document.addEventListener("DOMContentLoaded", setupOptions); document.addEventListener("DOMContentLoaded", setupOptions);
document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync); document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync);
document.querySelector("#replaceTabCheck").addEventListener( "change", enableDisableReplaceTab); document.querySelector("#replaceTabCheck").addEventListener( "change", enableDisableReplaceTab);
document.querySelector("#containersRestoreInput").addEventListener( "change", restoreContainers);
document.querySelector("#changeTheme").addEventListener( "change", changeTheme); document.querySelector("#changeTheme").addEventListener( "change", changeTheme);
maybeShowPermissionsWarningIcon(); maybeShowPermissionsWarningIcon();
@ -132,8 +183,12 @@ for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) {
} }
document.querySelectorAll("[data-btn-id]").forEach(btn => { document.querySelectorAll("[data-btn-id]").forEach(btn => {
btn.addEventListener("click", () => { btn.addEventListener("click", e => {
switch (btn.dataset.btnId) { switch (btn.dataset.btnId) {
case "containers-save-button":
e.preventDefault();
backupContainers();
break;
case "reset-onboarding": case "reset-onboarding":
resetOnboarding(); resetOnboarding();
break; break;

View file

@ -59,6 +59,24 @@
<p><em data-i18n-message-id="replaceTabDescription"></em></p> <p><em data-i18n-message-id="replaceTabDescription"></em></p>
</div> </div>
<h3 data-i18n-message-id="backup"></h3>
<div class="settings-group">
<fieldset>
<legend data-i18n-message-id="restoreLegend"></legend>
<input id="containersRestoreInput" type="file">
<p><em id="containers-restore-result"></em></p>
<p data-i18n-message-id="warningConfigOverride"></p>
</fieldset>
<fieldset>
<legend data-i18n-message-id="saveLegend"></legend>
<a id="containers-save-link" href="#" style="display: none;"></a>
<button data-i18n-message-id="saveButton" data-btn-id="containers-save-button"></button>
<p><em id="containers-save-result"></em></p>
<p data-i18n-message-id="noteWontBackupCookies"></p>
</fieldset>
</div>
<!-- <!--
TODO TODO
- Add data-i18n - Add data-i18n