From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../components/preferences/dialogs/connection.js | 653 +++++++++++++++++++++ 1 file changed, 653 insertions(+) create mode 100644 browser/components/preferences/dialogs/connection.js (limited to 'browser/components/preferences/dialogs/connection.js') diff --git a/browser/components/preferences/dialogs/connection.js b/browser/components/preferences/dialogs/connection.js new file mode 100644 index 0000000000..06c070dc54 --- /dev/null +++ b/browser/components/preferences/dialogs/connection.js @@ -0,0 +1,653 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from /browser/base/content/utilityOverlay.js */ +/* import-globals-from /toolkit/content/preferencesBindings.js */ +/* import-globals-from ../extensionControlled.js */ + +ChromeUtils.defineModuleGetter( + this, + "DoHConfigController", + "resource:///modules/DoHConfig.jsm" +); + +document + .getElementById("ConnectionsDialog") + .addEventListener("dialoghelp", window.top.openPrefsHelp); + +Preferences.addAll([ + // Add network.proxy.autoconfig_url before network.proxy.type so they're + // both initialized when network.proxy.type initialization triggers a call to + // gConnectionsDialog.updateReloadButton(). + { id: "network.proxy.autoconfig_url", type: "string" }, + { id: "network.proxy.type", type: "int" }, + { id: "network.proxy.http", type: "string" }, + { id: "network.proxy.http_port", type: "int" }, + { id: "network.proxy.ssl", type: "string" }, + { id: "network.proxy.ssl_port", type: "int" }, + { id: "network.proxy.socks", type: "string" }, + { id: "network.proxy.socks_port", type: "int" }, + { id: "network.proxy.socks_version", type: "int" }, + { id: "network.proxy.socks_remote_dns", type: "bool" }, + { id: "network.proxy.no_proxies_on", type: "string" }, + { id: "network.proxy.share_proxy_settings", type: "bool" }, + { id: "signon.autologin.proxy", type: "bool" }, + { id: "pref.advanced.proxies.disable_button.reload", type: "bool" }, + { id: "network.proxy.backup.ssl", type: "string" }, + { id: "network.proxy.backup.ssl_port", type: "int" }, + { id: "network.trr.mode", type: "int" }, + { id: "network.trr.uri", type: "string" }, + { id: "network.trr.custom_uri", type: "string" }, + { id: "doh-rollout.disable-heuristics", type: "bool" }, + { id: "doh-rollout.skipHeuristicsCheck", type: "bool" }, +]); + +const DoHConfigObserver = () => { + gConnectionsDialog.initDnsOverHttpsUI(); +}; + +window.addEventListener( + "DOMContentLoaded", + () => { + Preferences.get("network.proxy.type").on( + "change", + gConnectionsDialog.proxyTypeChanged.bind(gConnectionsDialog) + ); + Preferences.get("network.proxy.socks_version").on( + "change", + gConnectionsDialog.updateDNSPref.bind(gConnectionsDialog) + ); + + Preferences.get("network.trr.uri").on("change", () => { + gConnectionsDialog.updateDnsOverHttpsUI(); + }); + + Services.obs.addObserver( + DoHConfigObserver, + DoHConfigController.kConfigUpdateTopic + ); + window.addEventListener( + "unload", + e => { + Services.obs.removeObserver( + DoHConfigObserver, + DoHConfigController.kConfigUpdateTopic + ); + }, + { once: true } + ); + + // XXX: We can't init the DNS-over-HTTPs UI until the onsyncfrompreference for network.trr.mode + // has been called. The uiReady promise will be resolved after the first call to + // readDnsOverHttpsMode and the subsequent call to initDnsOverHttpsUI has happened. + gConnectionsDialog.uiReady = new Promise(resolve => { + gConnectionsDialog._areTrrPrefsReady = false; + gConnectionsDialog._handleTrrPrefsReady = resolve; + }).then(async () => { + // awaiting this ensures that initDnsOverHttpsUI() is called after + // execution has been returned to the caller of _handleTrrPrefsReady, + // which is the checkbox value reading path. This ensures the checkbox + // gets checked, then initDnsOverHttpsUI() is called, then the uiReady + // promise resolves, preventing intermittent failures in tests. + await gConnectionsDialog.initDnsOverHttpsUI(); + }); + + document + .getElementById("disableProxyExtension") + .addEventListener( + "command", + makeDisableControllingExtension(PREF_SETTING_TYPE, PROXY_KEY).bind( + gConnectionsDialog + ) + ); + gConnectionsDialog.updateProxySettingsUI(); + initializeProxyUI(gConnectionsDialog); + gConnectionsDialog.registerSyncPrefListeners(); + document + .getElementById("ConnectionsDialog") + .addEventListener("beforeaccept", e => + gConnectionsDialog.beforeAccept(e) + ); + }, + { once: true, capture: true } +); + +var gConnectionsDialog = { + beforeAccept(event) { + let dnsOverHttpsResolverChoice = document.getElementById( + "networkDnsOverHttpsResolverChoices" + ).value; + let writeURIandMode = uri => { + Services.prefs.setStringPref("network.trr.uri", uri); + // When writing the URI, also write the mode. This is needed in addition + // to the mode reacting in realtime to the checkbox state because of the + // case when the checkbox was ticked due to the rollout being enabled at + // the time of clicking "Accept". + Services.prefs.setIntPref( + "network.trr.mode", + this.writeDnsOverHttpsMode() + ); + }; + // We treat clicking "Accept" as a user choice, and set both the TRR + // URI and mode here. This will cause DoHController to permanently + // disable heuristics and the values at the time of accept will persist. + // This includes the case when no changes were made. + if (dnsOverHttpsResolverChoice == "custom") { + let customValue = document + .getElementById("networkCustomDnsOverHttpsInput") + .value.trim(); + if (customValue) { + writeURIandMode(customValue); + } else { + writeURIandMode(DoHConfigController.currentConfig.fallbackProviderURI); + } + } else { + writeURIandMode(dnsOverHttpsResolverChoice); + } + + var proxyTypePref = Preferences.get("network.proxy.type"); + if (proxyTypePref.value == 2) { + this.doAutoconfigURLFixup(); + return; + } + + if (proxyTypePref.value != 1) { + return; + } + + var httpProxyURLPref = Preferences.get("network.proxy.http"); + var httpProxyPortPref = Preferences.get("network.proxy.http_port"); + var shareProxiesPref = Preferences.get( + "network.proxy.share_proxy_settings" + ); + + // If the port is 0 and the proxy server is specified, focus on the port and cancel submission. + for (let prefName of ["http", "ssl", "socks"]) { + let proxyPortPref = Preferences.get( + "network.proxy." + prefName + "_port" + ); + let proxyPref = Preferences.get("network.proxy." + prefName); + // Only worry about ports which are currently active. If the share option is on, then ignore + // all ports except the HTTP and SOCKS port + if ( + proxyPref.value != "" && + proxyPortPref.value == 0 && + (prefName == "http" || prefName == "socks" || !shareProxiesPref.value) + ) { + document + .getElementById("networkProxy" + prefName.toUpperCase() + "_Port") + .focus(); + event.preventDefault(); + return; + } + } + + // In the case of a shared proxy preference, backup the current values and update with the HTTP value + if (shareProxiesPref.value) { + var proxyServerURLPref = Preferences.get("network.proxy.ssl"); + var proxyPortPref = Preferences.get("network.proxy.ssl_port"); + var backupServerURLPref = Preferences.get("network.proxy.backup.ssl"); + var backupPortPref = Preferences.get("network.proxy.backup.ssl_port"); + backupServerURLPref.value = + backupServerURLPref.value || proxyServerURLPref.value; + backupPortPref.value = backupPortPref.value || proxyPortPref.value; + proxyServerURLPref.value = httpProxyURLPref.value; + proxyPortPref.value = httpProxyPortPref.value; + } + + this.sanitizeNoProxiesPref(); + }, + + checkForSystemProxy() { + if ("@mozilla.org/system-proxy-settings;1" in Cc) { + document.getElementById("systemPref").removeAttribute("hidden"); + } + }, + + proxyTypeChanged() { + var proxyTypePref = Preferences.get("network.proxy.type"); + + // Update http + var httpProxyURLPref = Preferences.get("network.proxy.http"); + httpProxyURLPref.updateControlDisabledState(proxyTypePref.value != 1); + var httpProxyPortPref = Preferences.get("network.proxy.http_port"); + httpProxyPortPref.updateControlDisabledState(proxyTypePref.value != 1); + + // Now update the other protocols + this.updateProtocolPrefs(); + + var shareProxiesPref = Preferences.get( + "network.proxy.share_proxy_settings" + ); + shareProxiesPref.updateControlDisabledState(proxyTypePref.value != 1); + var autologinProxyPref = Preferences.get("signon.autologin.proxy"); + autologinProxyPref.updateControlDisabledState(proxyTypePref.value == 0); + var noProxiesPref = Preferences.get("network.proxy.no_proxies_on"); + noProxiesPref.updateControlDisabledState(proxyTypePref.value == 0); + + var autoconfigURLPref = Preferences.get("network.proxy.autoconfig_url"); + autoconfigURLPref.updateControlDisabledState(proxyTypePref.value != 2); + + this.updateReloadButton(); + + document.getElementById( + "networkProxyNoneLocalhost" + ).hidden = Services.prefs.getBoolPref( + "network.proxy.allow_hijacking_localhost", + false + ); + }, + + updateDNSPref() { + var socksVersionPref = Preferences.get("network.proxy.socks_version"); + var socksDNSPref = Preferences.get("network.proxy.socks_remote_dns"); + var proxyTypePref = Preferences.get("network.proxy.type"); + var isDefinitelySocks4 = + proxyTypePref.value == 1 && socksVersionPref.value == 4; + socksDNSPref.updateControlDisabledState( + isDefinitelySocks4 || proxyTypePref.value == 0 + ); + return undefined; + }, + + updateReloadButton() { + // Disable the "Reload PAC" button if the selected proxy type is not PAC or + // if the current value of the PAC input does not match the value stored + // in prefs. Likewise, disable the reload button if PAC is not configured + // in prefs. + + var typedURL = document.getElementById("networkProxyAutoconfigURL").value; + var proxyTypeCur = Preferences.get("network.proxy.type").value; + + var pacURL = Services.prefs.getCharPref("network.proxy.autoconfig_url"); + var proxyType = Services.prefs.getIntPref("network.proxy.type"); + + var disableReloadPref = Preferences.get( + "pref.advanced.proxies.disable_button.reload" + ); + disableReloadPref.updateControlDisabledState( + proxyTypeCur != 2 || proxyType != 2 || typedURL != pacURL + ); + }, + + readProxyType() { + this.proxyTypeChanged(); + return undefined; + }, + + updateProtocolPrefs() { + var proxyTypePref = Preferences.get("network.proxy.type"); + var shareProxiesPref = Preferences.get( + "network.proxy.share_proxy_settings" + ); + var proxyPrefs = ["ssl", "socks"]; + for (var i = 0; i < proxyPrefs.length; ++i) { + var proxyServerURLPref = Preferences.get( + "network.proxy." + proxyPrefs[i] + ); + var proxyPortPref = Preferences.get( + "network.proxy." + proxyPrefs[i] + "_port" + ); + + // Restore previous per-proxy custom settings, if present. + if (proxyPrefs[i] != "socks" && !shareProxiesPref.value) { + var backupServerURLPref = Preferences.get( + "network.proxy.backup." + proxyPrefs[i] + ); + var backupPortPref = Preferences.get( + "network.proxy.backup." + proxyPrefs[i] + "_port" + ); + if (backupServerURLPref.hasUserValue) { + proxyServerURLPref.value = backupServerURLPref.value; + backupServerURLPref.reset(); + } + if (backupPortPref.hasUserValue) { + proxyPortPref.value = backupPortPref.value; + backupPortPref.reset(); + } + } + + proxyServerURLPref.updateElements(); + proxyPortPref.updateElements(); + let prefIsShared = proxyPrefs[i] != "socks" && shareProxiesPref.value; + proxyServerURLPref.updateControlDisabledState( + proxyTypePref.value != 1 || prefIsShared + ); + proxyPortPref.updateControlDisabledState( + proxyTypePref.value != 1 || prefIsShared + ); + } + var socksVersionPref = Preferences.get("network.proxy.socks_version"); + socksVersionPref.updateControlDisabledState(proxyTypePref.value != 1); + this.updateDNSPref(); + return undefined; + }, + + readProxyProtocolPref(aProtocol, aIsPort) { + if (aProtocol != "socks") { + var shareProxiesPref = Preferences.get( + "network.proxy.share_proxy_settings" + ); + if (shareProxiesPref.value) { + var pref = Preferences.get( + "network.proxy.http" + (aIsPort ? "_port" : "") + ); + return pref.value; + } + + var backupPref = Preferences.get( + "network.proxy.backup." + aProtocol + (aIsPort ? "_port" : "") + ); + return backupPref.hasUserValue ? backupPref.value : undefined; + } + return undefined; + }, + + reloadPAC() { + Cc["@mozilla.org/network/protocol-proxy-service;1"] + .getService() + .reloadPAC(); + }, + + doAutoconfigURLFixup() { + var autoURL = document.getElementById("networkProxyAutoconfigURL"); + var autoURLPref = Preferences.get("network.proxy.autoconfig_url"); + try { + autoURLPref.value = autoURL.value = Services.uriFixup.getFixupURIInfo( + autoURL.value + ).preferredURI.spec; + } catch (ex) {} + }, + + sanitizeNoProxiesPref() { + var noProxiesPref = Preferences.get("network.proxy.no_proxies_on"); + // replace substrings of ; and \n with commas if they're neither immediately + // preceded nor followed by a valid separator character + noProxiesPref.value = noProxiesPref.value.replace( + /([^, \n;])[;\n]+(?![,\n;])/g, + "$1," + ); + // replace any remaining ; and \n since some may follow commas, etc. + noProxiesPref.value = noProxiesPref.value.replace(/[;\n]/g, ""); + }, + + readHTTPProxyServer() { + var shareProxiesPref = Preferences.get( + "network.proxy.share_proxy_settings" + ); + if (shareProxiesPref.value) { + this.updateProtocolPrefs(); + } + return undefined; + }, + + readHTTPProxyPort() { + var shareProxiesPref = Preferences.get( + "network.proxy.share_proxy_settings" + ); + if (shareProxiesPref.value) { + this.updateProtocolPrefs(); + } + return undefined; + }, + + getProxyControls() { + let controlGroup = document.getElementById("networkProxyType"); + return [ + ...controlGroup.querySelectorAll(":scope > radio"), + ...controlGroup.querySelectorAll("label"), + ...controlGroup.querySelectorAll("input"), + ...controlGroup.querySelectorAll("checkbox"), + ...document.querySelectorAll("#networkProxySOCKSVersion > radio"), + ...document.querySelectorAll("#ConnectionsDialogPane > checkbox"), + ]; + }, + + // Update the UI to show/hide the extension controlled message for + // proxy settings. + async updateProxySettingsUI() { + let isLocked = API_PROXY_PREFS.some(pref => + Services.prefs.prefIsLocked(pref) + ); + + function setInputsDisabledState(isControlled) { + for (let element of gConnectionsDialog.getProxyControls()) { + element.disabled = isControlled; + } + gConnectionsDialog.proxyTypeChanged(); + } + + if (isLocked) { + // An extension can't control this setting if any pref is locked. + hideControllingExtension(PROXY_KEY); + } else { + handleControllingExtension(PREF_SETTING_TYPE, PROXY_KEY).then( + setInputsDisabledState + ); + } + }, + + get dnsOverHttpsResolvers() { + let providers = DoHConfigController.currentConfig.providerList; + // if there's no default, we'll hold its position with an empty string + let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI; + let defaultIndex = providers.findIndex(p => p.uri == defaultURI); + if (defaultIndex == -1 && defaultURI) { + // the default value for the pref isn't included in the resolvers list + // so we'll make a stub for it. Without an id, we'll have to use the url as the label + providers.unshift({ uri: defaultURI }); + } + return providers; + }, + + isDnsOverHttpsLocked() { + return Services.prefs.prefIsLocked("network.trr.mode"); + }, + + isDnsOverHttpsEnabled() { + // We consider DoH enabled if: + // 1. network.trr.mode has a user-set value equal to 2 or 3. + // 2. network.trr.mode is 0, and DoH heuristics are enabled + let trrPref = Preferences.get("network.trr.mode"); + if (trrPref.value > 0) { + return trrPref.value == 2 || trrPref.value == 3; + } + + let rolloutEnabled = DoHConfigController.currentConfig.enabled; + let heuristicsDisabled = + Preferences.get("doh-rollout.disable-heuristics").value || + Preferences.get("doh-rollout.skipHeuristicsCheck").value; + return rolloutEnabled && !heuristicsDisabled; + }, + + readDnsOverHttpsMode() { + // called to update checked element property to reflect current pref value + let enabled = this.isDnsOverHttpsEnabled(); + let uriPref = Preferences.get("network.trr.uri"); + uriPref.updateControlDisabledState(!enabled || this.isDnsOverHttpsLocked()); + // this is the first signal we get when the prefs are available, so + // lazy-init if appropriate + if (!this._areTrrPrefsReady) { + this._areTrrPrefsReady = true; + this._handleTrrPrefsReady(); + } else { + this.updateDnsOverHttpsUI(); + } + return enabled; + }, + + writeDnsOverHttpsMode() { + // called to update pref with user change + let trrModeCheckbox = document.getElementById("networkDnsOverHttps"); + + let trrModeCurrent = Preferences.get("network.trr.mode").value; + if (trrModeCheckbox.checked) { + //Check if the user has set the value themself through about:config. + if (trrModeCurrent == Ci.nsIDNSService.MODE_TRRONLY) { + return Ci.nsIDNSService.MODE_TRRONLY; + } + // we treat checked/enabled as mode 2 + return Ci.nsIDNSService.MODE_TRRFIRST; + } + + return Ci.nsIDNSService.MODE_TRROFF; + }, + + updateDnsOverHttpsUI() { + // init and update of the UI must wait until the pref values are ready + if (!this._areTrrPrefsReady) { + return; + } + let [menu, customInput] = this.getDnsOverHttpsControls(); + let customDohContainer = document.getElementById( + "customDnsOverHttpsContainer" + ); + let customURI = Preferences.get("network.trr.custom_uri").value; + let currentURI = Preferences.get("network.trr.uri").value; + let resolvers = this.dnsOverHttpsResolvers; + let isCustom = menu.value == "custom"; + + if (this.isDnsOverHttpsEnabled()) { + this.toggleDnsOverHttpsUI(false); + if (isCustom) { + // if the current and custom_uri values mismatch, update the uri pref + if ( + currentURI && + !customURI && + !resolvers.find(r => r.uri == currentURI) + ) { + Services.prefs.setStringPref("network.trr.custom_uri", currentURI); + } + } + } else { + this.toggleDnsOverHttpsUI(true); + } + + if (!menu.disabled && isCustom) { + customDohContainer.hidden = false; + customInput.disabled = false; + customInput.scrollIntoView(); + } else { + customDohContainer.hidden = true; + customInput.disabled = true; + } + + // The height has likely changed, find our SubDialog and tell it to resize. + requestAnimationFrame(() => { + let dialogs = window.opener.gSubDialog._dialogs; + let dialog = dialogs.find(d => d._frame.contentDocument == document); + if (dialog) { + dialog.resizeVertically(); + } + }); + }, + + getDnsOverHttpsControls() { + return [ + document.getElementById("networkDnsOverHttpsResolverChoices"), + document.getElementById("networkCustomDnsOverHttpsInput"), + document.getElementById("networkDnsOverHttpsResolverChoicesLabel"), + document.getElementById("networkCustomDnsOverHttpsInputLabel"), + ]; + }, + + toggleDnsOverHttpsUI(disabled) { + for (let element of this.getDnsOverHttpsControls()) { + element.disabled = disabled; + } + }, + + initDnsOverHttpsUI() { + let resolvers = this.dnsOverHttpsResolvers; + let defaultURI = DoHConfigController.currentConfig.fallbackProviderURI; + let currentURI = Preferences.get("network.trr.uri").value; + let menu = document.getElementById("networkDnsOverHttpsResolverChoices"); + + // populate the DNS-Over-HTTPs resolver list + menu.removeAllItems(); + for (let resolver of resolvers) { + let item = menu.appendItem(undefined, resolver.uri); + if (resolver.uri == defaultURI) { + document.l10n.setAttributes( + item, + "connection-dns-over-https-url-item-default", + { + name: resolver.UIName || resolver.uri, + } + ); + } else { + item.label = resolver.UIName || resolver.uri; + } + } + let lastItem = menu.appendItem(undefined, "custom"); + document.l10n.setAttributes( + lastItem, + "connection-dns-over-https-url-custom" + ); + + // set initial selection in the resolver provider picker + let selectedIndex = currentURI + ? resolvers.findIndex(r => r.uri == currentURI) + : 0; + if (selectedIndex == -1) { + // select the last "Custom" item + selectedIndex = menu.itemCount - 1; + } + menu.selectedIndex = selectedIndex; + + if (this.isDnsOverHttpsLocked()) { + // disable all the options and the checkbox itself to disallow enabling them + this.toggleDnsOverHttpsUI(true); + document.getElementById("networkDnsOverHttps").disabled = true; + } else { + this.toggleDnsOverHttpsUI(false); + this.updateDnsOverHttpsUI(); + document.getElementById("networkDnsOverHttps").disabled = false; + } + }, + + registerSyncPrefListeners() { + function setSyncFromPrefListener(element_id, callback) { + Preferences.addSyncFromPrefListener( + document.getElementById(element_id), + callback + ); + } + function setSyncToPrefListener(element_id, callback) { + Preferences.addSyncToPrefListener( + document.getElementById(element_id), + callback + ); + } + setSyncFromPrefListener("networkProxyType", () => this.readProxyType()); + setSyncFromPrefListener("networkProxyHTTP", () => + this.readHTTPProxyServer() + ); + setSyncFromPrefListener("networkProxyHTTP_Port", () => + this.readHTTPProxyPort() + ); + setSyncFromPrefListener("shareAllProxies", () => + this.updateProtocolPrefs() + ); + setSyncFromPrefListener("networkProxySSL", () => + this.readProxyProtocolPref("ssl", false) + ); + setSyncFromPrefListener("networkProxySSL_Port", () => + this.readProxyProtocolPref("ssl", true) + ); + setSyncFromPrefListener("networkProxySOCKS", () => + this.readProxyProtocolPref("socks", false) + ); + setSyncFromPrefListener("networkProxySOCKS_Port", () => + this.readProxyProtocolPref("socks", true) + ); + setSyncFromPrefListener("networkDnsOverHttps", () => + this.readDnsOverHttpsMode() + ); + setSyncToPrefListener("networkDnsOverHttps", () => + this.writeDnsOverHttpsMode() + ); + }, +}; -- cgit v1.2.3