diff --git a/.travis.yml b/.travis.yml index 26424443..77ca72dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,3 +5,6 @@ node_js: notifications: irc: - "ircs://irc.mozilla.org:6697/#testpilot-containers-bots" + +install: + - npm install --legacy-peer-deps diff --git a/bin/build-addon.sh b/bin/build-addon.sh index 0669b134..6c744c67 100755 --- a/bin/build-addon.sh +++ b/bin/build-addon.sh @@ -11,7 +11,7 @@ git submodule init || die git submodule update --remote --depth 1 src/_locales || die print Y "Installing dependencies..." -npm install || die +npm install --legacy-peer-deps || die print Y "Running tests..." npm test diff --git a/package.json b/package.json index 14143d37..7d5775f1 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,12 @@ }, "dependencies": {}, "devDependencies": { - "addons-linter": "^1.3.2", + "addons-linter": "^3.23.0", "ajv": "^6.6.3", "chai": "^4.2.0", - "eslint": "^6.6.0", - "eslint-plugin-no-unsanitized": "^2.0.0", - "eslint-plugin-promise": "^3.4.0", + "eslint": "^7.32.0", + "eslint-plugin-no-unsanitized": "^4.0.0", + "eslint-plugin-promise": "^5.2.0", "htmllint-cli": "0.0.7", "json": ">=10.0.0", "mocha": "^6.2.2", diff --git a/src/css/options.css b/src/css/options.css index 9346afeb..5a6baf0f 100644 --- a/src/css/options.css +++ b/src/css/options.css @@ -1,29 +1,122 @@ body { + --grey10: #e7e7e7; + + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; background: #fff; - color: #202023; + color: rgb(74, 74, 79); + font-size: 13px; + overflow: hidden; } -h3 { +h3:first-of-type { margin-block-start: 2.5rem; } -h3:first-of-type { - margin-block-start: 1rem; +label { + display: flex; + align-items: center; + font-size: 14px; } -p, -label { - color: rgb(74, 74, 79); +label > span { + padding-inline-end: 4px; +} + +.settings-group { + margin-block-end: 16px; +} + +form { + display: flex; + flex-direction: column; + padding-block-end: 1rem; +} + +.settings-group p { + margin-inline-start: 24px; + margin-block: 4px 8px; +} + +input[type="checkbox"] { + margin-inline: 0 8px; + margin-block: 1px auto; + inline-size: 16px; + block-size: 16px; +} + +button { + margin-inline: 0 auto; +} + +.keyboard-shortcut { + display: flex; + flex-direction: row; + justify-content: space-between; + max-inline-size: 70%; + align-items: center; +} + +.bold { + font-weight: 600; +} + +.moz-vpn-proxy-permissions { + margin-block: 0 2rem; + padding-block-end: 1rem; + border-block-end: 1px solid var(--grey10); + display: flex; + flex-direction: column; +} + +h3.moz-vpn-proxy-permissions-title { + margin-block-start: 0; + position: relative; + display: flex; + align-items: center; +} + +.warning-icon { + display: flex; + align-items: center; +} + +.warning-icon.show-warning::before { + background-image: url("/img/warning.svg"); + background-size: 24px; + background-repeat: no-repeat; + background-position: center; + content: ""; + display: block; + block-size: 24px; + inline-size: 24px; + margin-inline-end: 0.5rem; +} + +.moz-vpn-proxy-permissions-title::before, +.moz-vpn-proxy-permissions-title::after { + background-color: var(--grey10); + content: ""; + height: 1px; + flex: 1 1 0%; +} + +h3.moz-vpn-proxy-permissions-title::before { + margin-inline-end: 2rem; + margin-inline-start: -50%; +} + +h3.moz-vpn-proxy-permissions-title::after { + margin-inline-start: 2rem; + margin-inline-end: -50%; } @media (prefers-color-scheme: dark) { body { - background: #202023; + background: #23212a; color: #fff; } - p, - label { + p { color: rgb(177, 177, 179); } } diff --git a/src/css/popup.css b/src/css/popup.css index 8616236f..0aaf85b2 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -445,6 +445,7 @@ input:checked + .slider::before { /* Primary CTA Buttons */ .primary-cta { + block-size: 32px; background-color: var(--primaryCtaDefault); border: transparent; border-radius: 4px; @@ -481,10 +482,6 @@ input:checked + .slider::before { transition: opacity 0.1s ease-in-out, max-height 0.3s ease-in-out; } -#moz-vpn-tout.disappear { - animation: hideTout 0.2s ease-in forwards; -} - @keyframes appear { 0% { opacity: 0; @@ -497,22 +494,6 @@ input:checked + .slider::before { } } -@keyframes hideTout { - 0% { - transform: translateY(0%); - opacity: 1; - } - - 50% { - opacity: 1; - } - - 100% { - transform: translateY(20%); - opacity: 0; - } -} - /* Mozilla VPN Controller UI in Container Management Panel */ .moz-vpn-content, @@ -522,9 +503,6 @@ input:checked + .slider::before { flex-direction: column; padding-block: 16px; transition: max-height 0.3s ease-in-out, padding-block-end 0.2s ease-in-out; - - /* max-block-size: 56px; */ - min-block-size: 56px; box-shadow: 0 0 0 1px var(--hr-grey); } @@ -610,7 +588,8 @@ input.proxies { .moz-vpn-cta { block-size: 32px; - margin-block: 16px; + margin-block-start: 16px; + margin-block-end: 4px; margin-inline: var(--marginInline); text-align: center; } @@ -685,6 +664,7 @@ input.proxies { max-block-size: 0; opacity: 0; visibility: hidden; + display: none; background-color: var(--bgColor); transition: max-height 0.2s ease-in-out, opacity 0.2s ease-in-out, visibility 0.2s ease-in-out; } @@ -713,6 +693,7 @@ input.proxies { } .expanded .collapsible-content { + display: flex; max-block-size: 500px; opacity: 1; visibility: visible; @@ -1337,6 +1318,44 @@ input[type=text] { min-block-size: 360px; } +.panel.onboarding-panel-8.optional-permissions-disabled { + min-block-size: 420px; + margin-block-end: 0; + margin-inline: 0; +} + +.optional-permissions-disabled #moz-vpn-fw-onboarding-done { + display: none !important; +} + +.moz-vpn-permissions { + padding-block: var(--marginInline); + padding-inline: var(--marginInline); + background-color: #cececf1c; + border-block-start: 1px solid var(--hr-grey); + display: none; +} + +.optional-permissions-disabled .moz-vpn-permissions { + display: block; + inline-size: 100%; +} + +.moz-vpn-onboarding-content { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + padding-inline: var(--marginInline); + padding-block-end: var(--marginInline); +} + +.moz-vpn-permissions-copy { + padding-inline: 20px; + font-size: 12px; + margin-block-end: 16px; +} + .panel-content { flex: 1; padding-block-start: 16px; @@ -1363,8 +1382,7 @@ input[type=text] { .onboarding-title { color: #43484e; font-size: var(--font-size-heading); - margin-block-end: 0; - margin-block-start: 0; + margin-block: 12px; margin-inline-end: 0; margin-inline-start: 0; max-inline-size: 80%; @@ -1518,9 +1536,7 @@ manage things like container crud */ /* Panel footer */ .panel-footer { align-items: center; - background: #efefef; block-size: var(--footerHeight); - border-block-end: 1px solid #d8d8d8; color: #000; display: flex; font-size: 13px; @@ -1994,6 +2010,24 @@ input { text-decoration: none; } +.info-icon-alert::after { + block-size: 12px; + inline-size: 12px; + background-color: var(--alertColor); + content: "1"; + border-radius: 50%; + position: absolute; + inset-block: -5px; + inset-inline-end: -6px; + box-shadow: 0 0 1px #00000075; + font-size: 8px; + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: bolder; +} + .delete-warning { padding-block-end: 8px; padding-block-start: 8px; @@ -2031,6 +2065,36 @@ tr:hover > td > .trash-button { margin-inline-start: 8px; } +/* ----- Permissions Overlay ---------- */ + +#advanced-proxy-settings-panel, +.advanced-proxy-panel-content { + position: absolute; + inset-block: 0; + inset-inline: 0; +} + +.permissions-overlay { + position: absolute; + inset-inline: 0 0; + inset-block-start: 40px; + inset-block-end: 0; + justify-content: center; + align-content: center; + flex-direction: column; + background-color: white; + padding-block: 2.25rem; + padding-inline: 2.25rem; + display: none; +} + +#enable-proxy-permissions { + text-align: center; + font-family: var(--fontMetropolis); + font-size: 14px; + margin-block-start: 1rem; +} + @media (prefers-color-scheme: dark) { :root { --iconCloseX: url("/img/close-light.svg"); @@ -2061,6 +2125,10 @@ tr:hover > td > .trash-button { --text-grey: #fefffe; } + .permissions-overlay { + background-color: #494755; + } + .tooltip { background-color: var(--controllerActive); } @@ -2069,6 +2137,10 @@ tr:hover > td > .trash-button { box-shadow: 0 0 21px 3px #323139; } + .moz-vpn-permissions { + background-color: #322f3e; + } + .blue-link { color: #36abfc; } @@ -2152,10 +2224,6 @@ tr:hover > td > .trash-button { background-color: #676767; } - .panel-footer { - border-block-end: solid 1px #4a4a4a; - } - input[type="text"]:focus { box-shadow: 0 0 0 3px var(--blue50); border-color: var(--blue30); diff --git a/src/img/warning.svg b/src/img/warning.svg new file mode 100644 index 00000000..31b54308 --- /dev/null +++ b/src/img/warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/js/background/assignManager.js b/src/js/background/assignManager.js index fd2ff5ce..a7681222 100644 --- a/src/js/background/assignManager.js +++ b/src/js/background/assignManager.js @@ -382,6 +382,12 @@ window.assignManager = { return currentContainerState && currentContainerState.isIsolated; }, + maybeAddProxyListeners() { + if (browser.proxy) { + browser.proxy.onRequest.addListener(this.handleProxifiedRequest, {urls: [""]}); + } + }, + init() { browser.contextMenus.onClicked.addListener((info, tab) => { info.bookmarkId ? @@ -390,7 +396,7 @@ window.assignManager = { }); // Before anything happens we decide if the request should be proxified - browser.proxy.onRequest.addListener(this.handleProxifiedRequest, {urls: [""]}); + this.maybeAddProxyListeners(); // Before a request is handled by the browser we decide if we should // route through a different container diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index 05cad399..7cf52161 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -19,6 +19,28 @@ const backgroundLogic = { } } }); + + browser.permissions.onAdded.addListener(permissions => this.resetPermissions(permissions)); + browser.permissions.onRemoved.addListener(permissions => this.resetPermissions(permissions)); + }, + + resetPermissions(permissions) { + permissions.permissions.forEach(async permission => { + switch (permission) { + case "bookmarks": + assignManager.resetBookmarksMenuItem(); + break; + + case "nativeMessaging": + await MozillaVPN_Background.removeMozillaVpnProxies(); + await browser.runtime.reload(); + break; + + case "proxy": + assignManager.maybeAddProxyListeners(); + break; + } + }); }, async getExtensionInfo() { diff --git a/src/js/background/messageHandler.js b/src/js/background/messageHandler.js index 37182bb0..5d644b60 100644 --- a/src/js/background/messageHandler.js +++ b/src/js/background/messageHandler.js @@ -20,9 +20,6 @@ const messageHandler = { case "resetSync": response = sync.resetSync(); break; - case "resetBookmarksContext": - response = assignManager.resetBookmarksMenuItem(); - break; case "deleteContainer": response = backgroundLogic.deleteContainer(m.message.userContextId); break; diff --git a/src/js/background/mozillaVpnBackground.js b/src/js/background/mozillaVpnBackground.js index 8a127304..2711302d 100644 --- a/src/js/background/mozillaVpnBackground.js +++ b/src/js/background/mozillaVpnBackground.js @@ -100,6 +100,19 @@ const MozillaVPN_Background = { get isolationKey() { return this._isolationKey; }, + + async removeMozillaVpnProxies() { + const proxies = await proxifiedContainers.retrieveAll(); + if (!proxies) { + return; + } + for (const proxyObj of proxies) { + const { proxy } = proxyObj; + if (proxy.countryCode !== undefined) { + await proxifiedContainers.delete(proxyObj.cookieStoreId); + } + } + }, }; MozillaVPN_Background.init(); diff --git a/src/js/mozillaVpn.js b/src/js/mozillaVpn.js index feedd328..941e148a 100644 --- a/src/js/mozillaVpn.js +++ b/src/js/mozillaVpn.js @@ -5,6 +5,11 @@ const MozillaVPN = { const mozillaVpnInstalled = await browser.runtime.sendMessage({ method: "MozillaVPN_getInstallationStatus" }); this.handleStatusIndicatorsInContainerLists(mozillaVpnInstalled); + const permissionsEnabled = await this.bothPermissionsEnabled(); + if (!permissionsEnabled) { + return; + } + const proxies = await this.getProxies(identities); if (Object.keys(proxies).length === 0) { return; @@ -156,6 +161,10 @@ const MozillaVPN = { }; }, + async bothPermissionsEnabled() { + return await browser.permissions.contains({ permissions: ["proxy", "nativeMessaging"] }); + }, + async getProxyWarnings(proxyObj) { if (!proxyObj) { @@ -245,7 +254,7 @@ const MozillaVPN = { randomInteger = (randomInteger - server.weight); } return nextServer; - } + }, }; window.MozillaVPN = MozillaVPN; diff --git a/src/js/options.js b/src/js/options.js index 025ae996..726827b5 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -1,17 +1,45 @@ const NUMBER_OF_KEYBOARD_SHORTCUTS = 10; -async function requestPermissions() { - const checkbox = document.querySelector("#bookmarksPermissions"); - if (checkbox.checked) { - const granted = await browser.permissions.request({permissions: ["bookmarks"]}); - if (!granted) { - checkbox.checked = false; +async function setUpCheckBoxes() { + document.querySelectorAll("[data-permission-id]").forEach(async(el) => { + const permissionId = el.dataset.permissionId; + const permissionEnabled = await browser.permissions.contains({ permissions: [permissionId] }); + el.checked = !!permissionEnabled; + }); +} + +function disablePermissionsInputs() { + document.querySelectorAll("[data-permission-id").forEach(el => { + el.disabled = true; + }); +} + +function enablePermissionsInputs() { + document.querySelectorAll("[data-permission-id").forEach(el => { + el.disabled = false; + }); +} + +document.querySelectorAll("[data-permission-id").forEach(async(el) => { + const permissionId = el.dataset.permissionId; + el.addEventListener("change", async() => { + if (el.checked) { + disablePermissionsInputs(); + const granted = await browser.permissions.request({ permissions: [permissionId] }); + if (!granted) { + el.checked = false; + enablePermissionsInputs(); + } return; } - } else { - await browser.permissions.remove({permissions: ["bookmarks"]}); - } - browser.runtime.sendMessage({ method: "resetBookmarksContext" }); + await browser.permissions.remove({ permissions: [permissionId] }); + }); +}); + +async function maybeShowPermissionsWarningIcon() { + const bothMozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled(); + const permissionsWarningEl = document.querySelector(".warning-icon"); + permissionsWarningEl.classList.toggle("show-warning", !bothMozillaVpnPermissionsEnabled); } async function enableDisableSync() { @@ -26,12 +54,8 @@ async function enableDisableReplaceTab() { } async function setupOptions() { - const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]}); const { syncEnabled } = await browser.storage.local.get("syncEnabled"); const { replaceTabEnabled } = await browser.storage.local.get("replaceTabEnabled"); - if (hasPermission) { - document.querySelector("#bookmarksPermissions").checked = true; - } document.querySelector("#syncCheck").checked = !!syncEnabled; document.querySelector("#replaceTabCheck").checked = !!replaceTabEnabled; setupContainerShortcutSelects(); @@ -78,13 +102,36 @@ function resetOnboarding() { browser.storage.local.set({"onboarding-stage": 0}); } +async function resetPermissionsUi() { + await maybeShowPermissionsWarningIcon(); + await setUpCheckBoxes(); + enablePermissionsInputs(); +} + +browser.permissions.onAdded.addListener(resetPermissionsUi); +browser.permissions.onRemoved.addListener(resetPermissionsUi); + document.addEventListener("DOMContentLoaded", setupOptions); -document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions); document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync); document.querySelector("#replaceTabCheck").addEventListener( "change", enableDisableReplaceTab); -document.querySelector("button").addEventListener("click", resetOnboarding); - +maybeShowPermissionsWarningIcon(); for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { document.querySelector("#open_container_"+i) .addEventListener("change", storeShortcutChoice); -} \ No newline at end of file +} + +document.querySelectorAll("[data-btn-id]").forEach(btn => { + btn.addEventListener("click", () => { + switch (btn.dataset.btnId) { + case "reset-onboarding": + resetOnboarding(); + break; + case "moz-vpn-learn-more": + browser.tabs.create({ + url: MozillaVPN.attachUtmParameters("https://support.mozilla.org/kb/protect-your-container-tabs-mozilla-vpn", "options-learn-more") + }); + break; + } + }); +}); +resetPermissionsUi(); diff --git a/src/js/popup.js b/src/js/popup.js index 0b82f7fc..f7f62f20 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -643,14 +643,29 @@ Logic.registerPanel(P_ONBOARDING_8, { // This method is called when the object is registered. initialize() { - Utils.addEnterHandler(document.querySelector("#onboarding-done-btn"), async () => { - await Logic.setOnboardingStage(8); - Logic.showPanel(P_CONTAINERS_LIST); + document.querySelectorAll(".onboarding-done").forEach(el => { + Utils.addEnterHandler(el, async () => { + await Logic.setOnboardingStage(8); + Logic.showPanel(P_CONTAINERS_LIST); + }); }); + }, // This method is called when the panel is shown. - prepare() { + async prepare() { + const mozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled(); + if (!mozillaVpnPermissionsEnabled) { + const panel = document.querySelector(".onboarding-panel-8"); + panel.classList.add("optional-permissions-disabled"); + + Utils.addEnterHandler(panel.querySelector("#onboarding-enable-permissions"), async () => { + const granted = await browser.permissions.request({ permissions: ["proxy", "nativeMessaging"] }); + if (granted) { + await Logic.setOnboardingStage(8); + } + }); + } return Promise.resolve(null); }, }); @@ -662,24 +677,7 @@ Logic.registerPanel(P_CONTAINERS_LIST, { // This method is called when the object is registered. async initialize() { - const mozillaVpnToutName = "moz-tout-main-panel"; - await browser.runtime.sendMessage({ method: "MozillaVPN_queryStatus" }); - Utils.addEnterHandler(document.querySelector("#moz-vpn-learn-more"), () => { - MozillaVPN.handleMozillaCtaClick("mac-main-panel-btn"); - window.close(); - }); - Utils.addEnterHandler(document.querySelector(".dismiss-moz-vpn-tout"), async() => { - const { mozillaVpnHiddenToutsList } = await browser.storage.local.get("mozillaVpnHiddenToutsList"); - if (typeof(mozillaVpnHiddenToutsList) === "undefined") { - await browser.storage.local.set({ "mozillaVpnHiddenToutsList": [] }); - } - document.querySelector("#moz-vpn-tout").classList.add("disappear"); - mozillaVpnHiddenToutsList.push({ - name: mozillaVpnToutName - }); - await browser.storage.local.set({ mozillaVpnHiddenToutsList }); - }); Utils.addEnterHandler(document.querySelector("#manage-containers-link"), (e) => { if (!e.target.classList.contains("disable-edit-containers")) { Logic.showPanel(MANAGE_CONTAINERS_PICKER); @@ -694,9 +692,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, { Utils.addEnterHandler(document.querySelector("#always-open-in"), () => { Logic.showPanel(ALWAYS_OPEN_IN_PICKER); }); - Utils.addEnterHandler(document.querySelector("#info-icon"), () => { - browser.runtime.openOptionsPage(); - }); Utils.addEnterHandler(document.querySelector("#sort-containers-link"), async () => { try { await browser.runtime.sendMessage({ @@ -708,16 +703,58 @@ Logic.registerPanel(P_CONTAINERS_LIST, { } }); + const mozillaVpnToutName = "moz-tout-main-panel"; + const mozillaVpnPermissionsWarningDotName = "moz-permissions-warning-dot"; + + let { mozillaVpnHiddenToutsList } = await browser.storage.local.get("mozillaVpnHiddenToutsList"); + if (typeof(mozillaVpnHiddenToutsList) === "undefined") { + await browser.storage.local.set({ "mozillaVpnHiddenToutsList": [] }); + mozillaVpnHiddenToutsList = []; + } + + // Decide whether to show Mozilla VPN tout const mozVpnTout = document.getElementById("moz-vpn-tout"); const mozillaVpnInstalled = await browser.runtime.sendMessage({ method: "MozillaVPN_getInstallationStatus" }); - if (mozillaVpnInstalled) { - return mozVpnTout.remove(); + const mozillaVpnToutShouldBeHidden = mozillaVpnHiddenToutsList.find(tout => tout.name === mozillaVpnToutName); + if (mozillaVpnInstalled || mozillaVpnToutShouldBeHidden) { + mozVpnTout.remove(); } - const { mozillaVpnHiddenToutsList } = await browser.storage.local.get("mozillaVpnHiddenToutsList"); - const mozillaVpnToutShouldBeHidden = mozillaVpnHiddenToutsList && mozillaVpnHiddenToutsList.find(tout => tout.name === mozillaVpnToutName); - if (mozillaVpnToutShouldBeHidden) { - return mozVpnTout.remove(); + + // Add handlers if tout is visible + const mozVpnDismissTout = document.querySelector(".dismiss-moz-vpn-tout"); + if (mozVpnDismissTout) { + Utils.addEnterHandler((mozVpnDismissTout), async() => { + mozVpnTout.remove(); + mozillaVpnHiddenToutsList.push({ + name: mozillaVpnToutName + }); + await browser.storage.local.set({ mozillaVpnHiddenToutsList }); + }); + + Utils.addEnterHandler(document.querySelector("#moz-vpn-learn-more"), () => { + MozillaVPN.handleMozillaCtaClick("mac-main-panel-btn"); + window.close(); + }); + } + + // Badge Options icon if both nativeMessaging and/or proxy permissions are disabled + const bothMozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled(); + const warningDotShouldBeHidden = mozillaVpnHiddenToutsList.find(tout => tout.name === mozillaVpnPermissionsWarningDotName); + const optionsIcon = document.getElementById("info-icon"); + if (optionsIcon && !bothMozillaVpnPermissionsEnabled && !warningDotShouldBeHidden) { + optionsIcon.classList.add("info-icon-alert"); } + + Utils.addEnterHandler((document.querySelector("#info-icon")), async() => { + browser.runtime.openOptionsPage(); + if (!mozillaVpnHiddenToutsList.find(tout => tout.name === mozillaVpnPermissionsWarningDotName)) { + optionsIcon.classList.remove("info-icon-alert"); + mozillaVpnHiddenToutsList.push({ + name: mozillaVpnPermissionsWarningDotName + }); + } + await browser.storage.local.set({ mozillaVpnHiddenToutsList }); + }); }, unregister() { @@ -799,7 +836,6 @@ Logic.registerPanel(P_CONTAINERS_LIST, { Utils.addEnterHandler(showPanelButton, () => { Logic.showPanel(P_CONTAINER_INFO, identity); }); - } const list = document.querySelector("#identities-list"); @@ -1438,18 +1474,25 @@ Logic.registerPanel(P_CONTAINER_EDIT, { 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) { + if (mozillaVpnCollapseEditContainerTout && !mozillaVpnInstalled) { this.collapseUi(); } // Add listeners if (!this.classList.contains("has-attached-listeners")) { - this.primaryCta.addEventListener("click", () => { - MozillaVPN.handleMozillaCtaClick("mac-edit-container-panel-btn"); + 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() => { @@ -1502,7 +1545,6 @@ Logic.registerPanel(P_CONTAINER_EDIT, { await proxifiedContainers.set(id.cookieStoreId, proxy); this.switch.checked = true; this.updateProxyDependentUi(proxy); - } else { this.switch.checked = false; return; @@ -1518,24 +1560,35 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const mozillaVpnInstalled = await browser.runtime.sendMessage({ method: "MozillaVPN_getInstallationStatus" }); const mozillaVpnConnected = await browser.runtime.sendMessage({ method: "MozillaVPN_getConnectionStatus" }); - if (!mozillaVpnInstalled) { + this.subtitle.textContent = browser.i18n.getMessage("integrateContainers"); - this.hideEls(this.switch, this.switchLabel, this.currentServerButton); - this.subtitle.textContent = browser.i18n.getMessage("protectThisContainer"); - this.primaryCta.addEventListener("click", this); + const bothMozillaVpnPermissionsEnabled = await MozillaVPN.bothPermissionsEnabled(); - } else { - - // Mozilla VPN installed... + 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) { + 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; } @@ -1589,10 +1642,8 @@ Logic.registerPanel(P_CONTAINER_EDIT, { } async updateProxyDependentUi(proxyInfo) { - const containerHasProxy = typeof(proxyInfo) !== "undefined"; - const mozillaVpnProxyLocationAvailable = (proxy) => { - return typeof(proxy.countryCode) !== "undefined" && typeof(proxyInfo.cityName) !== "undefined"; + return typeof(proxy) !== "undefined" && typeof(proxy.countryCode) !== "undefined" && typeof(proxy.cityName) !== "undefined"; }; const mozillaVpnProxyIsEnabled = (proxy) => { @@ -1605,7 +1656,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { const mozillaVpnConnected = await browser.runtime.sendMessage({ method: "MozillaVPN_getConnectionStatus" }); if ( - !containerHasProxy || + !proxyInfo || !mozillaVpnProxyLocationAvailable(proxyInfo) || !mozillaVpnConnected ) { @@ -1620,7 +1671,7 @@ Logic.registerPanel(P_CONTAINER_EDIT, { } // Populate inputs and server button with current or previously stored mozilla vpn proxy - if(containerHasProxy && mozillaVpnProxyLocationAvailable(proxyInfo)) { + 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; @@ -1631,12 +1682,10 @@ Logic.registerPanel(P_CONTAINER_EDIT, { expandUi() { this.classList.add("expanded"); - this.style.maxHeight = 500 + "px"; } collapseUi() { this.classList.remove("expanded"); - this.style.maxHeight = 56 + "px"; } hideEls(...els) { @@ -1673,6 +1722,10 @@ Logic.registerPanel(P_CONTAINER_EDIT, { 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); @@ -1835,23 +1888,22 @@ Logic.registerPanel(P_CONTAINER_EDIT, { return; } - const proxyData = await proxifiedContainers.retrieve(identity.cookieStoreId); - if (proxyData) { - if (proxyData.proxy && proxyData.proxy.mozProxyEnabled && !mozillaVpnConnected) { + const proxyPermissionEnabled = await browser.permissions.contains({ permissions: ["proxy"] }); + if (proxyPermissionEnabled) { + const proxyData = await proxifiedContainers.retrieve(identity.cookieStoreId); + if (proxyData && proxyData.proxy.mozProxyEnabled && !mozillaVpnConnected) { return; } - mozillaVpnUi.updateProxyDependentUi(proxyData.proxy); - return; + const proxy = proxyData ? proxyData.proxy : {}; + mozillaVpnUi.updateProxyDependentUi(proxy); } - - mozillaVpnUi.updateProxyDependentUi({}); }, }); Logic.registerPanel(P_ADVANCED_PROXY_SETTINGS, { panelSelector: "#advanced-proxy-settings-panel", - initialize(){ + async initialize() { this._proxyForm = document.querySelector(".advanced-proxy-panel-content"); this._advancedProxyInput = this._proxyForm.querySelector("#edit-advanced-proxy-input"); const clearAdvancedProxyInput = this._proxyForm.querySelector("#clear-advanced-proxy-input"); @@ -1931,6 +1983,40 @@ Logic.registerPanel(P_ADVANCED_PROXY_SETTINGS, { const identity = Logic.currentIdentity(); const advancedProxyInput = document.getElementById("edit-advanced-proxy-input"); + const proxyPermissionEnabled = await browser.permissions.contains({ permissions: ["proxy"] }); + if (!proxyPermissionEnabled) { + + // Restrict tabbing inside advanced proxy panel to proxy permissions ui + const panel = document.getElementById("advanced-proxy-settings-panel"); + const clickableEls = panel.querySelectorAll("button, a, input"); + clickableEls.forEach(el => { + if (!el.dataset.tabGroup && el.id !== "advanced-proxy-settings-return") { + el.setAttribute("tabindex", "-1"); + el.disabled = true; + } + }); + + // Show proxy permission overlay + const permissionsOverlay = document.getElementById("permissions-overlay"); + permissionsOverlay.style.display = "flex"; + + // Add "enable" button handling + const enableProxyPermissionsButton = document.getElementById("enable-proxy-permissions"); + + enableProxyPermissionsButton.addEventListener("click", async() => { + const granted = await browser.permissions.request({ permissions: ["proxy"] }); + if (granted) { + permissionsOverlay.style.display = "none"; + // restore normal panel tabbing + clickableEls.forEach(el => { + el.tabindex = "0"; + el.disabled = false; + }); + } + }); + } + + // reset input const resetProxyInput = () => { if (!advancedProxyInput) { diff --git a/src/manifest.json b/src/manifest.json index c236a9ab..ba11b450 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -18,16 +18,16 @@ "history", "idle", "management", - "nativeMessaging", "storage", "unlimitedStorage", "tabs", "webRequestBlocking", - "webRequest", - "proxy" + "webRequest" ], "optional_permissions": [ - "bookmarks" + "bookmarks", + "nativeMessaging", + "proxy" ], "browser_specific_settings": { "gecko": { diff --git a/src/options.html b/src/options.html index 3f9b66c4..607c10a9 100644 --- a/src/options.html +++ b/src/options.html @@ -4,85 +4,119 @@ + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + - - - - - + + + + + + + + + - + + - + - + - + - + - + - + - + - + - + - + + Mozilla VPN + diff --git a/src/popup.html b/src/popup.html index b0ed48f7..ab01ec3f 100644 --- a/src/popup.html +++ b/src/popup.html @@ -62,11 +62,22 @@ - - - - - + + + + + + + + + + + + + + + + @@ -98,7 +109,7 @@ Multi-Account Containers - + @@ -170,18 +181,18 @@ Mozilla VPN - - Mozilla VPN - - - - - - - + + Mozilla VPN + + + + + + - + + @@ -324,7 +335,7 @@ Mozilla VPN - + @@ -437,6 +448,10 @@ + + + Enable +
+ + - + - + - + - + - + - + - + - + - + - + + Mozilla VPN +
+ - + - + - + - + - + - + - + - + - + + Mozilla VPN +
+ - + - + - + - + - + - + - + - + + Mozilla VPN +
+ - + - + - + - + - + - + - + + Mozilla VPN +
+ - + - + - + - + - + - + + Mozilla VPN +
+ - + - + - + - + - + + Mozilla VPN +
+ - + - + - + - + + Mozilla VPN +
+ - + - + - + + Mozilla VPN +
+ - + - + + Mozilla VPN +
+ - + + Mozilla VPN +