Merge pull request #1903 from mozilla/proxy-support

Updated per-container proxy support
This commit is contained in:
luke crouch 2021-09-29 10:07:21 -05:00 committed by GitHub
commit 98e3412d68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 321 additions and 59 deletions

View file

@ -1,6 +1,6 @@
module.exports = { module.exports = {
"parserOptions": { "parserOptions": {
"ecmaVersion": 8 "ecmaVersion": 2018
}, },
"env": { "env": {
"browser": true, "browser": true,
@ -18,7 +18,8 @@ module.exports = {
"XPCOMUtils": true, "XPCOMUtils": true,
"OS": true, "OS": true,
"ADDON_UNINSTALL": true, "ADDON_UNINSTALL": true,
"ADDON_DISABLE": true "ADDON_DISABLE": true,
"proxifiedContainers": true
}, },
"plugins": [ "plugins": [
"promise", "promise",

View file

@ -586,7 +586,7 @@ manage things like container crud */
} }
.edit-container-panel fieldset:last-of-type { .edit-container-panel fieldset:last-of-type {
margin-block-end: 0; margin-block-start: 16px;
} }
.edit-container-panel input[type="text"] { .edit-container-panel input[type="text"] {
@ -886,12 +886,11 @@ input {
.site-isolation { .site-isolation {
inset-block-end: auto; inset-block-end: auto;
position: fixed;
} }
.options-label { .options-label {
cursor: pointer; cursor: pointer;
padding-inline-start: 25px; padding-inline-start: 4px;
} }
.manage-assigned-sites-list { .manage-assigned-sites-list {

View file

@ -109,10 +109,10 @@ window.assignManager = {
const siteConfigs = await this.area.get(); const siteConfigs = await this.area.get();
for(const urlKey of Object.keys(siteConfigs)) { for(const urlKey of Object.keys(siteConfigs)) {
if (urlKey.includes("siteContainerMap@@_")) { if (urlKey.includes("siteContainerMap@@_")) {
// For some reason this is stored as string... lets check // For some reason this is stored as string... lets check
// them both as that // them both as that
if (!!userContextId && if (!!userContextId &&
String(siteConfigs[urlKey].userContextId) String(siteConfigs[urlKey].userContextId)
!== String(userContextId)) { !== String(userContextId)) {
continue; continue;
} }
@ -127,7 +127,7 @@ window.assignManager = {
}, },
/* /*
* Looks for abandoned site assignments. If there is no identity with * Looks for abandoned site assignments. If there is no identity with
* the site assignment's userContextId (cookieStoreId), then the assignment * the site assignment's userContextId (cookieStoreId), then the assignment
* is removed. * is removed.
*/ */
@ -136,8 +136,8 @@ window.assignManager = {
const macConfigs = await this.area.get(); const macConfigs = await this.area.get();
for(const configKey of Object.keys(macConfigs)) { for(const configKey of Object.keys(macConfigs)) {
if (configKey.includes("siteContainerMap@@_")) { if (configKey.includes("siteContainerMap@@_")) {
const cookieStoreId = const cookieStoreId =
"firefox-container-" + macConfigs[configKey].userContextId; "firefox-container-" + macConfigs[configKey].userContextId;
const match = identitiesList.find( const match = identitiesList.find(
localIdentity => localIdentity.cookieStoreId === cookieStoreId localIdentity => localIdentity.cookieStoreId === cookieStoreId
); );
@ -146,7 +146,7 @@ window.assignManager = {
continue; continue;
} }
const updatedSiteAssignment = macConfigs[configKey]; const updatedSiteAssignment = macConfigs[configKey];
updatedSiteAssignment.identityMacAddonUUID = updatedSiteAssignment.identityMacAddonUUID =
await identityState.lookupMACaddonUUID(match.cookieStoreId); await identityState.lookupMACaddonUUID(match.cookieStoreId);
await this.set( await this.set(
configKey, configKey,
@ -164,7 +164,7 @@ window.assignManager = {
_neverAsk(m) { _neverAsk(m) {
const pageUrl = m.pageUrl; const pageUrl = m.pageUrl;
if (m.neverAsk === true) { if (m.neverAsk === true) {
// If we have existing data and for some reason it hasn't been // If we have existing data and for some reason it hasn't been
// deleted etc lets update it // deleted etc lets update it
this.storageArea.get(pageUrl).then((siteSettings) => { this.storageArea.get(pageUrl).then((siteSettings) => {
if (siteSettings) { if (siteSettings) {
@ -184,6 +184,17 @@ window.assignManager = {
return true; return true;
}, },
async handleProxifiedRequest(requestInfo) {
// The following blocks potentially dangerous requests for privacy that come without a tabId
if(requestInfo.tabId === -1)
return Utils.getBogusProxy();
const tab = await browser.tabs.get(requestInfo.tabId);
const proxy = await proxifiedContainers.retrieveFromBackground(tab.cookieStoreId);
return proxy;
},
// Before a request is handled by the browser we decide if we should // Before a request is handled by the browser we decide if we should
// route through a different container // route through a different container
async onBeforeRequest(options) { async onBeforeRequest(options) {
@ -212,7 +223,7 @@ window.assignManager = {
const userContextId = this.getUserContextIdFromCookieStore(tab); const userContextId = this.getUserContextIdFromCookieStore(tab);
// https://github.com/mozilla/multi-account-containers/issues/847 // https://github.com/mozilla/multi-account-containers/issues/847
// //
// Handle the case where this request's URL is not assigned to any particular // Handle the case where this request's URL is not assigned to any particular
// container. We must do the following check: // container. We must do the following check:
// //
@ -228,7 +239,7 @@ window.assignManager = {
// - the current tab's container is locked and only allows "www.google.com" // - the current tab's container is locked and only allows "www.google.com"
// - the incoming request is for "www.amazon.com", which has no specific container assignment // - the incoming request is for "www.amazon.com", which has no specific container assignment
// - in this case, we must re-open "www.amazon.com" in a new tab in the default container // - in this case, we must re-open "www.amazon.com" in a new tab in the default container
const siteIsolatedReloadInDefault = const siteIsolatedReloadInDefault =
await this._maybeSiteIsolatedReloadInDefault(siteSettings, tab); await this._maybeSiteIsolatedReloadInDefault(siteSettings, tab);
if (!siteIsolatedReloadInDefault) { if (!siteIsolatedReloadInDefault) {
@ -246,7 +257,7 @@ window.assignManager = {
const openTabId = removeTab ? tab.openerTabId : tab.id; const openTabId = removeTab ? tab.openerTabId : tab.id;
if (!this.canceledRequests[tab.id]) { if (!this.canceledRequests[tab.id]) {
// we decided to cancel the request at this point, register // we decided to cancel the request at this point, register
// canceled request // canceled request
this.canceledRequests[tab.id] = { this.canceledRequests[tab.id] = {
requestIds: { requestIds: {
@ -313,7 +324,7 @@ window.assignManager = {
- As the history won't span from one container to another it - As the history won't span from one container to another it
seems most sane to not try and reopen a tab on history.back() seems most sane to not try and reopen a tab on history.back()
- When users open a new tab themselves we want to make sure we - When users open a new tab themselves we want to make sure we
don't end up with three tabs as per: don't end up with three tabs as per:
https://github.com/mozilla/testpilot-containers/issues/421 https://github.com/mozilla/testpilot-containers/issues/421
If we are coming from an internal url that are used for the new If we are coming from an internal url that are used for the new
tab page (NEW_TAB_PAGES), we can safely close as user is unlikely tab page (NEW_TAB_PAGES), we can safely close as user is unlikely
@ -348,7 +359,7 @@ window.assignManager = {
// I.e. it will be opened in that container anyway, so we don't need to check if the // I.e. it will be opened in that container anyway, so we don't need to check if the
// current tab's container is locked or not. // current tab's container is locked or not.
if (siteSettings) { if (siteSettings) {
return false; return false;
} }
//tab is alredy reopening in the default container //tab is alredy reopening in the default container
@ -363,11 +374,14 @@ window.assignManager = {
init() { init() {
browser.contextMenus.onClicked.addListener((info, tab) => { browser.contextMenus.onClicked.addListener((info, tab) => {
info.bookmarkId ? info.bookmarkId ?
this._onClickedBookmark(info) : this._onClickedBookmark(info) :
this._onClickedHandler(info, tab); this._onClickedHandler(info, tab);
}); });
// Before anything happens we decide if the request should be proxified
browser.proxy.onRequest.addListener(this.handleProxifiedRequest, {urls: ["<all_urls>"]});
// Before a request is handled by the browser we decide if we should // Before a request is handled by the browser we decide if we should
// route through a different container // route through a different container
this.canceledRequests = {}; this.canceledRequests = {};
@ -479,7 +493,7 @@ window.assignManager = {
async _onClickedBookmark(info) { async _onClickedBookmark(info) {
async function _getBookmarksFromInfo(info) { async function _getBookmarksFromInfo(info) {
const [bookmarkTreeNode] = const [bookmarkTreeNode] =
await browser.bookmarks.get(info.bookmarkId); await browser.bookmarks.get(info.bookmarkId);
if (bookmarkTreeNode.type === "folder") { if (bookmarkTreeNode.type === "folder") {
return browser.bookmarks.getChildren(bookmarkTreeNode.id); return browser.bookmarks.getChildren(bookmarkTreeNode.id);
@ -489,9 +503,9 @@ window.assignManager = {
const bookmarks = await _getBookmarksFromInfo(info); const bookmarks = await _getBookmarksFromInfo(info);
for (const bookmark of bookmarks) { for (const bookmark of bookmarks) {
// Some checks on the urls from // Some checks on the urls from
// https://github.com/Rob--W/bookmark-container-tab/ thanks! // https://github.com/Rob--W/bookmark-container-tab/ thanks!
if ( !/^(javascript|place):/i.test(bookmark.url) && if ( !/^(javascript|place):/i.test(bookmark.url) &&
bookmark.type !== "folder") { bookmark.type !== "folder") {
const openInReaderMode = bookmark.url.startsWith("about:reader"); const openInReaderMode = bookmark.url.startsWith("about:reader");
if(openInReaderMode) { if(openInReaderMode) {
@ -569,12 +583,12 @@ window.assignManager = {
actionName = "removed from assigned sites list"; actionName = "removed from assigned sites list";
// remove site isolation if now empty // remove site isolation if now empty
await this._maybeRemoveSiteIsolation(userContextId); await this._maybeRemoveSiteIsolation(userContextId);
} }
if (tabId) { if (tabId) {
const tab = await browser.tabs.get(tabId); const tab = await browser.tabs.get(tabId);
setTimeout(function(){ setTimeout(function(){
browser.tabs.sendMessage(tabId, { browser.tabs.sendMessage(tabId, {
text: `Successfully ${actionName}` text: `Successfully ${actionName}`
}); });
@ -677,17 +691,17 @@ window.assignManager = {
reloadPageInDefaultContainer(url, index, active, openerTabId) { reloadPageInDefaultContainer(url, index, active, openerTabId) {
// To create a new tab in the default container, it is easiest just to omit the // To create a new tab in the default container, it is easiest just to omit the
// cookieStoreId entirely. // cookieStoreId entirely.
// //
// Unfortunately, if you create a new tab WITHOUT a cookieStoreId but WITH an openerTabId, // Unfortunately, if you create a new tab WITHOUT a cookieStoreId but WITH an openerTabId,
// then the new tab automatically inherits the opener tab's cookieStoreId. // then the new tab automatically inherits the opener tab's cookieStoreId.
// I.e. it opens in the wrong container! // I.e. it opens in the wrong container!
// //
// So we have to explicitly pass in a cookieStoreId when creating the tab, since we // So we have to explicitly pass in a cookieStoreId when creating the tab, since we
// are specifying the openerTabId. There doesn't seem to be any way // are specifying the openerTabId. There doesn't seem to be any way
// to look up the default container's cookieStoreId programatically, so sadly // to look up the default container's cookieStoreId programatically, so sadly
// we have to hardcode it here as "firefox-default". This is potentially // we have to hardcode it here as "firefox-default". This is potentially
// not cross-browser compatible. // not cross-browser compatible.
// //
// Note that we could have just omitted BOTH cookieStoreId and openerTabId. But the // Note that we could have just omitted BOTH cookieStoreId and openerTabId. But the
// drawback then is that if the user later closes the newly-created tab, the browser // drawback then is that if the user later closes the newly-created tab, the browser
// does not automatically return to the original opener tab. To get this desired behaviour, // does not automatically return to the original opener tab. To get this desired behaviour,

View file

@ -45,6 +45,10 @@ const backgroundLogic = {
await browser.contextualIdentities.remove(this.cookieStoreId(userContextId)); await browser.contextualIdentities.remove(this.cookieStoreId(userContextId));
} }
assignManager.deleteContainer(userContextId); assignManager.deleteContainer(userContextId);
// Now remove the identity->proxy association in proxifiedContainers also
proxifiedContainers.delete(this.cookieStoreId(userContextId));
return {done: true, userContextId}; return {done: true, userContextId};
}, },
@ -55,8 +59,17 @@ const backgroundLogic = {
this.cookieStoreId(options.userContextId), this.cookieStoreId(options.userContextId),
options.params options.params
); );
proxifiedContainers.set(this.cookieStoreId(options.userContextId), options.proxy);
} else { } else {
donePromise = browser.contextualIdentities.create(options.params); donePromise = browser.contextualIdentities.create(options.params);
// We cannot yet access the new cookieStoreId via this.cookieStoreId(...), so we take this from the resolved promise
donePromise.then((identity) => {
proxifiedContainers.set(identity.cookieStoreId, options.proxy);
}).catch(() => {
// Empty because this should never happen theoretically.
});
} }
await donePromise; await donePromise;
}, },
@ -183,7 +196,7 @@ const backgroundLogic = {
index: -1 index: -1
}); });
} else { } else {
//As we get a blank tab here we will need to await the tabs creation // As we get a blank tab here we will need to await the tabs creation
newWindowObj = await browser.windows.create({ newWindowObj = await browser.windows.create({
}); });
hiddenDefaultTabToClose = true; hiddenDefaultTabToClose = true;

View file

@ -13,6 +13,8 @@
"js/background/messageHandler.js", "js/background/messageHandler.js",
] ]
--> -->
<script type="text/javascript" src="../utils.js"></script>
<script type="text/javascript" src="../proxified-containers.js"></script>
<script type="text/javascript" src="backgroundLogic.js"></script> <script type="text/javascript" src="backgroundLogic.js"></script>
<script type="text/javascript" src="assignManager.js"></script> <script type="text/javascript" src="assignManager.js"></script>
<script type="text/javascript" src="badge.js"></script> <script type="text/javascript" src="badge.js"></script>

View file

@ -307,6 +307,10 @@ const Logic = {
return Utils.userContextId(identity.cookieStoreId); return Utils.userContextId(identity.cookieStoreId);
}, },
cookieStoreId(userContextId) {
return `firefox-container-${userContextId}`;
},
currentCookieStoreId() { currentCookieStoreId() {
const identity = Logic.currentIdentity(); const identity = Logic.currentIdentity();
return identity.cookieStoreId; return identity.cookieStoreId;
@ -1348,8 +1352,9 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
params: { params: {
name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(), name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(),
icon: formValues.get("container-icon") || DEFAULT_ICON, icon: formValues.get("container-icon") || DEFAULT_ICON,
color: formValues.get("container-color") || DEFAULT_COLOR, color: formValues.get("container-color") || DEFAULT_COLOR
} },
proxy: proxifiedContainers.parseProxy(document.getElementById("edit-container-panel-proxy").value) || Utils.DEFAULT_PROXY
} }
}); });
await Logic.refreshIdentities(); await Logic.refreshIdentities();
@ -1423,6 +1428,37 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
iconInput.checked = iconInput.value === identity.icon; iconInput.checked = iconInput.value === identity.icon;
}); });
// Clear the proxy field before doing the retrieval requests below
document.querySelector("#edit-container-panel-proxy").value = "";
const edit_proxy_dom = function(result) {
const proxyInput = document.querySelector("#edit-container-panel-proxy");
if (result.type === "direct" || typeof result.type === "undefined") {
proxyInput.value = "";
return;
}
proxyInput.value = `${result.type}://${result.host}:${result.port}`;
};
proxifiedContainers.retrieve(identity.cookieStoreId).then((result) => {
edit_proxy_dom(result.proxy);
}, (error) => {
if(error.error === "uninitialized" || error.error === "doesnotexist") {
proxifiedContainers.set(identity.cookieStoreId, Utils.DEFAULT_PROXY, error.error === "uninitialized").then((result) => {
edit_proxy_dom(result);
}, (error) => {
proxifiedContainers.report_proxy_error(error, "popup.js: unexpected set(...) error");
}).catch((error) => {
proxifiedContainers.report_proxy_error(error, "popup.js: unexpected set(...) exception");
});
}
else {
proxifiedContainers.report_proxy_error(error, "popup.js: unknown error");
}
}).catch((err) => {
proxifiedContainers.report_proxy_error(err, "popup.js: unexpected retrieve error");
});
const deleteButton = document.getElementById("delete-container-button"); const deleteButton = document.getElementById("delete-container-button");
Utils.addEnterHandler(deleteButton, () => { Utils.addEnterHandler(deleteButton, () => {
Logic.showPanel(P_CONTAINER_DELETE, identity); Logic.showPanel(P_CONTAINER_DELETE, identity);

View file

@ -0,0 +1,147 @@
// This object allows other scripts to access the list mapping containers to their proxies
proxifiedContainers = {
// Slightly modified version of 'retrieve' which returns a direct proxy whenever an error is met.
retrieveFromBackground(cookieStoreId = null) {
return new Promise((resolve, reject) => {
proxifiedContainers.retrieve(cookieStoreId).then((success) => {
resolve(success.proxy);
}, function() {
resolve(Utils.DEFAULT_PROXY);
}).catch((error) => {
reject(error);
});
});
},
report_proxy_error(error, identifier = null) {
// Currently I print to console but this is inefficient
const relevant_id_str = identifier === null ? "" : ` call supplied with id: ${identifier.toString()}`;
browser.extension.getBackgroundPage().console.log(`proxifiedContainers error occured ${relevant_id_str}: ${JSON.stringify(error)}`);
},
// Resolves to a proxy object which can be used in the return of the listener required for browser.proxy.onRequest.addListener
retrieve(cookieStoreId = null) {
return new Promise((resolve, reject) => {
browser.storage.local.get("proxifiedContainersKey").then((results) => {
// Steps to test:
// 1. Is result empty? If so we must inform the caller to intialize proxifiedContainersStore with some initial info.
// 2. Is cookieStoreId null? This means the caller probably wants everything currently in the proxifiedContainersStore object store
// 3. If there doesn't exist an entry for the associated cookieStoreId, inform the caller of this
// 4. Normal operation - if the cookieStoreId exists in the map, we can simply resolve with the correct proxy value
const results_array = results["proxifiedContainersKey"];
if (Object.getOwnPropertyNames(results).length === 0) {
reject({
error: "uninitialized",
message: ""
});
} else if (cookieStoreId === null) {
resolve(results_array);
} else {
const val = results_array.find(o => o.cookieStoreId === cookieStoreId);
if (typeof val !== "object" || val === null) {
reject({
error: "doesnotexist",
message: ""
});
} else {
resolve(val);
}
}
}, (error) => {
reject({
error: "internal",
message: error
});
}).catch((error) => {
proxifiedContainers.report_proxy_error(error, "proxified-containers.js: error 1");
});
});
},
set(cookieStoreId, proxy, initialize = false) {
return new Promise((resolve, reject) => {
if (initialize === true) {
const proxifiedContainersStore = [];
proxifiedContainersStore.push({
cookieStoreId: cookieStoreId,
proxy: proxy
});
browser.storage.local.set({
proxifiedContainersKey: proxifiedContainersStore
});
resolve(proxy);
}
// Assumes proxy is a properly formatted object
proxifiedContainers.retrieve().then((proxifiedContainersStore) => {
let index = proxifiedContainersStore.findIndex(i => i.cookieStoreId === cookieStoreId);
if (index === -1) {
proxifiedContainersStore.push({
cookieStoreId: cookieStoreId,
proxy: proxy
});
index = proxifiedContainersStore.length - 1;
} else {
proxifiedContainersStore[index] = {
cookieStoreId: cookieStoreId,
proxy: proxy
};
}
browser.storage.local.set({
proxifiedContainersKey: proxifiedContainersStore
});
resolve(proxifiedContainersStore[index]);
}, (errorObj) => {
reject(errorObj);
}).catch((error) => {
throw error;
});
});
},
//Parses a proxy description string of the format type://host[:port] or type://username:password@host[:port] (port is optional)
parseProxy(proxy_str) {
const proxyRegexp = /(?<type>(https?)|(socks4?)):\/\/(\b(?<username>\w+):(?<password>\w+)@)?(?<host>((?:\d{1,3}\.){3}\d{1,3}\b)|(\b([\w.-]+)(\.([\w.-]+))+))(:(?<port>\d+))?/;
if (proxyRegexp.test(proxy_str) !== true) {
return false;
}
const matches = proxyRegexp.exec(proxy_str);
return matches.groups;
},
// Deletes the proxy information object for a specified cookieStoreId [useful for cleaning]
delete(cookieStoreId) {
return new Promise((resolve, reject) => {
// Assumes proxy is a properly formatted object
proxifiedContainers.retrieve().then((proxifiedContainersStore) => {
const index = proxifiedContainersStore.findIndex(i => i.cookieStoreId === cookieStoreId);
if (index === -1) {
reject({error: "not-found", message: `Container '${cookieStoreId}' not found.`});
} else {
proxifiedContainersStore.splice(index, 1);
}
browser.storage.local.set({
proxifiedContainersKey: proxifiedContainersStore
});
resolve();
}, (errorObj) => {
reject(errorObj);
}).catch((error) => {
throw error;
});
});
}
};

View file

@ -1,3 +1,5 @@
/*global getBogusProxy */
const DEFAULT_FAVICON = "/img/blank-favicon.svg"; const DEFAULT_FAVICON = "/img/blank-favicon.svg";
// TODO use export here instead of globals // TODO use export here instead of globals
@ -19,6 +21,40 @@ const Utils = {
imageElement.addEventListener("load", loadListener); imageElement.addEventListener("load", loadListener);
return imageElement; return imageElement;
}, },
// See comment in PR #313 - so far the (hacky) method being used to block proxies is to produce a sufficiently long random address
getBogusProxy() {
const bogusFailover = 1;
const bogusType = "socks4";
const bogusPort = 9999;
const bogusUsername = "foo";
if(typeof window.Utils.pregeneratedString !== "undefined")
{
return {type:bogusType, host:`w.${window.Utils.pregeneratedString}.coo`, port:bogusPort, username:bogusUsername, failoverTimeout:bogusFailover};
}
else
{
// Initialize Utils.pregeneratedString
window.Utils.pregeneratedString = "";
// We generate a cryptographically random string (of length specified in bogusLength), but we only do so once - thus negating any time delay caused
const bogusLength = 8;
const array = new Uint8Array(bogusLength);
window.crypto.getRandomValues(array);
for(let i = 0; i < bogusLength; i++)
{
const s = array[i].toString(16);
if(s.length === 1)
window.Utils.pregeneratedString += `0${s}`;
else
window.Utils.pregeneratedString += s;
}
// The only issue with this approach is that if (for some unknown reason) pregeneratedString is not saved, it will result in an infinite loop - but better than a privacy leak!
return getBogusProxy();
}
},
/** /**
* Escapes any occurances of &, ", <, > or / with XML entities. * Escapes any occurances of &, ", <, > or / with XML entities.
* *
@ -62,7 +98,7 @@ const Utils = {
} }
return false; return false;
}, },
addEnterHandler(element, handler) { addEnterHandler(element, handler) {
element.addEventListener("click", (e) => { element.addEventListener("click", (e) => {
handler(e); handler(e);
@ -82,7 +118,7 @@ const Utils = {
handler(e); handler(e);
} }
}); });
}, },
userContextId(cookieStoreId = "") { userContextId(cookieStoreId = "") {
const userContextId = cookieStoreId.replace("firefox-container-", ""); const userContextId = cookieStoreId.replace("firefox-container-", "");
@ -102,10 +138,10 @@ const Utils = {
async reloadInContainer(url, currentUserContextId, newUserContextId, tabIndex, active) { async reloadInContainer(url, currentUserContextId, newUserContextId, tabIndex, active) {
return await browser.runtime.sendMessage({ return await browser.runtime.sendMessage({
method: "reloadInContainer", method: "reloadInContainer",
url, url,
currentUserContextId, currentUserContextId,
newUserContextId, newUserContextId,
tabIndex, tabIndex,
active active
}); });
}, },
@ -116,22 +152,30 @@ const Utils = {
if (currentTab.cookieStoreId !== identity.cookieStoreId) { if (currentTab.cookieStoreId !== identity.cookieStoreId) {
return await browser.runtime.sendMessage({ return await browser.runtime.sendMessage({
method: "assignAndReloadInContainer", method: "assignAndReloadInContainer",
url: currentTab.url, url: currentTab.url,
currentUserContextId: false, currentUserContextId: false,
newUserContextId: assignedUserContextId, newUserContextId: assignedUserContextId,
tabIndex: currentTab.index +1, tabIndex: currentTab.index +1,
active:currentTab.active active:currentTab.active
}); });
} }
await Utils.setOrRemoveAssignment( await Utils.setOrRemoveAssignment(
currentTab.id, currentTab.id,
currentTab.url, currentTab.url,
assignedUserContextId, assignedUserContextId,
false false
); );
} }
}; };
window.Utils = Utils;
window.Utils = Utils; // The following creates a fake (but convincing) constant Utils.DEFAULT_PROXY
Object.defineProperty(window.Utils, "DEFAULT_PROXY", {
value: Object.freeze({type: "direct"}),
writable: false,
enumerable: true,
// Setting configurable to false avoids deletion of Utils.DEFAULT_PROXY
configurable: false
});

View file

@ -28,7 +28,8 @@
"unlimitedStorage", "unlimitedStorage",
"tabs", "tabs",
"webRequestBlocking", "webRequestBlocking",
"webRequest" "webRequest",
"proxy"
], ],
"optional_permissions": [ "optional_permissions": [
"bookmarks" "bookmarks"

View file

@ -229,7 +229,7 @@
<span class="menu-text truncate-text">www.mozillllllllllllllllllllllllllllllllllllla.org</span> <span class="menu-text truncate-text">www.mozillllllllllllllllllllllllllllllllllllla.org</span>
<img class="trash-button" src="/img/container-close-tab.svg" /> <img class="trash-button" src="/img/container-close-tab.svg" />
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<div class="bottom-btn keyboard-nav hover-highlight" id="manage-container-link" tabindex="0" data-i18n-message-id="manageThisContainer"></div> <div class="bottom-btn keyboard-nav hover-highlight" id="manage-container-link" tabindex="0" data-i18n-message-id="manageThisContainer"></div>
@ -277,6 +277,10 @@
<fieldset id="edit-container-panel-choose-icon" class="radio-choice"> <fieldset id="edit-container-panel-choose-icon" class="radio-choice">
<legend class="form-header" data-i18n-message-id="icon"></legend> <legend class="form-header" data-i18n-message-id="icon"></legend>
</fieldset> </fieldset>
<fieldset>
<legend>Proxy (Optional)</legend>
<input type="text" name="container-proxy" id="edit-container-panel-proxy" maxlength="50" placeholder="type://host:port"/>
</fieldset>
</form> </form>
<div id="edit-container-options"> <div id="edit-container-options">
<div class="options-header" data-i18n-message-id="options"></div> <div class="options-header" data-i18n-message-id="options"></div>
@ -332,6 +336,7 @@
</div> </div>
<script src="js/utils.js"></script> <script src="js/utils.js"></script>
<script src="js/proxified-containers.js"></script>
<script src="js/popup.js"></script> <script src="js/popup.js"></script>
</body> </body>
</html> </html>

View file

@ -1,12 +1,12 @@
module.exports = { module.exports = {
env: { env: {
"node": true, "node": true,
"mocha": true "mocha": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 2018 "ecmaVersion": 2018
}, },
"rules": { "rules": {
"no-restricted-globals": ["error", "browser"] "no-restricted-globals": ["error", "browser"]
} }
} };