Wildcard subdomains - e.g. *.google.com
This commit is contained in:
parent
adeab46229
commit
6ab8da3b3a
6 changed files with 258 additions and 7 deletions
|
@ -1812,6 +1812,14 @@ manage things like container crud */
|
|||
padding-inline-start: 16px;
|
||||
}
|
||||
|
||||
#edit-sites-assigned .hostname .subdomain:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#edit-sites-assigned .hostname .subdomain.wildcardSubdomain {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.assigned-sites-list > div {
|
||||
display: flex;
|
||||
padding-block-end: 6px;
|
||||
|
|
|
@ -20,6 +20,10 @@ window.assignManager = {
|
|||
}
|
||||
},
|
||||
|
||||
getWildcardStoreKey(wildcardHostname) {
|
||||
return `wildcardMap@@_${wildcardHostname}`;
|
||||
},
|
||||
|
||||
setExempted(pageUrlorUrlKey, tabId) {
|
||||
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||
if (!(siteStoreKey in this.exemptedTabs)) {
|
||||
|
@ -46,6 +50,18 @@ window.assignManager = {
|
|||
return this.getByUrlKey(siteStoreKey);
|
||||
},
|
||||
|
||||
async getOrWildcardMatch(pageUrlorUrlKey) {
|
||||
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||
const siteSettings = await this.getByUrlKey(siteStoreKey);
|
||||
if (siteSettings) {
|
||||
return {
|
||||
siteStoreKey,
|
||||
siteSettings
|
||||
};
|
||||
}
|
||||
return this.getByWildcardMatch(siteStoreKey);
|
||||
},
|
||||
|
||||
async getSyncEnabled() {
|
||||
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
|
||||
return !!syncEnabled;
|
||||
|
@ -69,6 +85,26 @@ window.assignManager = {
|
|||
});
|
||||
},
|
||||
|
||||
async getByWildcardMatch(siteStoreKey) {
|
||||
// Keep stripping subdomains off site hostname until match a wildcard hostname
|
||||
let remainingHostname = siteStoreKey.replace(/^siteContainerMap@@_/, "");
|
||||
while (remainingHostname) {
|
||||
const wildcardStoreKey = this.getWildcardStoreKey(remainingHostname);
|
||||
siteStoreKey = await this.getByUrlKey(wildcardStoreKey);
|
||||
if (siteStoreKey) {
|
||||
const siteSettings = await this.getByUrlKey(siteStoreKey);
|
||||
if (siteSettings) {
|
||||
return {
|
||||
siteStoreKey,
|
||||
siteSettings
|
||||
};
|
||||
}
|
||||
}
|
||||
const indexOfDot = remainingHostname.indexOf(".");
|
||||
remainingHostname = indexOfDot < 0 ? null : remainingHostname.substring(indexOfDot + 1);
|
||||
}
|
||||
},
|
||||
|
||||
async set(pageUrlorUrlKey, data, exemptedTabIds, backup = true) {
|
||||
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
|
||||
if (exemptedTabIds) {
|
||||
|
@ -76,12 +112,16 @@ window.assignManager = {
|
|||
this.setExempted(pageUrlorUrlKey, tabId);
|
||||
});
|
||||
}
|
||||
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 +129,46 @@ 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]);
|
||||
}
|
||||
},
|
||||
|
||||
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 +233,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 +284,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 +688,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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1411,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();
|
||||
|
@ -1424,11 +1425,90 @@ Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
|
|||
delete assignments[siteKey];
|
||||
this.showAssignedContainers(assignments);
|
||||
});
|
||||
// Wildcard click-to-toggle subdomains
|
||||
trElement.querySelectorAll(".subdomain").forEach((subdomainLink) => {
|
||||
subdomainLink.addEventListener("click", async (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: hostname.substring(0, hostname.length - wildcardHostname.length),
|
||||
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));
|
||||
}
|
||||
|
||||
// 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);
|
||||
} 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