diff --git a/.gitmodules b/.gitmodules index c93b31b..0185a08 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "nscl"] path = src/nscl url = ../nscl.git + branch = container-tabs diff --git a/src/bg/LifeCycle.js b/src/bg/LifeCycle.js index 32c2d6d..f72d037 100644 --- a/src/bg/LifeCycle.js +++ b/src/bg/LifeCycle.js @@ -104,6 +104,7 @@ var LifeCycle = (() => { let {url} = tab; let {cypherText, key, iv} = await encrypt(JSON.stringify({ policy: ns.policy.dry(true), + contextStore: ns.contextStore.dry(true), allSeen, unrestrictedTabs: [...ns.unrestrictedTabs] })); @@ -188,7 +189,7 @@ var LifeCycle = (() => { iv }, key, cypherText ); - let {policy, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded)); + let {policy, contextStore, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded)); if (!policy) { throw new error("Ephemeral policy not found in survival tab %s!", tabId); } @@ -196,6 +197,7 @@ var LifeCycle = (() => { destroyIfNeeded(); if (ns.initializing) await ns.initializing; ns.policy = new Policy(policy); + ns.contextStore = new ContextStore(contextStore); await Promise.all( Object.entries(allSeen).map( async ([tabId, seen]) => { @@ -274,6 +276,17 @@ var LifeCycle = (() => { if (changed) { await ns.savePolicy(); } + if (ns.contextStore) { + changed = false; + for (let k of Object.keys(ns.contextStore.policies)){ + for (let p of ns.contextStore.policies[k].getPresets(presetNames)) { + if (callback(p)) changed = true; + } + } + if (changed) { + await ns.saveContextStore(); + } + } }; let configureNewCap = async (cap, presetNames, capsFilter) => { diff --git a/src/bg/RequestGuard.js b/src/bg/RequestGuard.js index f3a0e63..c46ae4e 100644 --- a/src/bg/RequestGuard.js +++ b/src/bg/RequestGuard.js @@ -280,8 +280,10 @@ var RequestGuard = (() => { } let key = [siteKey, origin][ret.option || 0]; if (!key) return; + let cookieStoreId = sender.tab && sender.tab.cookieStoreId; + let policy = ns.getPolicy(cookieStoreId); let contextUrl = sender.tab.url || documentUrl; - let {siteMatch, contextMatch, perms} = ns.policy.get(key, contextUrl); + let {siteMatch, contextMatch, perms} = policy.get(key, contextUrl); let {capabilities} = perms; if (!capabilities.has(policyType)) { let temp = sender.tab.incognito; // we don't want to store in PBM @@ -294,8 +296,9 @@ var RequestGuard = (() => { perms = new Permissions(new Set(capabilities), false, contextualSites); } */ - ns.policy.set(key, perms); + policy.set(key, perms); await ns.savePolicy(); + await ns.saveContextStore(); } return {enable: key}; }, @@ -397,7 +400,7 @@ var RequestGuard = (() => { }; function intersectCapabilities(perms, request) { - let {frameId, frameAncestors, tabId} = request; + let {frameId, frameAncestors, tabId, cookieStoreId} = request; if (frameId !== 0 && ns.sync.cascadeRestrictions) { let topUrl = frameAncestors && frameAncestors.length && frameAncestors[frameAncestors.length - 1].url; @@ -406,7 +409,8 @@ var RequestGuard = (() => { if (tab) topUrl = tab.url; } if (topUrl) { - return ns.policy.cascadeRestrictions(perms, topUrl).capabilities; + let policy = ns.getPolicy(cookieStoreId); + return policy.cascadeRestrictions(perms, topUrl).capabilities; } } return perms.capabilities; @@ -468,9 +472,10 @@ var RequestGuard = (() => { function checkLANRequest(request) { if (!ns.isEnforced(request.tabId)) return ALLOW; - let {originUrl, url} = request; + let {originUrl, url, cookieStoreId} = request; + let policy = ns.getPolicy(cookieStoreId); if (originUrl && !Sites.isInternal(originUrl) && url.startsWith("http") && - !ns.policy.can(originUrl, "lan", ns.policyContext(request))) { + !policy.can(originUrl, "lan", ns.policyContext(request))) { // we want to block any request whose origin resolves to at least one external WAN IP // and whose destination resolves to at least one LAN IP let {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo @@ -504,7 +509,6 @@ var RequestGuard = (() => { normalizeRequest(request); initPendingRequest(request); - let {policy} = ns let {tabId, type, url, originUrl} = request; if (type in policyTypesMap) { @@ -527,7 +531,9 @@ var RequestGuard = (() => { } return ALLOW; } + let {cookieStoreId} = request; let isFetch = "fetch" === policyType; + let policy = ns.getPolicy(cookieStoreId); if ((isFetch || "frame" === policyType) && (((isFetch && !originUrl @@ -639,12 +645,12 @@ var RequestGuard = (() => { let promises = []; pending.headersProcessed = true; - let {url, documentUrl, tabId, responseHeaders, type} = request; + let {url, documentUrl, tabId, cookieStoreId, responseHeaders, type} = request; let isMainFrame = type === "main_frame"; try { let capabilities; if (ns.isEnforced(tabId)) { - let policy = ns.policy; + let policy = ns.getPolicy(cookieStoreId); let {perms} = policy.get(url, ns.policyContext(request)); if (isMainFrame) { if (policy.autoAllowTop && perms === policy.DEFAULT) { @@ -769,8 +775,8 @@ var RequestGuard = (() => { } function injectPolicyScript(details) { - let {url, tabId, frameId} = details; - let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId}); + let {url, tabId, frameId, cookieStoreId} = details; + let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId, cookieStoreId}); policy.navigationURL = url; let debugStatement = ns.local.debug ? ` let mark = Date.now() + ":" + Math.random(); diff --git a/src/bg/Settings.js b/src/bg/Settings.js index 0312606..b942c6f 100644 --- a/src/bg/Settings.js +++ b/src/bg/Settings.js @@ -98,6 +98,7 @@ var Settings = { async update(settings) { let { policy, + contextStore, xssUserChoices, tabId, unrestrictedTab, @@ -146,6 +147,7 @@ var Settings = { if (settings.sync === null) { // user is resetting options policy = this.createDefaultDryPolicy(); + contextStore = new ContextStore().dry(); // overriden defaults when user manually resets options @@ -170,6 +172,12 @@ var Settings = { await ns.savePolicy(); } + if (contextStore) { + let newContextStore = new ContextStore(contextStore); + ns.contextStore = newContextStore + await ns.saveContextStore(); + } + if (typeof unrestrictedTab === "boolean") { ns.unrestrictedTabs[unrestrictedTab ? "add" : "delete"](tabId); } @@ -213,6 +221,7 @@ var Settings = { export() { return JSON.stringify({ policy: ns.policy.dry(), + contextStore: ns.contextStore.dry(), local: ns.local, sync: ns.sync, xssUserChoices: XSS.getUserChoices(), diff --git a/src/bg/main.js b/src/bg/main.js index 1748bb5..4dfede6 100644 --- a/src/bg/main.js +++ b/src/bg/main.js @@ -82,6 +82,19 @@ } } + if (!ns.contextStore) { // it could have been already retrieved by LifeCycle + let contextStoreData = (await Storage.get("sync", "contextStore")).contextStore; + if (contextStoreData) { + ns.contextStore = new ContextStore(contextStoreData); + await ns.contextStore.updateContainers(ns.policy); + } else { + log("No container data found. Initializing new policies.") + ns.contextStore = new ContextStore(); + await ns.contextStore.updateContainers(ns.policy); + await ns.saveContextStore(); + } + } + let {isTorBrowser} = ns.local; Sites.onionSecure = isTorBrowser; @@ -178,11 +191,13 @@ tabId = -1 }) { let policy = ns.policy.dry(true); + let contextStore = ns.contextStore.dry(true); let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null; let xssUserChoices = await XSS.getUserChoices(); let anonymyzedTabInfo = await Messages.send("settings", { policy, + contextStore, seen, xssUserChoices, local: ns.local, @@ -272,6 +287,7 @@ var ns = { running: false, policy: null, + contextStore: null, local: null, sync: null, initializing: null, @@ -293,9 +309,35 @@ return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request)); }, + getPolicy(cookieStoreId){ + if ( + ns.contextStore && + ns.contextStore.enabled && + ns.contextStore.policies.hasOwnProperty(cookieStoreId) + ) { + let currentPolicy = ns.contextStore.policies[cookieStoreId]; + debug("id", cookieStoreId, "has cookiestore", currentPolicy); + if (currentPolicy) return currentPolicy; + } + debug("default cookiestore", cookieStoreId); + return ns.policy; + }, + computeChildPolicy({url, contextUrl}, sender) { - let {tab, frameId} = sender; - let policy = ns.policy; + let {tab, frameId, cookieStoreId} = sender; + let tabId = tab ? tab.id : -1; + let topUrl; + if (frameId === 0) { + topUrl = url; + } else if (tab) { + if (!tab.url) tab = TabCache.get(tabId); + if (tab) topUrl = tab.url; + } + if (!topUrl) topUrl = url; + if (!contextUrl) contextUrl = topUrl; + + if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId; + let policy = ns.getPolicy(cookieStoreId); let {isTorBrowser} = ns.local; if (!policy) { console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing); @@ -308,17 +350,6 @@ }; } - let tabId = tab ? tab.id : -1; - let topUrl; - if (frameId === 0) { - topUrl = url; - } else if (tab) { - if (!tab.url) tab = TabCache.get(tabId); - if (tab) topUrl = tab.url; - } - if (!topUrl) topUrl = url; - if (!contextUrl) contextUrl = topUrl; - if (Sites.isInternal(url) || !ns.isEnforced(tabId)) { policy = null; } @@ -384,7 +415,7 @@ await Storage.set("sync", { policy: this.policy.dry() }); - await browser.webRequest.handlerBehaviorChanged() + await browser.webRequest.handlerBehaviorChanged(); } return this.policy; }, @@ -401,6 +432,16 @@ browser.tabs.create({url: url.toString() }); }, + async saveContextStore() { + if (this.contextStore) { + await Storage.set("sync", { + contextStore: this.contextStore.dry() + }); + await browser.webRequest.handlerBehaviorChanged(); + } + return this.contextStore; + }, + async save(obj) { if (obj && obj.storage) { let toBeSaved = { diff --git a/src/manifest.json b/src/manifest.json index 5b45d06..94cad09 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -36,7 +36,8 @@ "webRequest", "webRequestBlocking", "dns", - "" + "", + "contextualIdentities" ], "background": { @@ -58,6 +59,7 @@ "/nscl/common/Sites.js", "/nscl/common/Permissions.js", "/nscl/common/Policy.js", + "/nscl/common/ContextStore.js", "/nscl/common/locale.js", "/nscl/common/Storage.js", "/nscl/common/include.js", diff --git a/src/nscl b/src/nscl index 08593a2..1a0a804 160000 --- a/src/nscl +++ b/src/nscl @@ -1 +1 @@ -Subproject commit 08593a23b46e2616ffadf2836291ef233e62ac89 +Subproject commit 1a0a8045ba82cac3c6f1decd05a91bac8871f7e2 diff --git a/src/ui/options.css b/src/ui/options.css index c126d49..9099945 100644 --- a/src/ui/options.css +++ b/src/ui/options.css @@ -82,6 +82,24 @@ fieldset:disabled { flex: 2 2; } +.per-site-buttons { + display: flex; + flex-flow: row wrap; + justify-content: flex-end; + width: 100%; + text-align: right; + margin: .5em 0 0 0; +} +#btn-clear-container { + margin-inline-start: .5em; +} +#copy-container { + margin-inline: .5em; +} +#copy-container-label { + margin-block: auto; +} + #policy { display: block; margin-top: .5em; @@ -91,6 +109,12 @@ fieldset:disabled { .hide, body:not(.debug) div.debug { display: none; } +#context-store { + display: block; + margin-top: .5em; + min-height: 20em; + width: 90%; +} #debug-tools { padding-left: 2.5em; @@ -110,6 +134,14 @@ fieldset:disabled { font-weight: bold; } +#context-store-error { + background: red; + color: #ff8; + padding: 0; + margin: 0; + font-weight: bold; +} + input, button { font-size: 1em; } diff --git a/src/ui/options.html b/src/ui/options.html index 5c1bbf9..899c472 100644 --- a/src/ui/options.html +++ b/src/ui/options.html @@ -53,6 +53,9 @@ SPDX-License-Identifier: GPL-3.0-or-later + + +
@@ -75,6 +78,10 @@ SPDX-License-Identifier: GPL-3.0-or-later

+ +
+
+ + +
+ +
@@ -198,6 +213,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
+
+ +
+ +
diff --git a/src/ui/options.js b/src/ui/options.js index dfb5d6e..8cefd5a 100644 --- a/src/ui/options.js +++ b/src/ui/options.js @@ -28,6 +28,7 @@ document.querySelector("#version").textContent = _("Version", await UI.init(); let policy = UI.policy; + let contextStore = UI.contextStore; // simple general options @@ -36,7 +37,8 @@ document.querySelector("#version").textContent = _("Version", opt("global", o => { if (o) { policy.enforced = !o.checked; - UI.updateSettings({policy}); + contextStore.setAll({"enforced": !o.checked}); + UI.updateSettings({policy, contextStore}); } let {enforced} = policy; let disabled = !enforced; @@ -51,13 +53,24 @@ document.querySelector("#version").textContent = _("Version", opt("auto", o => { if (o) { policy.autoAllowTop = o.checked; - UI.updateSettings({policy}); + contextStore.setAll({"autoAllowTop": o.checked}); + UI.updateSettings({policy, contextStore}); } return policy.autoAllowTop; }); opt("cascadeRestrictions"); + opt("containers", async o => { + if (o) { + contextStore.enabled = o.checked; + await contextStore.updateContainers(policy); + UI.updateSettings({contextStore}); + } + updateContainersEnabled(); + return contextStore.enabled; + }) + opt("xss"); opt("overrideTorBrowserPolicy"); @@ -138,7 +151,10 @@ document.querySelector("#version").textContent = _("Version", opt("debug", "local", o => { let {checked} = o; document.body.classList.toggle("debug", checked); - if (checked) updateRawPolicyEditor(); + if (checked) { + updateRawPolicyEditor(); + updateRawContextStoreEditor(); + } }); UI.wireChoice("TabGuardMode"); @@ -177,6 +193,12 @@ document.querySelector("#version").textContent = _("Version", let parent = document.getElementById("presets"); let presetsUI = new UI.Sites(parent, {"DEFAULT": true, "TRUSTED": true, "UNTRUSTED": true}); + presetsUI.onChange = () => { + if (policy && contextStore) { // contextStore presets always copy default policy's + contextStore.updatePresets(policy); + UI.updateSettings({policy, contextStore}); + } + } presetsUI.render([""]); window.setTimeout(() => { @@ -188,23 +210,87 @@ document.querySelector("#version").textContent = _("Version", // SITES UI let sitesUI = new UI.Sites(document.getElementById("sites")); - UI.onSettings = () => { - policy = UI.policy; - sitesUI.render(policy.sites); + let containerSelect = document.querySelector("#select-container"); + let containerCopy = document.querySelector("#copy-container"); + var cookieStoreId = containerSelect.value; + var currentPolicy = await UI.getPolicy(cookieStoreId); + + function updateContainersEnabled() { + let containersEnabled = Boolean(contextStore.enabled && browser.contextualIdentities); + document.querySelector("#opt-containers").disabled = !browser.contextualIdentities; + document.querySelector("#opt-containers").checked = contextStore.enabled; + document.querySelector("#select-container").hidden = !containersEnabled; + document.querySelector("#select-container-label").hidden = !containersEnabled; + document.querySelector("#per-site-buttons").style.display = containersEnabled? "flex" : "none"; + } + updateContainersEnabled(); + + async function changeContainer() { + cookieStoreId = containerSelect.value; + currentPolicy = await UI.getPolicy(cookieStoreId); + debug("container change", cookieStoreId, currentPolicy); + sitesUI.clear() + sitesUI.policy = currentPolicy; + sitesUI.render(currentPolicy.sites); + } + containerSelect.onchange = changeContainer; + + async function copyContainer() { + cookieStoreId = containerSelect.value; + let copyCookieStoreId = containerCopy.value; + let copyContainerName = containerCopy.options[containerCopy.selectedIndex].text; + let copyPolicy = await UI.getPolicy(copyCookieStoreId); + if (confirm(`Copying permissions from "${copyContainerName}".\n` + "All site permissions for this container will be removed.\nThis action cannot be reverted.\nDo you want to continue?")) { + sitesUI.clear() + currentPolicy = await UI.replacePolicy(cookieStoreId, new Policy(copyPolicy.dry(true))); + await UI.updateSettings({policy, contextStore}); + sitesUI.policy = currentPolicy; + sitesUI.render(currentPolicy.sites); + } + } + containerCopy.onchange = copyContainer; + + var containers = []; + async function updateContainerOptions() { + let newContainers = [{cookieStoreId: "default", name: "Default"},]; + let identities = browser.contextualIdentities && await browser.contextualIdentities.query({}); + if (identities) { + identities.forEach(({cookieStoreId, name}) => { + newContainers.push({cookieStoreId, name}); + }) + } + if (JSON.stringify(newContainers) == JSON.stringify(containers)) return; + containers = newContainers; + var container_options = "" + for (var container of containers) { + container_options += "" + } + containerSelect.innerHTML = container_options; + containerSelect.value = cookieStoreId; + containerCopy.innerHTML = container_options; + } + containerSelect.onfocus = updateContainerOptions; + containerCopy.onfocus = updateContainerOptions; + if (contextStore.enabled) await updateContainerOptions(); + + UI.onSettings = async () => { + currentPolicy = await UI.getPolicy(cookieStoreId); + sitesUI.render(currentPolicy.sites); } { sitesUI.onChange = () => { if (UI.local.debug) { updateRawPolicyEditor(); + updateRawContextStoreEditor(); } }; - sitesUI.render(policy.sites); + sitesUI.render(currentPolicy.sites); let newSiteForm = document.querySelector("#form-newsite"); let newSiteInput = newSiteForm.newsite; let button = newSiteForm.querySelector("button"); let canAdd = s => { - let match = policy.get(s).siteMatch; + let match = currentPolicy.get(s).siteMatch; return match === null || s.length > match.length; } @@ -222,14 +308,23 @@ document.querySelector("#version").textContent = _("Version", let site = newSiteInput.value.trim(); let valid = Sites.isValid(site); if (valid && canAdd(site)) { - policy.set(site, policy.TRUSTED); - UI.updateSettings({policy}); + currentPolicy.set(site, currentPolicy.TRUSTED); + UI.updateSettings({policy, contextStore}); newSiteInput.value = ""; - sitesUI.render(policy.sites); + sitesUI.render(currentPolicy.sites); sitesUI.hilite(site); sitesUI.onChange(); } }, true); + + document.querySelector("#btn-clear-container").addEventListener("click", async ev => { + if (confirm("All site permissions for this container will be removed.\nThis action cannot be reverted.\nDo you want to continue?")) { + sitesUI.clear() + currentPolicy.sites = Sites.hydrate({}); + await UI.updateSettings({policy, contextStore}); + sitesUI.render(currentPolicy.sites); + } + }); } window.setTimeout(() => { @@ -260,6 +355,7 @@ document.querySelector("#version").textContent = _("Version", try { UI.policy = policy = new Policy(JSON.parse(ed.value)); UI.updateSettings({policy}); + containerSelect.value = "default"; sitesUI.render(policy.sites); ed.className = ""; document.getElementById("policy-error").textContent = ""; @@ -270,4 +366,30 @@ document.querySelector("#version").textContent = _("Version", } } } + + function updateRawContextStoreEditor() { + if (!UI.local.debug) return; + + // RAW POLICY EDITING (debug only) + if (!browser.contextualIdentities) { + document.querySelector("#edit-context-store").style.display = "none"; + return; + } + let contextStoreEditor = document.getElementById("context-store"); + contextStoreEditor.value = JSON.stringify(contextStore.dry(true), null, 2); + if (!contextStoreEditor.onchange) contextStoreEditor.onchange = (e) => { + let ed = e.currentTarget + try { + UI.contextStore = contextStore = new ContextStore(JSON.parse(ed.value)); + UI.updateSettings({contextStore}); + + ed.className = ""; + document.getElementById("context-store-error").textContent = ""; + } catch (e) { + error(e); + ed.className = "error"; + document.getElementById("context-store-error").textContent = e.message; + } + } + } })(); diff --git a/src/ui/popup.css b/src/ui/popup.css index 6353d31..a010c68 100644 --- a/src/ui/popup.css +++ b/src/ui/popup.css @@ -94,6 +94,14 @@ html:not(.mobile) #scrollable { display: none; } +#top > .container-id { + text-align: left; + margin: .1em; + padding: .4em; + font-size: 1.2em; + border:1px solid lightgray; +} + .hider { background: var(--form-color1); box-shadow: inset 0 1px 3px #444; diff --git a/src/ui/popup.html b/src/ui/popup.html index ce97baa..9627baa 100644 --- a/src/ui/popup.html +++ b/src/ui/popup.html @@ -33,6 +33,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ×
+
Default
diff --git a/src/ui/popup.js b/src/ui/popup.js index 52d3362..c3a1a47 100644 --- a/src/ui/popup.js +++ b/src/ui/popup.js @@ -80,6 +80,7 @@ addEventListener("unload", e => { } else { tabId = tab.id; } + let cookieStoreId = pageTab.cookieStoreId; addEventListener("keydown", e => { if (e.code === "Enter") { @@ -108,6 +109,19 @@ addEventListener("unload", e => { }); } + if (UI.contextStore && UI.contextStore.enabled && browser.contextualIdentities) { + try { + let containerName = (await browser.contextualIdentities.get(cookieStoreId)).name; + document.querySelector("#container-id").textContent = containerName; + debug("found container name", containerName, "for cookieStoreId", cookieStoreId); + } catch(err) { + document.querySelector("#container-id").textContent = "Default"; + debug("no container for cookieStoreId", cookieStoreId, "error:", err.message); + } + } else { + document.querySelector("#container-id").style.visibility = 'hidden'; + } + await include("/ui/toolbar.js"); UI.toolbarInit(); { @@ -298,7 +312,9 @@ addEventListener("unload", e => { let justDomains = !UI.local.showFullAddresses; - sitesUI = new UI.Sites(document.getElementById("sites")); + let policy = await UI.getPolicy(cookieStoreId); + debug("popup policy", policy); + sitesUI = new UI.Sites(document.getElementById("sites"), UI.DEF_PRESETS, policy); sitesUI.onChange = (row) => { pendingReload(sitesUI.anyPermissionsChanged()); @@ -330,7 +346,7 @@ addEventListener("unload", e => { typesMap } = sitesUI; typesMap.clear(); - let policySites = UI.policy.sites; + let policySites = policy.sites; let domains = new Map(); let protocols = new Set(); function urlToLabel(url) { diff --git a/src/ui/ui.js b/src/ui/ui.js index 2e5b5b3..e6e865b 100644 --- a/src/ui/ui.js +++ b/src/ui/ui.js @@ -45,6 +45,7 @@ var UI = (() => { "/nscl/common/Sites.js", "/nscl/common/Permissions.js", "/nscl/common/Policy.js", + "/nscl/common/ContextStore.js" ]; this.mobile = UA.mobile; let root = document.documentElement; @@ -58,7 +59,8 @@ var UI = (() => { async settings(m) { if (UI.tabId !== m.tabId) return; UI.policy = new Policy(m.policy); - UI.snapshot = UI.policy.snapshot; + UI.contextStore = new ContextStore(m.contextStore); + UI.snapshot = UI.policy.snapshot+UI.contextStore.snapshot; UI.seen = m.seen; UI.unrestrictedTab = m.unrestrictedTab; UI.xssUserChoices = m.xssUserChoices; @@ -96,7 +98,7 @@ var UI = (() => { await inited; this.initialized = true; - debug("Imported", Policy); + debug("Imported", Policy, ContextStore); }, async pullSettings() { try { @@ -106,10 +108,12 @@ var UI = (() => { browser.runtime.reload(); } }, - async updateSettings({policy, xssUserChoices, unrestrictedTab, local, sync, reloadAffected, command}) { + async updateSettings({policy, contextStore, xssUserChoices, unrestrictedTab, local, sync, reloadAffected, command}) { if (policy) policy = policy.dry(true); + if (contextStore) contextStore = contextStore.dry(true); return await Messages.send("updateSettings", { policy, + contextStore, xssUserChoices, unrestrictedTab, local, @@ -130,13 +134,41 @@ var UI = (() => { async revokeTemp(reloadAffected = false) { let policy = this.policy; Policy.hydrate(policy.dry(), policy); + let contextStore = this.contextStore + ContextStore.hydrate(contextStore.dry(), contextStore) if (this.isDirty(true)) { - await this.updateSettings({policy, reloadAffected}); + await this.updateSettings({policy, contextStore, reloadAffected}); + } + }, + + async getPolicy(cookieStoreId) { + await this.contextStore.updateContainers(this.policy); + if (this.contextStore.enabled && this.contextStore.policies.hasOwnProperty(cookieStoreId)) { + let currentPolicy = this.contextStore.policies[cookieStoreId]; + debug("id", cookieStoreId, "has cookiestore", currentPolicy); + return currentPolicy; + } else { + debug("default cookiestore", cookieStoreId); + return this.policy; + } + }, + + async replacePolicy(cookieStoreId, policy) { + await this.contextStore.updateContainers(this.policy); + if (this.contextStore.policies.hasOwnProperty(cookieStoreId)) { + this.contextStore.policies[cookieStoreId] = policy; + debug("replaced id", cookieStoreId, "with policy", policy); + let currentPolicy = this.contextStore.policies[cookieStoreId]; + return currentPolicy; + } else { + this.policy = policy; + debug("replaced default cookiestore", cookieStoreId, "with policy", policy); + return this.policy; } }, isDirty(reset = false) { - let currentSnapshot = this.policy.snapshot; + let currentSnapshot = this.policy.snapshot+this.contextStore.snapshot; let dirty = currentSnapshot != this.snapshot; if (reset) this.snapshot = currentSnapshot; return dirty; @@ -322,7 +354,7 @@ var UI = (() => { function fireOnChange(sitesUI, data) { if (UI.isDirty(true)) { - UI.updateSettings({policy: UI.policy}); + UI.updateSettings({policy: UI.policy, contextStore: UI.contextStore}); if (sitesUI.onChange) sitesUI.onChange(data, this); } } @@ -397,11 +429,11 @@ var UI = (() => { const INCOGNITO_PRESETS = ["DEFAULT", "T_TRUSTED", "CUSTOM"]; UI.Sites = class { - constructor(parentNode, presets = DEF_PRESETS) { + constructor(parentNode, presets = DEF_PRESETS, policy = null) { this.parentNode = parentNode; - let policy = UI.policy; + this.policy = (policy)? policy : UI.policy; this.uiCount = UI.Sites.count = (UI.Sites.count || 0) + 1; - this.sites = policy.sites; + this.sites = this.policy.sites; this.presets = presets; this.customizing = null; this.typesMap = new Map(); @@ -510,6 +542,11 @@ var UI = (() => { this.customize(null); this.sitesCount = 0; + this.sites = ({ + trusted: [], + untrusted: [], + custom: {}, + }) } siteNeeds(site, type) { @@ -549,7 +586,6 @@ var UI = (() => { let tempToggle = preset.parentNode.querySelector("input.temp"); if (ev.type === "change") { - let {policy} = UI; if (!row._originalPerms) { row._originalPerms = row.perms.clone(); Object.defineProperty(row, "permissionsChanged", { @@ -563,7 +599,7 @@ var UI = (() => { if (!opt) return; let context = opt.value; if (context === "*") context = null; - ({siteMatch, perms, contextMatch} = policy.get(siteMatch, context)); + ({siteMatch, perms, contextMatch} = this.policy.get(siteMatch, context)); if (!context) { row._customPerms = perms; } else if (contextMatch !== context) { @@ -582,7 +618,7 @@ var UI = (() => { let presetValue = preset.value; - let policyPreset = presetValue.startsWith("T_") ? policy[presetValue.substring(2)].tempTwin : policy[presetValue]; + let policyPreset = presetValue.startsWith("T_") ? this.policy[presetValue.substring(2)].tempTwin : this.policy[presetValue]; if (policyPreset && row.perms !== policyPreset) { row.perms = policyPreset; @@ -600,7 +636,7 @@ var UI = (() => { row.perms = policyPreset; delete row._customPerms; if (siteMatch) { - policy.set(siteMatch, policyPreset); + this.policy.set(siteMatch, policyPreset); } else { this.customize(policyPreset, preset, row); } @@ -614,7 +650,7 @@ var UI = (() => { let perms = row._customPerms || (row._customPerms = new Permissions(new Set(row.perms.capabilities), temp)); row.perms = perms; - policy.set(siteMatch, perms); + this.policy.set(siteMatch, perms); this.customize(perms, preset, row); } } @@ -705,7 +741,7 @@ var UI = (() => { let selected = ctxSelect.querySelector("option:checked"); ctxReset.disabled = !(selected && selected.value !== "*"); ctxReset.onclick = () => { - let perms = UI.policy.get(row.siteMatch).perms; + let perms = this.policy.get(row.siteMatch).perms; perms.contextual.delete(row.contextMatch); fireOnChange(this, row); selected.previousElementSibling.selected = true; @@ -883,7 +919,7 @@ var UI = (() => { site = site.site; context = site.context; } - let {siteMatch, perms, contextMatch} = UI.policy.get(site, context); + let {siteMatch, perms, contextMatch} = this.policy.get(site, context); this.append(site, siteMatch, perms, contextMatch); if (!hasTemp) hasTemp = perms.temp; } @@ -936,30 +972,28 @@ var UI = (() => { } async tempTrustAll() { - let {policy} = UI; let changed = 0; for (let row of this.allSiteRows()) { if (row._preset === "DEFAULT") { - policy.set(row._site, policy.TRUSTED.tempTwin); + this.policy.set(row._site, this.policy.TRUSTED.tempTwin); changed++; } } if (changed && UI.isDirty(true)) { - await UI.updateSettings({policy, reloadAffected: true}); + await UI.updateSettings({policy: UI.policy, contextStore: UI.contextStore, reloadAffected: true}); } return changed; } createSiteRow(site, siteMatch, perms, contextMatch = null, sitesCount = this.sitesCount++) { debug("Creating row for site: %s, matching %s / %s, %o", site, siteMatch, contextMatch, perms); - let policy = UI.policy; let row = this.rowTemplate.cloneNode(true); row.sitesCount = sitesCount; let url; try { url = new URL(site); if (siteMatch !== site && siteMatch === url.protocol) { - perms = policy.DEFAULT; + perms = this.policy.DEFAULT; } } catch (e) { if (/^(\w+:)\/*$/.test(site)) { @@ -976,7 +1010,7 @@ var UI = (() => { let {hostname} = url; let overrideDefault = site && url.protocol && site !== url.protocol ? - policy.get(url.protocol, contextMatch) : null; + this.policy.get(url.protocol, contextMatch) : null; if (overrideDefault && !overrideDefault.siteMatch) overrideDefault = null; let domain = tld.getDomain(hostname); @@ -1036,7 +1070,7 @@ var UI = (() => { let getPresetName = perms => { let presetName = "CUSTOM"; for (let p of ["TRUSTED", "UNTRUSTED", "DEFAULT"]) { - let preset = policy[p]; + let preset = this.policy[p]; switch (perms) { case preset: presetName = p; @@ -1078,7 +1112,7 @@ var UI = (() => { for (let p of TEMP_PRESETS) { if (p in this.presets && (unsafeMatch || tempFirst && p === "TRUSTED")) { row.querySelector(`.presets input[value="${p}"]`).parentNode.querySelector("input.temp").checked = true; - perms = policy.TRUSTED.tempTwin; + perms = this.policy.TRUSTED.tempTwin; } } } @@ -1112,9 +1146,8 @@ var UI = (() => { if (site !== row.siteMatch) { this.customize(null); let focused = document.activeElement; - let {policy} = UI; - policy.set(row.siteMatch, policy.DEFAULT); - policy.set(site, row.perms); + this.policy.set(row.siteMatch, this.policy.DEFAULT); + this.policy.set(site, row.perms); for(let r of this.allSiteRows()) { if (r !== row && r.siteMatch === site && r.contextMatch === row.contextMatch) { r.remove();