Merge d4431c4eff
into 7dee05ec1f
This commit is contained in:
commit
2a9768228c
6 changed files with 304 additions and 8 deletions
|
@ -1812,6 +1812,18 @@ manage things like container crud */
|
|||
padding-inline-start: 16px;
|
||||
}
|
||||
|
||||
#edit-sites-assigned .hostname .subdomain:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#edit-sites-assigned .hostname .subdomain.wildcardSubdomain {
|
||||
background-color: var(--identity-icon-color);
|
||||
border-radius: 8px;
|
||||
margin-right: 4px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.assigned-sites-list > div {
|
||||
display: flex;
|
||||
padding-block-end: 6px;
|
||||
|
|
|
@ -20,6 +20,22 @@ window.assignManager = {
|
|||
}
|
||||
},
|
||||
|
||||
getWildcardStoreKey(wildcardHostname) {
|
||||
return `wildcardMap@@_${wildcardHostname}`;
|
||||
},
|
||||
|
||||
getWildcardStoreKeys(siteStoreKey) {
|
||||
// E.g. "siteContainerMap@@_www.mozilla.org" =>
|
||||
// ["wildcardMap@@_www.mozilla.org", "wildcardMap@@_mozilla.org", "wildcardMap@@_org"]
|
||||
let previous;
|
||||
return siteStoreKey.replace(/^siteContainerMap@@_/, "")
|
||||
.split(".")
|
||||
.reverse()
|
||||
.map((subdomain) => previous = previous ? `${subdomain}.${previous}` : subdomain)
|
||||
.map((hostname) => this.getWildcardStoreKey(hostname))
|
||||
.reverse();
|
||||
},
|
||||
|
||||
setExempted(pageUrlorUrlKey, tabId) {
|
||||
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||
if (!(siteStoreKey in this.exemptedTabs)) {
|
||||
|
@ -46,6 +62,42 @@ window.assignManager = {
|
|||
return this.getByUrlKey(siteStoreKey);
|
||||
},
|
||||
|
||||
async getOrWildcardMatch(pageUrlorUrlKey) {
|
||||
// 1st store request: siteStoreKey + wildcardStoreKeys
|
||||
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||
const wildcardStoreKeys = this.getWildcardStoreKeys(siteStoreKey);
|
||||
const combinedStoreKeys = [siteStoreKey].concat(wildcardStoreKeys);
|
||||
let storageResponse = await this.area.get(combinedStoreKeys);
|
||||
if (!storageResponse) { return null; }
|
||||
|
||||
// Try exact match
|
||||
const siteSettings = storageResponse[siteStoreKey];
|
||||
if (siteSettings) {
|
||||
return {
|
||||
siteStoreKey,
|
||||
siteSettings
|
||||
};
|
||||
}
|
||||
|
||||
// 2nd store request (maybe): siteStoreKeys that were mapped from wildcardStoreKeys
|
||||
const siteStoreKeys = wildcardStoreKeys.map((k) => storageResponse[k]).filter((k) => !!k);
|
||||
if (siteStoreKeys.length > 0) {
|
||||
storageResponse = await this.area.get(siteStoreKeys);
|
||||
if (!storageResponse) { return null; }
|
||||
|
||||
// Try wildcard matches
|
||||
for (const siteStoreKey of siteStoreKeys) {
|
||||
const siteSettings = storageResponse[siteStoreKey];
|
||||
if (siteSettings) {
|
||||
return {
|
||||
siteStoreKey,
|
||||
siteSettings
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getSyncEnabled() {
|
||||
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
|
||||
return !!syncEnabled;
|
||||
|
@ -76,12 +128,19 @@ window.assignManager = {
|
|||
this.setExempted(pageUrlorUrlKey, tabId);
|
||||
});
|
||||
}
|
||||
if (data.wildcardHostname) {
|
||||
await this.removeDuplicateWildcardHostname(data.wildcardHostname, siteStoreKey);
|
||||
}
|
||||
await this.removeWildcardLookup(siteStoreKey);
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
data.identityMacAddonUUID =
|
||||
await identityState.lookupMACaddonUUID(data.userContextId);
|
||||
await this.area.set({
|
||||
[siteStoreKey]: data
|
||||
});
|
||||
if (data.wildcardHostname) {
|
||||
await this.setWildcardLookup(siteStoreKey, data.wildcardHostname);
|
||||
}
|
||||
const syncEnabled = await this.getSyncEnabled();
|
||||
if (backup && syncEnabled) {
|
||||
await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
|
||||
|
@ -89,19 +148,64 @@ window.assignManager = {
|
|||
return;
|
||||
},
|
||||
|
||||
async setWildcardLookup(siteStoreKey, wildcardHostname) {
|
||||
const wildcardStoreKey = this.getWildcardStoreKey(wildcardHostname);
|
||||
return this.area.set({
|
||||
[wildcardStoreKey]: siteStoreKey
|
||||
});
|
||||
},
|
||||
|
||||
async remove(pageUrlorUrlKey, shouldSync = true) {
|
||||
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||
// When we remove an assignment we should clear all the exemptions
|
||||
this.removeExempted(pageUrlorUrlKey);
|
||||
// When we remove an assignment we should clear the wildcard lookup
|
||||
await this.removeWildcardLookup(siteStoreKey);
|
||||
await this.area.remove([siteStoreKey]);
|
||||
const syncEnabled = await this.getSyncEnabled();
|
||||
if (shouldSync && syncEnabled) await sync.storageArea.backup({siteStoreKey});
|
||||
return;
|
||||
},
|
||||
|
||||
async removeWildcardLookup(siteStoreKey) {
|
||||
const siteSettings = await this.getByUrlKey(siteStoreKey);
|
||||
const wildcardHostname = siteSettings && siteSettings.wildcardHostname;
|
||||
if (wildcardHostname) {
|
||||
const wildcardStoreKey = this.getWildcardStoreKey(wildcardHostname);
|
||||
await this.area.remove([wildcardStoreKey]);
|
||||
}
|
||||
},
|
||||
|
||||
// Must not set the same wildcardHostname property on multiple sites.
|
||||
// E.g. 'google.com' on both 'www.google.com' and 'mail.google.com'.
|
||||
//
|
||||
// Necessary because the stored wildcardLookup map is 1-to-1, i.e. either
|
||||
// 'google.com' => 'www.google.com', or
|
||||
// 'google.com' => 'mail.google.com', but not both!
|
||||
async removeDuplicateWildcardHostname(wildcardHostname, expectedSiteStoreKey) {
|
||||
const wildcardStoreKey = this.getWildcardStoreKey(wildcardHostname);
|
||||
const siteStoreKey = await this.getByUrlKey(wildcardStoreKey);
|
||||
if (siteStoreKey && siteStoreKey !== expectedSiteStoreKey) {
|
||||
const siteSettings = await this.getByUrlKey(siteStoreKey);
|
||||
if (siteSettings && siteSettings.wildcardHostname === wildcardHostname) {
|
||||
delete siteSettings.wildcardHostname;
|
||||
await this.set(siteStoreKey, siteSettings); // Will cause wildcard mapping to be cleared
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async deleteContainer(userContextId) {
|
||||
const sitesByContainer = await this.getAssignedSites(userContextId);
|
||||
this.area.remove(Object.keys(sitesByContainer));
|
||||
// Delete wildcard lookups
|
||||
const wildcardStoreKeys = Object.values(sitesByContainer)
|
||||
.map((site) => {
|
||||
if (site && site.wildcardHostname) {
|
||||
return this.getWildcardStoreKey(site.wildcardHostname);
|
||||
}
|
||||
})
|
||||
.filter((wildcardStoreKey) => { return !!wildcardStoreKey; });
|
||||
this.area.remove(wildcardStoreKeys);
|
||||
},
|
||||
|
||||
async getAssignedSites(userContextId = null) {
|
||||
|
@ -166,10 +270,10 @@ window.assignManager = {
|
|||
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);
|
||||
this.storageArea.getOrWildcardMatch(pageUrl).then((siteMatchResult) => {
|
||||
if (siteMatchResult) {
|
||||
siteMatchResult.siteSettings.neverAsk = true;
|
||||
this.storageArea.set(siteMatchResult.siteStoreKey, siteMatchResult.siteSettings);
|
||||
}
|
||||
}).catch((e) => {
|
||||
throw e;
|
||||
|
@ -217,10 +321,11 @@ window.assignManager = {
|
|||
return {};
|
||||
}
|
||||
this.removeContextMenu();
|
||||
const [tab, siteSettings] = await Promise.all([
|
||||
const [tab, siteMatchResult] = await Promise.all([
|
||||
browser.tabs.get(options.tabId),
|
||||
this.storageArea.get(options.url)
|
||||
this.storageArea.getOrWildcardMatch(options.url)
|
||||
]);
|
||||
const siteSettings = siteMatchResult && siteMatchResult.siteSettings;
|
||||
let container;
|
||||
try {
|
||||
container = await browser.contextualIdentities
|
||||
|
@ -620,6 +725,14 @@ window.assignManager = {
|
|||
}
|
||||
},
|
||||
|
||||
async _setWildcardHostnameForAssignment(pageUrl, wildcardHostname) {
|
||||
const siteSettings = await this.storageArea.get(pageUrl);
|
||||
if (siteSettings) {
|
||||
siteSettings.wildcardHostname = wildcardHostname;
|
||||
await this.storageArea.set(pageUrl, siteSettings);
|
||||
}
|
||||
},
|
||||
|
||||
async _maybeRemoveSiteIsolation(userContextId) {
|
||||
const assignments = await this.storageArea.getByContainer(userContextId);
|
||||
const hasAssignments = assignments && Object.keys(assignments).length > 0;
|
||||
|
@ -637,7 +750,8 @@ window.assignManager = {
|
|||
// Ensure we have a cookieStore to assign to
|
||||
if (cookieStore
|
||||
&& this.isTabPermittedAssign(tab)) {
|
||||
return this.storageArea.get(tab.url);
|
||||
const siteMatchResult = await this.storageArea.getOrWildcardMatch(tab.url);
|
||||
return siteMatchResult && siteMatchResult.siteSettings;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
|
|
@ -45,6 +45,9 @@ const messageHandler = {
|
|||
// m.url is the assignment to be removed/added
|
||||
response = assignManager._setOrRemoveAssignment(m.tabId, m.url, m.userContextId, m.value);
|
||||
break;
|
||||
case "setWildcardHostnameForAssignment":
|
||||
response = assignManager._setWildcardHostnameForAssignment(m.url, m.wildcardHostname);
|
||||
break;
|
||||
case "sortTabs":
|
||||
backgroundLogic.sortTabs();
|
||||
break;
|
||||
|
|
|
@ -1376,6 +1376,7 @@ Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
|
|||
|
||||
// Populating the panel: name and icon
|
||||
document.getElementById("edit-assignments-title").textContent = identity.name;
|
||||
document.getElementById("edit-sites-assigned").setAttribute("data-identity-color", identity.color);
|
||||
|
||||
const userContextId = Logic.currentUserContextId();
|
||||
const assignments = await Logic.getAssignmentObjectByContainer(userContextId);
|
||||
|
@ -1410,10 +1411,11 @@ Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
|
|||
trElement.innerHTML = Utils.escaped`
|
||||
<td>
|
||||
<div class="favicon"></div>
|
||||
<span title="${site.hostname}" class="menu-text">${site.hostname}</span>
|
||||
<span title="${site.hostname}" class="menu-text hostname"></span>
|
||||
<img class="trash-button delete-assignment" src="/img/container-delete.svg" />
|
||||
</td>`;
|
||||
trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl));
|
||||
trElement.querySelector(".hostname").appendChild(this.assignmentHostnameElement(site));
|
||||
const deleteButton = trElement.querySelector(".trash-button");
|
||||
Utils.addEnterHandler(deleteButton, async () => {
|
||||
const userContextId = Logic.currentUserContextId();
|
||||
|
@ -1423,11 +1425,92 @@ Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
|
|||
delete assignments[siteKey];
|
||||
this.showAssignedContainers(assignments);
|
||||
});
|
||||
// Wildcard click-to-toggle subdomains
|
||||
trElement.querySelectorAll(".subdomain").forEach((subdomainLink) => {
|
||||
subdomainLink.addEventListener("click", (e) => {
|
||||
const wildcardHostname = e.target.getAttribute("data-wildcardHostname");
|
||||
Utils.setWildcardHostnameForAssignment(assumedUrl, wildcardHostname);
|
||||
if (wildcardHostname) {
|
||||
// Remove wildcard from other site that has same wildcard
|
||||
Object.values(assignments).forEach((site) => {
|
||||
if (site.wildcardHostname === wildcardHostname) { delete site.wildcardHostname; }
|
||||
});
|
||||
site.wildcardHostname = wildcardHostname;
|
||||
} else {
|
||||
delete site.wildcardHostname;
|
||||
}
|
||||
this.showAssignedContainers(assignments);
|
||||
});
|
||||
});
|
||||
trElement.classList.add("menu-item", "hover-highlight", "keyboard-nav");
|
||||
tableElement.appendChild(trElement);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getSubdomains(site) {
|
||||
const hostname = site.hostname;
|
||||
const wildcardHostname = site.wildcardHostname;
|
||||
if (wildcardHostname && wildcardHostname !== hostname) {
|
||||
if (hostname.endsWith(wildcardHostname)) {
|
||||
return {
|
||||
wildcard: "★",
|
||||
remaining: wildcardHostname
|
||||
};
|
||||
} else {
|
||||
// In case something got corrupted, allow user to fix error
|
||||
// by clicking '★' link to clear corrupted wildcard hostname
|
||||
return {
|
||||
wildcard: "★",
|
||||
remaining: hostname
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
wildcard: null,
|
||||
remaining: hostname
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
assignmentHostnameElement(site) {
|
||||
const result = document.createElement("span");
|
||||
const subdomains = this.getSubdomains(site);
|
||||
|
||||
// Add wildcard subdomain(s)
|
||||
if (subdomains.wildcard) {
|
||||
result.appendChild(this.assignmentSubdomainLink(null, subdomains.wildcard));
|
||||
result.appendChild(document.createTextNode("."));
|
||||
}
|
||||
|
||||
// Add non-wildcard subdomains
|
||||
let remainingHostname = subdomains.remaining;
|
||||
let indexOfDot;
|
||||
while ((indexOfDot = remainingHostname.indexOf(".")) >= 0) {
|
||||
const subdomain = remainingHostname.substring(0, indexOfDot);
|
||||
remainingHostname = remainingHostname.substring(indexOfDot + 1);
|
||||
result.appendChild(this.assignmentSubdomainLink(remainingHostname, subdomain));
|
||||
result.appendChild(document.createTextNode("."));
|
||||
}
|
||||
|
||||
// Root domain
|
||||
if (remainingHostname) { result.appendChild(document.createTextNode(remainingHostname)); }
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
assignmentSubdomainLink(wildcardHostnameOnClick, text) {
|
||||
const result = document.createElement("a");
|
||||
result.className = "subdomain";
|
||||
if (wildcardHostnameOnClick) {
|
||||
result.setAttribute("data-wildcardHostname", wildcardHostnameOnClick);
|
||||
result.title = `*.${wildcardHostnameOnClick}`;
|
||||
} else {
|
||||
result.classList.add("wildcardSubdomain");
|
||||
}
|
||||
result.appendChild(document.createTextNode(text));
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
// P_CONTAINER_EDIT: Editor for a container.
|
||||
|
|
|
@ -138,6 +138,14 @@ const Utils = {
|
|||
});
|
||||
},
|
||||
|
||||
setWildcardHostnameForAssignment(url, wildcardHostname) {
|
||||
return browser.runtime.sendMessage({
|
||||
method: "setWildcardHostnameForAssignment",
|
||||
url,
|
||||
wildcardHostname
|
||||
});
|
||||
},
|
||||
|
||||
async reloadInContainer(url, currentUserContextId, newUserContextId, tabIndex, active) {
|
||||
return await browser.runtime.sendMessage({
|
||||
method: "reloadInContainer",
|
||||
|
|
76
test/features/wildcard.test.js
Normal file
76
test/features/wildcard.test.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
const {initializeWithTab} = require("../common");
|
||||
|
||||
describe("Wildcard Subdomains Feature", function () {
|
||||
const url1 = "http://www.example.com";
|
||||
const url2 = "http://zzz.example.com";
|
||||
const wildcardHostname = "example.com";
|
||||
|
||||
beforeEach(async function () {
|
||||
this.webExt = await initializeWithTab({
|
||||
cookieStoreId: "firefox-container-4",
|
||||
url: url1
|
||||
});
|
||||
await this.webExt.popup.helper.clickElementById("always-open-in");
|
||||
await this.webExt.popup.helper.clickElementByQuerySelectorAll("#picker-identities-list > .menu-item");
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.webExt.destroy();
|
||||
});
|
||||
|
||||
describe("open new Tab with different subdomain in the default container", function () {
|
||||
beforeEach(async function () {
|
||||
// new Tab opening url2 in default container
|
||||
await this.webExt.background.browser.tabs._create({
|
||||
cookieStoreId: "firefox-default",
|
||||
url: url2
|
||||
}, {
|
||||
options: {
|
||||
webRequestError: true // because request is canceled due to reopening
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should not open the confirm page", async function () {
|
||||
this.webExt.background.browser.tabs.create.should.not.have.been.called;
|
||||
});
|
||||
|
||||
it("should not remove the new Tab that got opened in the default container", function () {
|
||||
this.webExt.background.browser.tabs.remove.should.not.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe("set wildcard hostname and then open new Tab with different subdomain in the default container", function () {
|
||||
let newTab;
|
||||
beforeEach(async function () {
|
||||
// Set wildcard
|
||||
await this.webExt.background.window.assignManager._setWildcardHostnameForAssignment(url1, wildcardHostname);
|
||||
|
||||
// new Tab opening url2 in default container
|
||||
newTab = await this.webExt.background.browser.tabs._create({
|
||||
cookieStoreId: "firefox-default",
|
||||
url: url2
|
||||
}, {
|
||||
options: {
|
||||
webRequestError: true // because request is canceled due to reopening
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should open the confirm page", async function () {
|
||||
this.webExt.background.browser.tabs.create.should.have.been.calledWithMatch({
|
||||
url: "moz-extension://fake/confirm-page.html?" +
|
||||
`url=${encodeURIComponent(url2)}` +
|
||||
`&cookieStoreId=${this.webExt.tab.cookieStoreId}`,
|
||||
cookieStoreId: undefined,
|
||||
openerTabId: null,
|
||||
index: 2,
|
||||
active: true
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove the new Tab that got opened in the default container", function () {
|
||||
this.webExt.background.browser.tabs.remove.should.have.been.calledWith(newTab.id);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue