Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wildcard subdomains v2 - e.g. *.google.com #2352

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/css/popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
109 changes: 103 additions & 6 deletions src/js/background/assignManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ window.assignManager = {
}
},

getWildcardStoreKey(wildcardHostname) {
return `wildcardMap@@_${wildcardHostname}`;
},

setExempted(pageUrlorUrlKey, tabId) {
const siteStoreKey = this.getSiteStoreKey(pageUrlorUrlKey);
if (!(siteStoreKey in this.exemptedTabs)) {
Expand All @@ -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;
Expand All @@ -69,39 +85,111 @@ 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) {
exemptedTabIds.forEach((tabId) => {
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});
}
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) {
Expand Down Expand Up @@ -166,10 +254,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;
Expand Down Expand Up @@ -217,10 +305,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
Expand Down Expand Up @@ -620,6 +709,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;
Expand Down
3 changes: 3 additions & 0 deletions src/js/background/messageHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
85 changes: 84 additions & 1 deletion src/js/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,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);
Expand Down Expand Up @@ -1411,10 +1412,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();
Expand All @@ -1424,11 +1426,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.
Expand Down
8 changes: 8 additions & 0 deletions src/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down