Wildcard subdomains - e.g. *.google.com

This commit is contained in:
Francis McKenzie 2019-08-06 00:07:45 +02:00
parent dc9e8f6399
commit e6eff75677
13 changed files with 505 additions and 116 deletions

View file

@ -26,7 +26,7 @@
"stylelint-config-standard": "^16.0.0",
"stylelint-order": "^0.3.0",
"web-ext": "^2.9.3",
"webextensions-jsdom": "^1.1.0"
"webextensions-jsdom": "^1.1.1"
},
"homepage": "https://github.com/mozilla/multi-account-containers#readme",
"license": "MPL-2.0",

View file

@ -832,6 +832,11 @@ span ~ .panel-header-text {
flex: 1;
}
/* Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473 */
.assigned-sites-list .hostname .subdomain:hover {
text-decoration: underline;
}
.radio-choice > .radio-container {
align-items: center;
block-size: 29px;

View file

@ -3,6 +3,8 @@ module.exports = {
"../../.eslintrc.js"
],
"globals": {
"utils": false,
"wildcardManager": false,
"assignManager": true,
"badge": true,
"backgroundLogic": true,

View file

@ -6,115 +6,150 @@ const assignManager = {
MENU_MOVE_ID: "move-to-new-window-container",
OPEN_IN_CONTAINER: "open-bookmark-in-container-tab",
storageArea: {
area: browser.storage.local,
area: new utils.NamedStore("siteContainerMap"),
exemptedTabs: {},
getSiteStoreKey(pageUrl) {
const url = new window.URL(pageUrl);
const storagePrefix = "siteContainerMap@@_";
if (url.port === "80" || url.port === "443") {
return `${storagePrefix}${url.hostname}`;
async matchUrl(pageUrl) {
const siteId = backgroundLogic.getSiteIdFromUrl(pageUrl);
// Try exact match
let siteSettings = await this.get(siteId);
if (!siteSettings) {
// Try wildcard match
const wildcard = await wildcardManager.match(siteId);
if (wildcard) {
siteSettings = await this.get(wildcard);
}
}
return siteSettings;
},
create(siteId, userContextId, options = {}) {
const siteSettings = { userContextId, neverAsk:!!options.neverAsk };
this._setTransientProperties(siteId, siteSettings, options.wildcard);
return siteSettings;
},
async get(siteId) {
const siteSettings = await this.area.get(siteId);
await this._loadTransientProperties(siteId, siteSettings);
return siteSettings;
},
async set(siteSettings) {
const siteId = siteSettings.siteId;
const exemptedTabs = siteSettings.exemptedTabs;
const wildcard = siteSettings.wildcard;
// Store exempted tabs
this.exemptedTabs[siteId] = exemptedTabs;
// Store/remove wildcard mapping
if (wildcard && wildcard !== siteId) {
await wildcardManager.set(siteId, wildcard);
} else {
return `${storagePrefix}${url.hostname}${url.port}`;
await wildcardManager.remove(siteId);
}
// Remove transient properties before storing
const cleanSiteSettings = Object.assign({}, siteSettings);
this._unsetTransientProperties(cleanSiteSettings);
// Store assignment
return this.area.set(siteId, cleanSiteSettings);
},
setExempted(pageUrl, tabId) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
if (!(siteStoreKey in this.exemptedTabs)) {
this.exemptedTabs[siteStoreKey] = [];
}
this.exemptedTabs[siteStoreKey].push(tabId);
},
removeExempted(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
this.exemptedTabs[siteStoreKey] = [];
},
isExempted(pageUrl, tabId) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
if (!(siteStoreKey in this.exemptedTabs)) {
return false;
}
return this.exemptedTabs[siteStoreKey].includes(tabId);
},
get(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
return new Promise((resolve, reject) => {
this.area.get([siteStoreKey]).then((storageResponse) => {
if (storageResponse && siteStoreKey in storageResponse) {
resolve(storageResponse[siteStoreKey]);
}
resolve(null);
}).catch((e) => {
reject(e);
});
});
},
set(pageUrl, data, exemptedTabIds) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
if (exemptedTabIds) {
exemptedTabIds.forEach((tabId) => {
this.setExempted(pageUrl, tabId);
});
}
return this.area.set({
[siteStoreKey]: data
});
},
remove(pageUrl) {
const siteStoreKey = this.getSiteStoreKey(pageUrl);
async remove(siteId) {
// When we remove an assignment we should clear all the exemptions
this.removeExempted(pageUrl);
return this.area.remove([siteStoreKey]);
delete this.exemptedTabs[siteId];
// ...and also clear the wildcard mapping
await wildcardManager.remove(siteId);
return this.area.remove(siteId);
},
async deleteContainer(userContextId) {
const sitesByContainer = await this.getByContainer(userContextId);
this.area.remove(Object.keys(sitesByContainer));
const siteSettingsById = await this.getByContainer(userContextId);
const siteIds = Object.keys(siteSettingsById);
siteIds.forEach((siteId) => {
// When we remove an assignment we should clear all the exemptions
delete this.exemptedTabs[siteId];
});
// ...and also clear the wildcard mappings
await wildcardManager.removeAll(siteIds);
return this.area.removeAll(siteIds);
},
async getByContainer(userContextId) {
const sites = {};
const siteConfigs = await this.area.get();
Object.keys(siteConfigs).forEach((key) => {
const siteSettingsById = await this.area.getSome((siteId, siteSettings) => {
// For some reason this is stored as string... lets check them both as that
if (String(siteConfigs[key].userContextId) === String(userContextId)) {
const site = siteConfigs[key];
// In hindsight we should have stored this
// TODO file a follow up to clean the storage onLoad
site.hostname = key.replace(/^siteContainerMap@@_/, "");
sites[key] = site;
}
return String(siteSettings.userContextId) === String(userContextId);
});
return sites;
await this._loadTransientPropertiesForAll(siteSettingsById);
return siteSettingsById;
},
async _loadTransientProperties(siteId, siteSettings) {
if (siteId && siteSettings) {
const wildcard = await wildcardManager.get(siteId);
const exemptedTabs = this.exemptedTabs[siteId];
this._setTransientProperties(siteId, siteSettings, wildcard, exemptedTabs);
}
},
async _loadTransientPropertiesForAll(siteSettingsById) {
const siteIds = Object.keys(siteSettingsById);
if (siteIds.length > 0) {
const siteIdsToWildcards = await wildcardManager.getAll(siteIds);
siteIds.forEach((siteId) => {
const siteSettings = siteSettingsById[siteId];
const wildcard = siteIdsToWildcards[siteId];
const exemptedTabs = this.exemptedTabs[siteId];
this._setTransientProperties(siteId, siteSettings, wildcard, exemptedTabs);
});
}
},
_setTransientProperties(siteId, siteSettings, wildcard, exemptedTabs = []) {
siteSettings.siteId = siteId;
siteSettings.hostname = siteId;
siteSettings.wildcard = wildcard;
siteSettings.exemptedTabs = exemptedTabs;
},
_unsetTransientProperties(siteSettings) {
delete siteSettings.siteId;
delete siteSettings.hostname;
delete siteSettings.wildcard;
delete siteSettings.exemptedTabs;
}
},
_neverAsk(m) {
async _neverAsk(m) {
const pageUrl = m.pageUrl;
if (m.neverAsk === true) {
// If we have existing data and for some reason it hasn't been deleted etc lets update it
this.storageArea.get(pageUrl).then((siteSettings) => {
if (siteSettings) {
siteSettings.neverAsk = true;
this.storageArea.set(pageUrl, siteSettings);
}
}).catch((e) => {
throw e;
});
const neverAsk = m.neverAsk;
if (neverAsk === true) {
const siteSettings = await this.storageArea.matchUrl(pageUrl);
if (siteSettings && !siteSettings.neverAsk) {
siteSettings.neverAsk = true;
await this.storageArea.set(siteSettings);
}
}
},
// We return here so the confirm page can load the tab when exempted
async _exemptTab(m) {
const pageUrl = m.pageUrl;
this.storageArea.setExempted(pageUrl, m.tabId);
return true;
const tabId = m.tabId;
const siteSettings = await this.storageArea.matchUrl(pageUrl);
if (siteSettings && siteSettings.exemptedTabs.indexOf(tabId) === -1) {
siteSettings.exemptedTabs.push(tabId);
await this.storageArea.set(siteSettings);
}
},
// Before a request is handled by the browser we decide if we should route through a different container
@ -125,7 +160,7 @@ const assignManager = {
this.removeContextMenu();
const [tab, siteSettings] = await Promise.all([
browser.tabs.get(options.tabId),
this.storageArea.get(options.url)
this.storageArea.matchUrl(options.url)
]);
let container;
try {
@ -142,8 +177,8 @@ const assignManager = {
}
const userContextId = this.getUserContextIdFromCookieStore(tab);
if (!siteSettings
|| userContextId === siteSettings.userContextId
|| this.storageArea.isExempted(options.url, tab.id)) {
|| siteSettings.userContextId === userContextId
|| siteSettings.exemptedTabs.includes(tab.id)) {
return {};
}
const removeTab = backgroundLogic.NEW_TAB_PAGES.has(tab.url)
@ -367,7 +402,14 @@ const assignManager = {
return true;
},
async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove) {
_determineAssignmentMatchesUrl(siteSettings, url) {
const siteId = backgroundLogic.getSiteIdFromUrl(url);
if (siteSettings.siteId === siteId) { return true; }
if (siteSettings.wildcard && siteId.endsWith(siteSettings.wildcard)) { return true; }
return false;
},
async _setOrRemoveAssignment(tabId, pageUrl, userContextId, remove, options = {}) {
let actionName;
// https://github.com/mozilla/testpilot-containers/issues/626
@ -375,43 +417,53 @@ const assignManager = {
// the value to a string for accurate checking
userContextId = String(userContextId);
const siteId = backgroundLogic.getSiteIdFromUrl(pageUrl);
if (!remove) {
const siteSettings = this.storageArea.create(siteId, userContextId, options);
// Auto exempt all tabs that exist for this hostname that are not in the same container
const tabs = await browser.tabs.query({});
const assignmentStoreKey = this.storageArea.getSiteStoreKey(pageUrl);
const exemptedTabIds = tabs.filter((tab) => {
const tabStoreKey = this.storageArea.getSiteStoreKey(tab.url);
/* Auto exempt all tabs that exist for this hostname that are not in the same container */
if (tabStoreKey === assignmentStoreKey &&
this.getUserContextIdFromCookieStore(tab) !== userContextId) {
return true;
}
return false;
siteSettings.exemptedTabs = tabs.filter((tab) => {
if (!this._determineAssignmentMatchesUrl(siteSettings, tab.url)) { return false; }
if (this.getUserContextIdFromCookieStore(tab) === userContextId) { return false; }
return true;
}).map((tab) => {
return tab.id;
});
await this.storageArea.set(pageUrl, {
userContextId,
neverAsk: false
}, exemptedTabIds);
await this.storageArea.set(siteSettings);
actionName = "added";
} else {
await this.storageArea.remove(pageUrl);
await this.storageArea.remove(siteId);
actionName = "removed";
}
browser.tabs.sendMessage(tabId, {
text: `Successfully ${actionName} site to always open in this container`
});
if (!options.silent) {
browser.tabs.sendMessage(tabId, {
text: `Successfully ${actionName} site to always open in this container`
});
}
const tab = await browser.tabs.get(tabId);
this.calculateContextMenu(tab);
},
async _setOrRemoveWildcard(tabId, pageUrl, userContextId, wildcard) {
// Get existing settings, so we can preserve neverAsk property
const siteId = backgroundLogic.getSiteIdFromUrl(pageUrl);
const siteSettings = await this.storageArea.get(siteId);
const neverAsk = siteSettings && siteSettings.neverAsk;
// Remove assignment
await this._setOrRemoveAssignment(tabId, pageUrl, userContextId, true, {silent:true});
// Add assignment
await this._setOrRemoveAssignment(tabId, pageUrl, userContextId, false, {silent:true, wildcard:wildcard, neverAsk:neverAsk});
},
async _getAssignment(tab) {
const cookieStore = this.getUserContextIdFromCookieStore(tab);
// Ensure we have a cookieStore to assign to
if (cookieStore
&& this.isTabPermittedAssign(tab)) {
return await this.storageArea.get(tab.url);
return await this.storageArea.matchUrl(tab.url);
}
return false;
},

View file

@ -329,5 +329,17 @@ const backgroundLogic = {
cookieStoreId(userContextId) {
return `firefox-container-${userContextId}`;
},
// A URL host string that is used to identify a site assignment, e.g.:
// www.example.com
// www.example.com:8080
getSiteIdFromUrl(pageUrl) {
const url = new window.URL(pageUrl);
if (url.port === "" || url.port === "80" || url.port === "443") {
return `${url.hostname}`;
} else {
return `${url.hostname}:${url.port}`;
}
}
};

View file

@ -13,7 +13,9 @@
"js/background/messageHandler.js",
]
-->
<script type="text/javascript" src="utils.js"></script>
<script type="text/javascript" src="backgroundLogic.js"></script>
<script type="text/javascript" src="wildcardManager.js"></script>
<script type="text/javascript" src="assignManager.js"></script>
<script type="text/javascript" src="badge.js"></script>
<script type="text/javascript" src="identityState.js"></script>

View file

@ -37,6 +37,12 @@ const messageHandler = {
return assignManager._setOrRemoveAssignment(tab.id, m.url, m.userContextId, m.value);
});
break;
case "setOrRemoveWildcard":
// Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
response = browser.tabs.get(m.tabId).then((tab) => {
return assignManager._setOrRemoveWildcard(tab.id, m.url, m.userContextId, m.wildcard);
});
break;
case "sortTabs":
backgroundLogic.sortTabs();
break;
@ -91,7 +97,7 @@ const messageHandler = {
if (typeof message.url === "undefined") {
throw new Error("Missing message.url");
}
response = assignManager.storageArea.get(message.url);
response = assignManager.storageArea.matchUrl(message.url);
break;
default:
throw new Error("Unknown message.method");

106
src/js/background/utils.js Normal file
View file

@ -0,0 +1,106 @@
const utils = { // eslint-disable-line no-unused-vars
// Copy object and remove keys with predicate
filterObj(obj, predicate) {
if (obj && typeof obj !== "object") { throw new Error(`Invalid arg: ${obj}`); }
if (!obj) { return {}; }
return Object.assign({}, ...Object.entries(obj).map(([k,v]) => {
if (predicate(k, v)) {
return { [k]: v };
} else {
return null;
}
}));
},
// Store data in a named storage area.
//
// (Note that all data for all stores is stored in the same single storage area,
// but this class provides accessor methods to get/set only the data that applies
// to one specific named store, as identified in the constructor.)
NamedStore: class {
constructor(name) {
this.prefix = `${name}@@_`;
}
_storeKeyForKey(key) {
if (Array.isArray(key)) {
return key.map(oneKey => oneKey.startsWith(this.prefix) ? oneKey : `${this.prefix}${oneKey}`);
} else if (key) {
return key.startsWith(this.prefix) ? key : `${this.prefix}${key}`;
} else {
return null;
}
}
_keyForStoreKey(storeKey) {
if (Array.isArray(storeKey)) {
return storeKey.map(oneStoreKey => oneStoreKey.startsWith(this.prefix) ? oneStoreKey.substring(this.prefix.length) : null);
} else if (storeKey) {
return storeKey.startsWith(this.prefix) ? storeKey.substring(this.prefix.length) : null;
} else {
return null;
}
}
get(key) {
if (typeof key !== "string") { return Promise.reject(new Error(`Invalid arg: ${key}`)); }
const storeKey = this._storeKeyForKey(key);
return new Promise((resolve, reject) => {
browser.storage.local.get([storeKey]).then((storageResponse) => {
if (storeKey in storageResponse) {
resolve(storageResponse[storeKey]);
} else {
resolve(null);
}
}).catch((e) => {
reject(e);
});
});
}
getAll(keys) {
if (keys && !Array.isArray(keys)) { return Promise.reject(new Error(`Invalid arg: ${keys}`)); }
const storeKeys = this._storeKeyForKey(keys);
return new Promise((resolve, reject) => {
browser.storage.local.get(storeKeys).then((storageResponse) => {
if (storageResponse) {
resolve(Object.assign({}, ...Object.entries(storageResponse).map(([oneStoreKey, data]) => {
const key = this._keyForStoreKey(oneStoreKey);
return key ? { [key]: data } : null;
})));
} else {
resolve({});
}
}).catch((e) => {
reject(e);
});
});
}
async getSome(predicate) {
const all = await this.getAll();
return utils.filterObj(all, predicate);
}
set(key, data) {
if (typeof key !== "string") { return Promise.reject(new Error(`Invalid arg: ${key}`)); }
const storeKey = this._storeKeyForKey(key);
return browser.storage.local.set({
[storeKey]: data
});
}
remove(key) {
if (typeof key !== "string") { return Promise.reject(new Error(`Invalid arg: ${key}`)); }
const storeKey = this._storeKeyForKey(key);
return browser.storage.local.remove(storeKey);
}
removeAll(keys) {
if (keys && !Array.isArray(keys)) { return Promise.reject(new Error(`Invalid arg: ${keys}`)); }
const storeKeys = this._storeKeyForKey(keys);
return browser.storage.local.remove(storeKeys);
}
}
};

View file

@ -0,0 +1,69 @@
/**
Manages mappings of Site Host <-> Wildcard Host.
E.g. drive.google.com <-> google.com
Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
*/
const wildcardManager = { // eslint-disable-line no-unused-vars
bySite: new utils.NamedStore("siteToWildcardMap"),
byWildcard: new utils.NamedStore("wildcardToSiteMap"),
// Site -> Wildcard
get(site) {
return this.bySite.get(site);
},
async getAll(sites) {
return this.bySite.getAll(sites);
},
async set(site, wildcard) {
// Remove existing site -> wildcard
const oldSite = await this.byWildcard.get(wildcard);
if (oldSite === site) { return; } // Wildcard already set
if (oldSite) { await this.bySite.remove(oldSite); }
// Set new mappings site <-> wildcard
await this.bySite.set(site, wildcard);
await this.byWildcard.set(wildcard, site);
},
async remove(site) {
const wildcard = await this.bySite.get(site);
if (!wildcard) { return; }
await this.bySite.remove(site);
await this.byWildcard.remove(wildcard);
},
async removeAll(sites) {
const data = await this.bySite.getAll(sites);
const existingSites = Object.keys(data);
const existingWildcards = Object.values(data);
await this.bySite.removeAll(existingSites);
await this.byWildcard.removeAll(existingWildcards);
},
// Site -> Site that owns Wildcard
async match(site) {
// Keep stripping subdomains off site domain until match a wildcard domain
do {
// Use the ever-shortening site hostname as if it is a wildcard
const siteHavingWildcard = await this.byWildcard.get(site);
if (siteHavingWildcard) { return siteHavingWildcard; }
} while ((site = this._removeSubdomain(site)));
return null;
},
_removeSubdomain(site) {
const indexOfDot = site.indexOf(".");
if (indexOfDot < 0) {
return null;
} else {
return site.substring(indexOfDot + 1);
}
}
};

View file

@ -368,6 +368,17 @@ const Logic = {
});
},
// Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
setOrRemoveWildcard(tabId, url, userContextId, wildcard) {
return browser.runtime.sendMessage({
method: "setOrRemoveWildcard",
tabId,
url,
userContextId,
wildcard
});
},
generateIdentityName() {
const defaultName = "Container #";
const ids = [];
@ -393,7 +404,7 @@ const Logic = {
getCurrentPanelElement() {
const panelItem = this._panels[this._currentPanel];
return document.querySelector(this.getPanelSelector(panelItem));
},
}
};
// P_ONBOARDING_1: First page for Onboarding.
@ -1030,16 +1041,38 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
const assumedUrl = `https://${site.hostname}/favicon.ico`;
trElement.innerHTML = escaped`
<div class="favicon"></div>
<div title="${site.hostname}" class="truncate-text hostname">
${site.hostname}
</div>
<div title="${site.hostname}" class="truncate-text hostname"></div>
<img
class="pop-button-image delete-assignment"
src="/img/container-delete.svg"
/>`;
trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl));
const deleteButton = trElement.querySelector(".delete-assignment");
trElement.querySelector(".hostname").appendChild(this.assignmentElement(site));
const that = this;
// Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
trElement.querySelectorAll(".subdomain").forEach(function(subdomainLink) {
subdomainLink.addEventListener("click", async (e) => {
const userContextId = Logic.currentUserContextId();
// Wildcard hostname is stored in id attribute
const wildcard = e.target.id;
if (wildcard) {
// Remove wildcard from other site that has same wildcard
Object.values(assignments).forEach((site) => {
if (site.wildcard === wildcard) { delete site.wildcard; }
});
site.wildcard = wildcard;
} else {
delete site.wildcard;
}
const currentTab = await Logic.currentTab();
Logic.setOrRemoveWildcard(currentTab.id, assumedUrl, userContextId, wildcard);
that.showAssignedContainers(assignments);
});
});
const deleteButton = trElement.querySelector(".delete-assignment");
Logic.addEnterHandler(deleteButton, async () => {
const userContextId = Logic.currentUserContextId();
// Lets show the message to the current tab
@ -1049,11 +1082,46 @@ Logic.registerPanel(P_CONTAINER_EDIT, {
delete assignments[siteKey];
that.showAssignedContainers(assignments);
});
trElement.classList.add("container-info-tab-row", "clickable");
tableElement.appendChild(trElement);
});
}
},
// Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
assignmentElement(site) {
const result = document.createElement("span");
// Remove wildcard subdomain
if (site.wildcard && site.wildcard !== site.hostname) {
result.appendChild(this.assignmentSubdomainLink(null, "___"));
result.appendChild(document.createTextNode("."));
}
// Add wildcard subdomain
let hostname = site.wildcard ? site.wildcard : site.hostname;
let indexOfDot;
while ((indexOfDot = hostname.indexOf(".")) >= 0) {
const subdomain = hostname.substring(0, indexOfDot);
hostname = hostname.substring(indexOfDot + 1);
result.appendChild(this.assignmentSubdomainLink(hostname, subdomain));
result.appendChild(document.createTextNode("."));
}
// Root domain
result.appendChild(document.createTextNode(hostname));
return result;
},
assignmentSubdomainLink(wildcard, text) {
const result = document.createElement("a");
if (wildcard) { result.id = wildcard; }
result.className = "subdomain";
result.appendChild(document.createTextNode(text));
return result;
},
initializeRadioButtons() {
const colorRadioTemplate = (containerColor) => {

View file

@ -25,7 +25,7 @@ describe("External Webextensions", () => {
const [promise] = background.browser.runtime.onMessageExternal.addListener.yield(message, sender);
const answer = await promise;
expect(answer).to.deep.equal({
expect(answer).to.deep.include({
userContextId: "1",
neverAsk: false
});

View file

@ -0,0 +1,58 @@
// Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
describe("Wildcard Subdomains Feature", () => {
const url1 = "http://www.example.com";
const url2 = "http://mail.example.com";
let activeTab;
beforeEach(async () => {
activeTab = await helper.browser.initializeWithTab({
cookieStoreId: "firefox-container-1",
url: url1
});
});
describe("click the 'Always open in' checkbox in the popup", () => {
beforeEach(async () => {
// popup click to set assignment for activeTab.url
await helper.popup.clickElementById("container-page-assigned");
});
describe("click the assigned URL's subdomain to convert it to a wildcard", () => {
beforeEach(async () => {
await helper.popup.setWildcard(activeTab, "example.com");
});
describe("open new Tab with a different subdomain in the default container", () => {
let newTab;
beforeEach(async () => {
// new Tab opening activeTab.url in default container
newTab = await helper.browser.openNewTab({
cookieStoreId: "firefox-default",
url: url2
}, {
options: {
webRequestError: true // because request is canceled due to reopening
}
});
});
it("should open the confirm page", async () => {
// should have created a new tab with the confirm page
background.browser.tabs.create.should.have.been.calledWithMatch({
url: "moz-extension://fake/confirm-page.html?" +
`url=${encodeURIComponent(url2)}` +
`&cookieStoreId=${activeTab.cookieStoreId}`,
cookieStoreId: undefined,
openerTabId: null,
index: 2,
active: true
});
});
it("should remove the new Tab that got opened in the default container", () => {
background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
});
});
});
});
});

View file

@ -11,6 +11,8 @@ module.exports = {
}
},
popup: {
// Required to access variables, because nyc messes up 'eval'
script: "function evalScript(v) { return eval(v); }",
jsdom: {
beforeParse(window) {
window.browser.storage.local.set({
@ -39,6 +41,13 @@ module.exports = {
async clickLastMatchingElementByQuerySelector(querySelector) {
await popup.helper.clickElementByQuerySelectorAll(querySelector, "last");
},
// Wildcard subdomains: https://github.com/mozilla/multi-account-containers/issues/473
async setWildcard(tab, wildcard) {
const Logic = popup.window.evalScript("Logic");
const userContextId = Logic.userContextId(tab.cookieStoreId);
await Logic.setOrRemoveWildcard(tab.id, tab.url, userContextId, wildcard);
}
}
};