Add support for container tabs
This commit is contained in:
parent
d11028e63b
commit
3af98f7730
|
@ -1,3 +1,4 @@
|
|||
[submodule "nscl"]
|
||||
path = src/nscl
|
||||
url = ../nscl.git
|
||||
branch = container-tabs
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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",
|
||||
|
|
2
src/nscl
2
src/nscl
|
@ -1 +1 @@
|
|||
Subproject commit 08593a23b46e2616ffadf2836291ef233e62ac89
|
||||
Subproject commit 1a0a8045ba82cac3c6f1decd05a91bac8871f7e2
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
87
src/ui/ui.js
87
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();
|
||||
|
|
Loading…
Reference in New Issue