Add support for container tabs

This commit is contained in:
Aaron Kollasch 2021-11-06 16:07:09 -04:00
parent d11028e63b
commit 3af98f7730
No known key found for this signature in database
GPG Key ID: F813CAE853E39883
14 changed files with 373 additions and 68 deletions

1
.gitmodules vendored
View File

@ -1,3 +1,4 @@
[submodule "nscl"]
path = src/nscl
url = ../nscl.git
branch = container-tabs

View File

@ -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) => {

View File

@ -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();

View File

@ -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(),

View File

@ -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 = {

View File

@ -36,7 +36,8 @@
"webRequest",
"webRequestBlocking",
"dns",
"<all_urls>"
"<all_urls>",
"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",

@ -1 +1 @@
Subproject commit 08593a23b46e2616ffadf2836291ef233e62ac89
Subproject commit 1a0a8045ba82cac3c6f1decd05a91bac8871f7e2

View File

@ -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;
}

View File

@ -53,6 +53,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<span id="enforceOnRestart-opt">
<input type="checkbox" id="opt-enforceOnRestart"><label for="opt-enforceOnRestart" id="lbl-enforceOnRestart">__MSG_EnforceOnRestart__</label>
</span>
<span id="containers-opt">
<input type="checkbox" class="enforcement_required" id="opt-containers"><label for="opt-containers" id="lbl-containers">Enable support for container tabs</label>
</span>
</div>
<div class="opt-group">
<span id="auto-opt">
@ -75,6 +78,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
<h3 class="flextabs__tab"><button class="flextabs__toggle enforcement_required">__MSG_SectionSitePermissions__</button></h3>
<div class="flextabs__content">
<label for="select-container" id="select-container-label">Choose a container:</label>
<select name="select-container" id="select-container">
<option value="default">Default</option>
</select>
<section class="sect-sites">
<form id="form-newsite" class="browser-style" >
<label id="newsite-label" for="newsite" accesskey="__MSG_WebAddress_accesskey__">__MSG_WebAddress__</label><input name="newsite" id="newsite" type="text" placeholder="[https://]noscript.net"
@ -85,6 +92,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="cssload-whirlpool"></div>
</div>
</div>
<div class="per-site-buttons" id="per-site-buttons">
<label for="copy-container" id="copy-container-label">Copy permissions from container:</label>
<select name="copy-container" id="copy-container">
<option value="default">Default</option>
</select>
<div style="inline-size:0;margin-inline:.5em;border:1px solid #888"></div>
<button id="btn-clear-container">Clear permissions for this container</button>
</div>
</section>
</div>
@ -198,6 +213,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div id="policy-error"></div>
<textarea id="policy" class="browser-style">
</textarea>
<div id="edit-context-store">
<label for="contextStore">ContextStore:</label>
<div id="context-store-error"></div>
<textarea id="context-store" class="browser-style">
</textarea>
</div>
</div>
</div>

View File

@ -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 += "<option value=" + container.cookieStoreId + ">" + container.name + "</option>"
}
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;
}
}
}
})();

View File

@ -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;

View File

@ -33,6 +33,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="hider-close">×</a>
</div>
<div class="spacer"></div>
<div id="container-id" class="container-id">Default</div>
<button aria-role="button" id="enforce" class="toggle icon"></button>
<button aria-role="button" id="enforce-tab" class="toggle icon"></button>
<button aria-role="button" id="temp-trust-page" class="toggle icon" title="__MSG_TempTrustPage__"></button>

View File

@ -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) {

View File

@ -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();