Skip to content

Commit 3af98f7

Browse files
committed
Add support for container tabs
1 parent d11028e commit 3af98f7

File tree

14 files changed

+373
-68
lines changed

14 files changed

+373
-68
lines changed

.gitmodules

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
[submodule "nscl"]
22
path = src/nscl
33
url = ../nscl.git
4+
branch = container-tabs

src/bg/LifeCycle.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ var LifeCycle = (() => {
104104
let {url} = tab;
105105
let {cypherText, key, iv} = await encrypt(JSON.stringify({
106106
policy: ns.policy.dry(true),
107+
contextStore: ns.contextStore.dry(true),
107108
allSeen,
108109
unrestrictedTabs: [...ns.unrestrictedTabs]
109110
}));
@@ -188,14 +189,15 @@ var LifeCycle = (() => {
188189
iv
189190
}, key, cypherText
190191
);
191-
let {policy, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
192+
let {policy, contextStore, allSeen, unrestrictedTabs} = JSON.parse(new TextDecoder().decode(encoded));
192193
if (!policy) {
193194
throw new error("Ephemeral policy not found in survival tab %s!", tabId);
194195
}
195196
ns.unrestrictedTabs = new Set(unrestrictedTabs);
196197
destroyIfNeeded();
197198
if (ns.initializing) await ns.initializing;
198199
ns.policy = new Policy(policy);
200+
ns.contextStore = new ContextStore(contextStore);
199201
await Promise.all(
200202
Object.entries(allSeen).map(
201203
async ([tabId, seen]) => {
@@ -274,6 +276,17 @@ var LifeCycle = (() => {
274276
if (changed) {
275277
await ns.savePolicy();
276278
}
279+
if (ns.contextStore) {
280+
changed = false;
281+
for (let k of Object.keys(ns.contextStore.policies)){
282+
for (let p of ns.contextStore.policies[k].getPresets(presetNames)) {
283+
if (callback(p)) changed = true;
284+
}
285+
}
286+
if (changed) {
287+
await ns.saveContextStore();
288+
}
289+
}
277290
};
278291

279292
let configureNewCap = async (cap, presetNames, capsFilter) => {

src/bg/RequestGuard.js

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,10 @@ var RequestGuard = (() => {
280280
}
281281
let key = [siteKey, origin][ret.option || 0];
282282
if (!key) return;
283+
let cookieStoreId = sender.tab && sender.tab.cookieStoreId;
284+
let policy = ns.getPolicy(cookieStoreId);
283285
let contextUrl = sender.tab.url || documentUrl;
284-
let {siteMatch, contextMatch, perms} = ns.policy.get(key, contextUrl);
286+
let {siteMatch, contextMatch, perms} = policy.get(key, contextUrl);
285287
let {capabilities} = perms;
286288
if (!capabilities.has(policyType)) {
287289
let temp = sender.tab.incognito; // we don't want to store in PBM
@@ -294,8 +296,9 @@ var RequestGuard = (() => {
294296
perms = new Permissions(new Set(capabilities), false, contextualSites);
295297
}
296298
*/
297-
ns.policy.set(key, perms);
299+
policy.set(key, perms);
298300
await ns.savePolicy();
301+
await ns.saveContextStore();
299302
}
300303
return {enable: key};
301304
},
@@ -397,7 +400,7 @@ var RequestGuard = (() => {
397400
};
398401

399402
function intersectCapabilities(perms, request) {
400-
let {frameId, frameAncestors, tabId} = request;
403+
let {frameId, frameAncestors, tabId, cookieStoreId} = request;
401404
if (frameId !== 0 && ns.sync.cascadeRestrictions) {
402405
let topUrl = frameAncestors && frameAncestors.length
403406
&& frameAncestors[frameAncestors.length - 1].url;
@@ -406,7 +409,8 @@ var RequestGuard = (() => {
406409
if (tab) topUrl = tab.url;
407410
}
408411
if (topUrl) {
409-
return ns.policy.cascadeRestrictions(perms, topUrl).capabilities;
412+
let policy = ns.getPolicy(cookieStoreId);
413+
return policy.cascadeRestrictions(perms, topUrl).capabilities;
410414
}
411415
}
412416
return perms.capabilities;
@@ -468,9 +472,10 @@ var RequestGuard = (() => {
468472

469473
function checkLANRequest(request) {
470474
if (!ns.isEnforced(request.tabId)) return ALLOW;
471-
let {originUrl, url} = request;
475+
let {originUrl, url, cookieStoreId} = request;
476+
let policy = ns.getPolicy(cookieStoreId);
472477
if (originUrl && !Sites.isInternal(originUrl) && url.startsWith("http") &&
473-
!ns.policy.can(originUrl, "lan", ns.policyContext(request))) {
478+
!policy.can(originUrl, "lan", ns.policyContext(request))) {
474479
// we want to block any request whose origin resolves to at least one external WAN IP
475480
// and whose destination resolves to at least one LAN IP
476481
let {proxyInfo} = request; // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo
@@ -504,7 +509,6 @@ var RequestGuard = (() => {
504509
normalizeRequest(request);
505510
initPendingRequest(request);
506511

507-
let {policy} = ns
508512
let {tabId, type, url, originUrl} = request;
509513

510514
if (type in policyTypesMap) {
@@ -527,7 +531,9 @@ var RequestGuard = (() => {
527531
}
528532
return ALLOW;
529533
}
534+
let {cookieStoreId} = request;
530535
let isFetch = "fetch" === policyType;
536+
let policy = ns.getPolicy(cookieStoreId);
531537

532538
if ((isFetch || "frame" === policyType) &&
533539
(((isFetch && !originUrl
@@ -639,12 +645,12 @@ var RequestGuard = (() => {
639645
let promises = [];
640646

641647
pending.headersProcessed = true;
642-
let {url, documentUrl, tabId, responseHeaders, type} = request;
648+
let {url, documentUrl, tabId, cookieStoreId, responseHeaders, type} = request;
643649
let isMainFrame = type === "main_frame";
644650
try {
645651
let capabilities;
646652
if (ns.isEnforced(tabId)) {
647-
let policy = ns.policy;
653+
let policy = ns.getPolicy(cookieStoreId);
648654
let {perms} = policy.get(url, ns.policyContext(request));
649655
if (isMainFrame) {
650656
if (policy.autoAllowTop && perms === policy.DEFAULT) {
@@ -769,8 +775,8 @@ var RequestGuard = (() => {
769775
}
770776

771777
function injectPolicyScript(details) {
772-
let {url, tabId, frameId} = details;
773-
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId});
778+
let {url, tabId, frameId, cookieStoreId} = details;
779+
let policy = ns.computeChildPolicy({url}, {tab: {id: tabId}, frameId, cookieStoreId});
774780
policy.navigationURL = url;
775781
let debugStatement = ns.local.debug ? `
776782
let mark = Date.now() + ":" + Math.random();

src/bg/Settings.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ var Settings = {
9898
async update(settings) {
9999
let {
100100
policy,
101+
contextStore,
101102
xssUserChoices,
102103
tabId,
103104
unrestrictedTab,
@@ -146,6 +147,7 @@ var Settings = {
146147
if (settings.sync === null) {
147148
// user is resetting options
148149
policy = this.createDefaultDryPolicy();
150+
contextStore = new ContextStore().dry();
149151

150152
// overriden defaults when user manually resets options
151153

@@ -170,6 +172,12 @@ var Settings = {
170172
await ns.savePolicy();
171173
}
172174

175+
if (contextStore) {
176+
let newContextStore = new ContextStore(contextStore);
177+
ns.contextStore = newContextStore
178+
await ns.saveContextStore();
179+
}
180+
173181
if (typeof unrestrictedTab === "boolean") {
174182
ns.unrestrictedTabs[unrestrictedTab ? "add" : "delete"](tabId);
175183
}
@@ -213,6 +221,7 @@ var Settings = {
213221
export() {
214222
return JSON.stringify({
215223
policy: ns.policy.dry(),
224+
contextStore: ns.contextStore.dry(),
216225
local: ns.local,
217226
sync: ns.sync,
218227
xssUserChoices: XSS.getUserChoices(),

src/bg/main.js

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@
8282
}
8383
}
8484

85+
if (!ns.contextStore) { // it could have been already retrieved by LifeCycle
86+
let contextStoreData = (await Storage.get("sync", "contextStore")).contextStore;
87+
if (contextStoreData) {
88+
ns.contextStore = new ContextStore(contextStoreData);
89+
await ns.contextStore.updateContainers(ns.policy);
90+
} else {
91+
log("No container data found. Initializing new policies.")
92+
ns.contextStore = new ContextStore();
93+
await ns.contextStore.updateContainers(ns.policy);
94+
await ns.saveContextStore();
95+
}
96+
}
97+
8598
let {isTorBrowser} = ns.local;
8699
Sites.onionSecure = isTorBrowser;
87100

@@ -178,11 +191,13 @@
178191
tabId = -1
179192
}) {
180193
let policy = ns.policy.dry(true);
194+
let contextStore = ns.contextStore.dry(true);
181195
let seen = tabId !== -1 ? await ns.collectSeen(tabId) : null;
182196
let xssUserChoices = await XSS.getUserChoices();
183197
let anonymyzedTabInfo =
184198
await Messages.send("settings", {
185199
policy,
200+
contextStore,
186201
seen,
187202
xssUserChoices,
188203
local: ns.local,
@@ -272,6 +287,7 @@
272287
var ns = {
273288
running: false,
274289
policy: null,
290+
contextStore: null,
275291
local: null,
276292
sync: null,
277293
initializing: null,
@@ -293,21 +309,22 @@
293309
return !this.isEnforced(request.tabId) || this.policy.can(request.url, capability, this.policyContext(request));
294310
},
295311

296-
computeChildPolicy({url, contextUrl}, sender) {
297-
let {tab, frameId} = sender;
298-
let policy = ns.policy;
299-
let {isTorBrowser} = ns.local;
300-
if (!policy) {
301-
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
302-
return {
303-
permissions: new Permissions(Permissions.DEFAULT).dry(),
304-
unrestricted: false,
305-
cascaded: false,
306-
fallback: true,
307-
isTorBrowser,
308-
};
312+
getPolicy(cookieStoreId){
313+
if (
314+
ns.contextStore &&
315+
ns.contextStore.enabled &&
316+
ns.contextStore.policies.hasOwnProperty(cookieStoreId)
317+
) {
318+
let currentPolicy = ns.contextStore.policies[cookieStoreId];
319+
debug("id", cookieStoreId, "has cookiestore", currentPolicy);
320+
if (currentPolicy) return currentPolicy;
309321
}
322+
debug("default cookiestore", cookieStoreId);
323+
return ns.policy;
324+
},
310325

326+
computeChildPolicy({url, contextUrl}, sender) {
327+
let {tab, frameId, cookieStoreId} = sender;
311328
let tabId = tab ? tab.id : -1;
312329
let topUrl;
313330
if (frameId === 0) {
@@ -319,6 +336,20 @@
319336
if (!topUrl) topUrl = url;
320337
if (!contextUrl) contextUrl = topUrl;
321338

339+
if (!cookieStoreId && tab) cookieStoreId = tab.cookieStoreId;
340+
let policy = ns.getPolicy(cookieStoreId);
341+
let {isTorBrowser} = ns.local;
342+
if (!policy) {
343+
console.log("Policy is null, initializing: %o, sending fallback.", ns.initializing);
344+
return {
345+
permissions: new Permissions(Permissions.DEFAULT).dry(),
346+
unrestricted: false,
347+
cascaded: false,
348+
fallback: true,
349+
isTorBrowser,
350+
};
351+
}
352+
322353
if (Sites.isInternal(url) || !ns.isEnforced(tabId)) {
323354
policy = null;
324355
}
@@ -384,7 +415,7 @@
384415
await Storage.set("sync", {
385416
policy: this.policy.dry()
386417
});
387-
await browser.webRequest.handlerBehaviorChanged()
418+
await browser.webRequest.handlerBehaviorChanged();
388419
}
389420
return this.policy;
390421
},
@@ -401,6 +432,16 @@
401432
browser.tabs.create({url: url.toString() });
402433
},
403434

435+
async saveContextStore() {
436+
if (this.contextStore) {
437+
await Storage.set("sync", {
438+
contextStore: this.contextStore.dry()
439+
});
440+
await browser.webRequest.handlerBehaviorChanged();
441+
}
442+
return this.contextStore;
443+
},
444+
404445
async save(obj) {
405446
if (obj && obj.storage) {
406447
let toBeSaved = {

src/manifest.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"webRequest",
3737
"webRequestBlocking",
3838
"dns",
39-
"<all_urls>"
39+
"<all_urls>",
40+
"contextualIdentities"
4041
],
4142

4243
"background": {
@@ -58,6 +59,7 @@
5859
"/nscl/common/Sites.js",
5960
"/nscl/common/Permissions.js",
6061
"/nscl/common/Policy.js",
62+
"/nscl/common/ContextStore.js",
6163
"/nscl/common/locale.js",
6264
"/nscl/common/Storage.js",
6365
"/nscl/common/include.js",

src/ui/options.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,24 @@ fieldset:disabled {
8282
flex: 2 2;
8383
}
8484

85+
.per-site-buttons {
86+
display: flex;
87+
flex-flow: row wrap;
88+
justify-content: flex-end;
89+
width: 100%;
90+
text-align: right;
91+
margin: .5em 0 0 0;
92+
}
93+
#btn-clear-container {
94+
margin-inline-start: .5em;
95+
}
96+
#copy-container {
97+
margin-inline: .5em;
98+
}
99+
#copy-container-label {
100+
margin-block: auto;
101+
}
102+
85103
#policy {
86104
display: block;
87105
margin-top: .5em;
@@ -91,6 +109,12 @@ fieldset:disabled {
91109
.hide, body:not(.debug) div.debug {
92110
display: none;
93111
}
112+
#context-store {
113+
display: block;
114+
margin-top: .5em;
115+
min-height: 20em;
116+
width: 90%;
117+
}
94118

95119
#debug-tools {
96120
padding-left: 2.5em;
@@ -110,6 +134,14 @@ fieldset:disabled {
110134
font-weight: bold;
111135
}
112136

137+
#context-store-error {
138+
background: red;
139+
color: #ff8;
140+
padding: 0;
141+
margin: 0;
142+
font-weight: bold;
143+
}
144+
113145
input, button {
114146
font-size: 1em;
115147
}

0 commit comments

Comments
 (0)