${identity.name}`;
fragment.appendChild(tr);
tr.appendChild(td);
Utils.addEnterHandler(tr, () => {
pickedFunction(identity);
});
}
});
const list = document.querySelector("#picker-identities-list");
list.innerHTML = "";
list.appendChild(fragment);
return Promise.resolve(null);
}
});
// ALWAYS_OPEN_IN_PICKER: Makes the list editable.
// ----------------------------------------------------------------------------
Logic.registerPanel(ALWAYS_OPEN_IN_PICKER, {
panelSelector: "#container-picker-panel",
// This method is called when the object is registered.
initialize() {
},
// This method is called when the panel is shown.
async prepare() {
const identities = Logic.identities();
Logic.listenToPickerBackButton();
document.getElementById("picker-title").textContent = browser.i18n.getMessage("alwaysOpenIn");
const fragment = document.createDocumentFragment();
document.getElementById("new-container-div").innerHTML = "";
for (const identity of identities) {
const tr = document.createElement("tr");
tr.classList.add("menu-item", "hover-highlight", "keyboard-nav");
tr.setAttribute("tabindex", "0");
const td = document.createElement("td");
td.innerHTML = Utils.escaped`
${identity.name}
`;
fragment.appendChild(tr);
tr.appendChild(td);
Utils.addEnterHandler(tr, () => {
Utils.alwaysOpenInContainer(identity);
window.close();
});
}
const list = document.querySelector("#picker-identities-list");
list.innerHTML = "";
list.appendChild(fragment);
return Promise.resolve(null);
}
});
// P_CONTAINER_ASSIGNMENTS: Shows Site Assignments and allows editing.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINER_ASSIGNMENTS, {
panelSelector: "#edit-container-assignments",
// This method is called when the object is registered.
initialize() { },
// This method is called when the panel is shown.
async prepare() {
const identity = Logic.currentIdentity();
// Populating the panel: name and icon
document.getElementById("edit-assignments-title").textContent = identity.name;
const userContextId = Logic.currentUserContextId();
const assignments = await Logic.getAssignmentObjectByContainer(userContextId);
this.showAssignedContainers(assignments);
return Promise.resolve(null);
},
showAssignedContainers(assignments) {
const closeContEl = document.querySelector("#close-container-assignment-panel");
Utils.addEnterHandler(closeContEl, () => {
const identity = Logic.currentIdentity();
Logic.showPanel(P_CONTAINER_EDIT, identity, false, false);
});
const assignmentPanel = document.getElementById("edit-sites-assigned");
const assignmentKeys = Object.keys(assignments);
assignmentPanel.hidden = !(assignmentKeys.length > 0);
if (assignments) {
const tableElement = document.querySelector("#edit-sites-assigned");
/* Remove previous assignment list,
after removing one we rerender the list */
while (tableElement.firstChild) {
tableElement.firstChild.remove();
}
assignmentKeys.forEach((siteKey) => {
const site = assignments[siteKey];
const trElement = document.createElement("tr");
/* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load.
This is pending a better solution for favicons from web extensions */
const assumedUrl = `https://${site.hostname}/favicon.ico`;
trElement.innerHTML = Utils.escaped`
${site.hostname}
`;
trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl));
const deleteButton = trElement.querySelector(".trash-button");
Utils.addEnterHandler(deleteButton, async () => {
const userContextId = Logic.currentUserContextId();
// Lets show the message to the current tab
// const currentTab = await Utils.currentTab();
Utils.setOrRemoveAssignment(false, assumedUrl, userContextId, true);
delete assignments[siteKey];
this.showAssignedContainers(assignments);
});
trElement.classList.add("menu-item", "hover-highlight", "keyboard-nav");
tableElement.appendChild(trElement);
});
}
},
});
// P_CONTAINER_EDIT: Editor for a container.
// ----------------------------------------------------------------------------
Logic.registerPanel(P_CONTAINER_EDIT, {
panelSelector: "#edit-container-panel",
// This method is called when the object is registered.
async initialize() {
this.initializeRadioButtons();
await browser.runtime.sendMessage({ method: "MozillaVPN_queryServers" });
await browser.runtime.sendMessage({ method: "MozillaVPN_queryStatus" });
class MozVpnContainerUi extends HTMLElement {
constructor() {
super();
this.subtitle = this.querySelector(".moz-vpn-subtitle");
this.collapsibleContent = this.querySelector(".collapsible-content");
this.visibilityTogglers = this.querySelectorAll(".hide-show-label");
this.hideShowButton = this.querySelector(".expand-collapse");
this.primaryCta = this.querySelector("#get-mozilla-vpn");
this.advancedProxySettingsButton = document.querySelector(".advanced-proxy-settings-btn");
this.toutName = "moz-tout-edit-container-panel";
// Switch
this.switch = this.querySelector("#moz-vpn-switch");
this.switchLabel = this.querySelector(".switch");
// Current server button
this.currentServerButton = this.querySelector("#moz-vpn-current-server");
this.currentCityName = this.querySelector(".current-city-name");
this.currentCountryFlag = this.querySelector(".current-country-flag");
this.currentCountryCode;
// Proxy inputs + viewer
this.advancedProxyAddress = document.getElementById("advanced-proxy-address");
this.proxyAddressInput = document.querySelector("#edit-container-panel-proxy");
this.cityNameInput = document.getElementById("city-name-input");
this.countryCodeInput = document.getElementById("country-code-input");
this.mozProxyEnabledInput = document.getElementById("moz-proxy-enabled");
}
async connectedCallback() {
const { mozillaVpnHiddenToutsList } = await browser.storage.local.get("mozillaVpnHiddenToutsList");
const mozillaVpnCollapseEditContainerTout = mozillaVpnHiddenToutsList && mozillaVpnHiddenToutsList.find(tout => tout.name === this.toutName);
const mozillaVpnInstalled = await browser.runtime.sendMessage({ method: "MozillaVPN_getInstallationStatus" });
this.hideShowButton.addEventListener("click", this);
if (mozillaVpnCollapseEditContainerTout && !mozillaVpnInstalled) {
this.collapseUi();
}
// Add listeners
if (!this.classList.contains("has-attached-listeners")) {
const bothMozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled();
this.primaryCta.addEventListener("click", async() => {
if (!bothMozillaVpnPermissionsEnabled && mozillaVpnInstalled) {
await browser.permissions.request({ permissions: ["proxy", "nativeMessaging"] });
} else {
MozillaVPN.handleMozillaCtaClick("mac-edit-container-panel-btn");
}
});
this.switch.addEventListener("click", async() => {
const { mozillaVpnServers } = await browser.storage.local.get("mozillaVpnServers");
const id = Logic.currentIdentity();
this.enableDisableProxyButtons();
if (!this.switch.checked) {
const deactivatedMozProxy = MozillaVPN.getProxy(
this.countryCodeInput.value,
this.cityNameInput.value,
undefined,
mozillaVpnServers
);
if (!deactivatedMozProxy) {
return;
}
await proxifiedContainers.set(id.cookieStoreId, deactivatedMozProxy);
this.switch.checked = false;
return;
}
let proxy;
if (this.countryCodeInput.value.length === 2) {
// User is re-enabling a Mozilla proxy for this container.
// Use the stored location information to select a server
// in the same location.
proxy = MozillaVPN.getProxy(
this.countryCodeInput.value,
this.cityNameInput.value,
true,
mozillaVpnServers
);
} else {
// No saved Mozilla VPN proxy information. Get something new.
const { randomServerCountryCode, randomServerCityName } = await MozillaVPN.pickRandomLocation();
proxy = MozillaVPN.getProxy(
randomServerCountryCode,
randomServerCityName,
true,
mozillaVpnServers
);
}
if (proxy) {
await proxifiedContainers.set(id.cookieStoreId, proxy);
this.switch.checked = true;
this.updateProxyDependentUi(proxy);
} else {
this.switch.checked = false;
this.updateProxyDependentUi({});
return;
}
});
}
this.classList.add("has-attached-listeners");
this.currentServerButton.classList.add("hidden");
}
async updateMozVpnStatusDependentUi() {
const mozillaVpnInstalled = await browser.runtime.sendMessage({ method: "MozillaVPN_getInstallationStatus" });
const mozillaVpnConnected = await browser.runtime.sendMessage({ method: "MozillaVPN_getConnectionStatus" });
this.subtitle.textContent = browser.i18n.getMessage("integrateContainers");
const bothMozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled();
if (mozillaVpnInstalled && !bothMozillaVpnPermissionsEnabled) {
this.subtitle.style.flex = "1 1 100%";
this.classList.remove("show-server-button");
this.subtitle.textContent = browser.i18n.getMessage("additionalPermissionNeeded");
this.hideEls(this.hideShowButton, this.switch, this.switchLabel, this.currentServerButton);
this.primaryCta.style.display = "block";
this.primaryCta.textContent = browser.i18n.getMessage("enable");
return;
}
if (mozillaVpnInstalled) {
// Hide cta and hide/show button
this.hideEls(this.primaryCta, this.hideShowButton);
// Update subtitle
this.subtitle.textContent = mozillaVpnConnected ? browser.i18n.getMessage("useCustomLocation") : browser.i18n.getMessage("mozillaVpnMustBeOn");
this.subtitle.style.flex = "1 1 80%";
this.currentServerButton.style.display = "flex";
}
if (mozillaVpnConnected) {
[this.switchLabel, this.switch].forEach(el => {
el.style.display = "inline-block";
});
} else {
this.hideEls(this.switch, this.switchLabel, this.currentServerButton);
this.switch.checked = false;
}
if ((mozillaVpnInstalled && !mozillaVpnConnected) || mozillaVpnConnected) {
this.expandUi();
}
}
async enableDisableProxyButtons() {
const mozillaVpnConnected = await browser.runtime.sendMessage({ method: "MozillaVPN_getConnectionStatus" });
if (!this.switch.checked || this.switch.disabled || !mozillaVpnConnected) {
this.currentServerButton.disabled = true;
this.advancedProxySettingsButton.disabled = false;
document.getElementById("moz-proxy-enabled").value = undefined;
return;
}
this.currentServerButton.disabled = false;
this.advancedProxySettingsButton.disabled = true;
this.advancedProxyAddress.textContent = "";
}
updateProxyInputs(proxyInfo) {
const resetProxyStorageEls = () => {
[this.proxyAddressInput, this.cityNameInput, this.countryCodeInput, this.mozProxyEnabledInput].forEach(el => {
el.value = "";
});
this.advancedProxyAddress.textContent = "";
};
resetProxyStorageEls();
if (typeof(proxyInfo) === "undefined" || typeof(proxyInfo.type) === "undefined") {
// no custom proxy is set
return;
}
this.cityNameInput.value = proxyInfo.cityName;
this.countryCodeInput.value = proxyInfo.countryCode;
this.mozProxyEnabledInput.value = proxyInfo.mozProxyEnabled;
this.proxyAddressInput.value = `${proxyInfo.type}://${proxyInfo.host}:${proxyInfo.port}`;
if (typeof(proxyInfo.countryCode) === "undefined" && proxyInfo.type) {
// Set custom proxy URL below 'Advanced proxy settings' button label
this.advancedProxyAddress.textContent = `${proxyInfo.type}://${proxyInfo.host}:${proxyInfo.port}`;
}
}
async updateProxyDependentUi(proxyInfo) {
const mozillaVpnProxyLocationAvailable = (proxy) => {
return typeof(proxy) !== "undefined" && typeof(proxy.countryCode) !== "undefined" && typeof(proxy.cityName) !== "undefined";
};
const mozillaVpnProxyIsEnabled = (proxy) => {
return typeof(proxy) !== "undefined" && typeof(proxy.mozProxyEnabled) !== "undefined" && proxy.mozProxyEnabled === true;
};
this.switch.checked = mozillaVpnProxyIsEnabled(proxyInfo);
this.updateProxyInputs(proxyInfo);
this.enableDisableProxyButtons();
const mozillaVpnConnected = await browser.runtime.sendMessage({ method: "MozillaVPN_getConnectionStatus" });
if (
!proxyInfo ||
!mozillaVpnProxyLocationAvailable(proxyInfo) ||
!mozillaVpnConnected
) {
// Hide server location button
this.currentServerButton.classList.add("hidden");
this.classList.remove("show-server-button");
} else {
// Unhide server location button
this.currentServerButton.style.display = "flex";
this.currentServerButton.classList.remove("hidden");
this.classList.add("show-server-button");
}
// Populate inputs and server button with current or previously stored mozilla vpn proxy
if(proxyInfo && mozillaVpnProxyLocationAvailable(proxyInfo)) {
this.currentCountryFlag.style.backgroundImage = `url("./img/flags/${proxyInfo.countryCode.toUpperCase()}.png")`;
this.currentCountryFlag.style.backgroundImage = proxyInfo.countryCode + ".png";
this.currentCityName.textContent = proxyInfo.cityName;
this.countryCode = proxyInfo.countryCode;
}
}
expandUi() {
this.classList.add("expanded");
}
collapseUi() {
this.classList.remove("expanded");
}
hideEls(...els) {
els.forEach(el => {
el.style.display = "none";
});
}
async handleEvent(e) {
e.preventDefault();
e.stopPropagation();
if (e.type === "keyup" && e.key !== " ") {
return;
}
this.classList.toggle("expanded");
const { mozillaVpnHiddenToutsList } = await browser.storage.local.get("mozillaVpnHiddenToutsList");
if (typeof(mozillaVpnHiddenToutsList) === "undefined") {
await browser.storage.local.set({ "mozillaVpnHiddenToutsList":[] });
}
const toutIndex = mozillaVpnHiddenToutsList.findIndex(tout => tout.name === mozillaVpnUi.toutName);
if (toutIndex === -1) {
mozillaVpnHiddenToutsList.push({ name: mozillaVpnUi.toutName });
} else {
this.expandUi();
mozillaVpnHiddenToutsList.splice(toutIndex, 1);
}
return await browser.storage.local.set({ mozillaVpnHiddenToutsList });
}
}
customElements.define("moz-vpn-container-ui", MozVpnContainerUi);
const mozillaVpnUi = document.querySelector("moz-vpn-container-ui");
mozillaVpnUi.updateMozVpnStatusDependentUi();
browser.permissions.onAdded.addListener(() => { mozillaVpnUi.updateMozVpnStatusDependentUi(); });
browser.permissions.onRemoved.addListener(() => { mozillaVpnUi.updateMozVpnStatusDependentUi(); });
const advancedProxySettingsButton = document.querySelector(".advanced-proxy-settings-btn");
Utils.addEnterHandler(advancedProxySettingsButton, () => {
Logic.showPanel(P_ADVANCED_PROXY_SETTINGS, this.getEditInProgressIdentity(), false, false);
});
const serverListButton = document.getElementById("moz-vpn-current-server");
Utils.addEnterHandler(serverListButton, () => {
const mozVpnEnabled = document.querySelector("#moz-vpn-switch").checked;
if (!mozVpnEnabled) {
return;
}
Logic.showPanel(P_MOZILLA_VPN_SERVER_LIST, this.getEditInProgressIdentity(), false);
});
Utils.addEnterHandler(document.querySelector("#close-container-edit-panel"), () => {
// Resets listener from siteIsolation checkbox to keep the update queue to 0.
const siteIsolation = document.querySelector("#site-isolation");
siteIsolation.removeEventListener("change", addRemoveSiteIsolation, false);
const formValues = new FormData(this._editForm);
if (formValues.get("container-id") !== NEW_CONTAINER_ID) {
this._submitForm();
} else {
Logic.showPreviousPanel();
}
});
this._editForm = document.getElementById("edit-container-panel-form");
this._editForm.addEventListener("submit", () => {
this._submitForm();
});
Utils.addEnterHandler(document.querySelector("#create-container-cancel-link"), () => {
Logic.showPanel(MANAGE_CONTAINERS_PICKER);
});
Utils.addEnterHandler(document.querySelector("#create-container-ok-link"), () => {
this._submitForm();
});
// Add new site to current container
const siteLink = document.querySelector("#edit-container-site-link");
Logic.addEnterHandler(siteLink, () => {
this._addSite();
});
},
async _submitForm() {
const formValues = new FormData(this._editForm);
try {
await browser.runtime.sendMessage({
method: "createOrUpdateContainer",
message: {
userContextId: formValues.get("container-id") || NEW_CONTAINER_ID,
params: {
name: document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName(),
icon: formValues.get("container-icon") || DEFAULT_ICON,
color: formValues.get("container-color") || DEFAULT_COLOR
},
}
});
await Logic.refreshIdentities();
Logic.showPreviousPanel();
} catch (e) {
Logic.showPreviousPanel();
}
},
openServerList() {
const updatedIdentity = this.getEditInProgressIdentity();
Logic.showPanel(P_MOZILLA_VPN_SERVER_LIST, updatedIdentity, false);
},
// This prevents identity edits (change of icon, color, etc)
// from getting lost when navigating to and from one
// of the edit sub-pages (advanced proxy settings, for instance).
getEditInProgressIdentity() {
const formValues = new FormData(this._editForm);
const editedIdentity = Logic.currentIdentity();
editedIdentity.color = formValues.get("container-color") || DEFAULT_COLOR;
editedIdentity.icon = formValues.get("container-icon") || DEFAULT_ICON;
editedIdentity.name = document.getElementById("edit-container-panel-name-input").value || Logic.generateIdentityName();
return editedIdentity;
},
async _addSite() {
// Get URL and container ID from form
const formValues = new FormData(this._editForm);
const url = formValues.get("site-name");
const userContextId = formValues.get("container-id");
const currentTab = await Logic.currentTab();
const tabId = currentTab.id;
const fullURL = this.checkUrl(url);
if (fullURL !== null) {
// Assign URL to container
await Logic.setOrRemoveAssignment(tabId, fullURL, userContextId, false);
// Clear form
document.querySelector("#edit-container-panel-site-input").value = "";
// Show new assignments
const assignments = await Logic.getAssignmentObjectByContainer(userContextId);
this.showAssignedContainers(assignments);
}
},
checkUrl(url){
// append "https://" if protocol not found
const validUrl = /[\w.-]+(?:\.[\w.-]+)/g;
const regexWww = /.*www\..*/g;
const regexhttp = /^http:\/\/.*/g;
const regexhttps = /^https:\/\/.*/g;
let newURL = url;
if (!url.match(validUrl)) {
return null;
}
if (!url.match(regexhttp) && !url.match(regexhttps)) {
newURL = "https://" + url;
}
if (!url.match(regexWww)) {
if (newURL.match(regexhttp)) {
newURL = "http://www." + newURL.substring(7);
} else if (newURL.match(regexhttps)) {
newURL = "https://www." + newURL.substring(8);
}
}
return newURL;
},
showAssignedContainers(assignments) {
const assignmentPanel = document.getElementById("edit-sites-assigned");
const assignmentKeys = Object.keys(assignments);
assignmentPanel.hidden = !(assignmentKeys.length > 0);
if (assignments) {
const tableElement = assignmentPanel.querySelector(".assigned-sites-list");
/* Remove previous assignment list,
after removing one we rerender the list */
while (tableElement.firstChild) {
tableElement.firstChild.remove();
}
assignmentKeys.forEach((siteKey) => {
const site = assignments[siteKey];
const trElement = document.createElement("div");
/* As we don't have the full or correct path the best we can assume is the path is HTTPS and then replace with a broken icon later if it doesn't load.
This is pending a better solution for favicons from web extensions */
const assumedUrl = `https://${site.hostname}/favicon.ico`;
trElement.innerHTML = escaped`
${site.hostname}
`;
trElement.getElementsByClassName("favicon")[0].appendChild(Utils.createFavIconElement(assumedUrl));
const deleteButton = trElement.querySelector(".delete-assignment");
const that = this;
Logic.addEnterHandler(deleteButton, async () => {
const userContextId = Logic.currentUserContextId();
// Lets show the message to the current tab
// TODO remove then when firefox supports arrow fn async
const currentTab = await Logic.currentTab();
Logic.setOrRemoveAssignment(currentTab.id, assumedUrl, userContextId, true);
delete assignments[siteKey];
that.showAssignedContainers(assignments);
});
trElement.classList.add("container-info-tab-row", "clickable");
tableElement.appendChild(trElement);
});
}
},
initializeRadioButtons() {
const colorRadioTemplate = (containerColor) => {
return Utils.escaped`