connectionSettingsDialog.js (11446B)
1 "use strict"; 2 3 const { TorSettings, TorProxyType } = ChromeUtils.importESModule( 4 "resource://gre/modules/TorSettings.sys.mjs" 5 ); 6 7 const gConnectionSettingsDialog = { 8 _acceptButton: null, 9 _useProxyCheckbox: null, 10 _proxyTypeLabel: null, 11 _proxyTypeMenulist: null, 12 _proxyAddressLabel: null, 13 _proxyAddressTextbox: null, 14 _proxyPortLabel: null, 15 _proxyPortTextbox: null, 16 _proxyUsernameLabel: null, 17 _proxyUsernameTextbox: null, 18 _proxyPasswordLabel: null, 19 _proxyPasswordTextbox: null, 20 _useFirewallCheckbox: null, 21 _allowedPortsLabel: null, 22 _allowedPortsTextbox: null, 23 24 selectors: { 25 useProxyCheckbox: "checkbox#torPreferences-connection-toggleProxy", 26 proxyTypeLabel: "label#torPreferences-localProxy-type", 27 proxyTypeList: "menulist#torPreferences-localProxy-builtinList", 28 proxyAddressLabel: "label#torPreferences-localProxy-address", 29 proxyAddressTextbox: "input#torPreferences-localProxy-textboxAddress", 30 proxyPortLabel: "label#torPreferences-localProxy-port", 31 proxyPortTextbox: "input#torPreferences-localProxy-textboxPort", 32 proxyUsernameLabel: "label#torPreferences-localProxy-username", 33 proxyUsernameTextbox: "input#torPreferences-localProxy-textboxUsername", 34 proxyPasswordLabel: "label#torPreferences-localProxy-password", 35 proxyPasswordTextbox: "input#torPreferences-localProxy-textboxPassword", 36 useFirewallCheckbox: "checkbox#torPreferences-connection-toggleFirewall", 37 firewallAllowedPortsLabel: "label#torPreferences-connection-allowedPorts", 38 firewallAllowedPortsTextbox: 39 "input#torPreferences-connection-textboxAllowedPorts", 40 }, 41 42 /** 43 * The "proxy" and "firewall" settings to pass on to TorSettings. 44 * 45 * Each group is `null` whilst the inputs are invalid. 46 * 47 * @type {{proxy: ?object, firewall: ?object}} 48 */ 49 _settings: { proxy: null, firewall: null }, 50 51 init() { 52 const currentSettings = TorSettings.getSettings(); 53 54 const dialog = document.getElementById("torPreferences-connection-dialog"); 55 dialog.addEventListener("dialogaccept", event => { 56 if (!this._settings.proxy || !this._settings.firewall) { 57 // Do not close yet. 58 event.preventDefault(); 59 return; 60 } 61 // TODO: Maybe wait for the method to resolve before closing. Although 62 // this can take a few seconds. See tor-browser#43467. 63 TorSettings.changeSettings(this._settings); 64 }); 65 this._acceptButton = dialog.getButton("accept"); 66 67 const selectors = this.selectors; 68 69 // Local Proxy 70 this._useProxyCheckbox = document.querySelector(selectors.useProxyCheckbox); 71 this._useProxyCheckbox.checked = currentSettings.proxy.enabled; 72 this._useProxyCheckbox.addEventListener("command", () => { 73 this.updateProxyType(); 74 }); 75 76 this._proxyTypeLabel = document.querySelector(selectors.proxyTypeLabel); 77 78 const mockProxies = [ 79 { 80 value: TorProxyType.Socks4, 81 l10nId: "tor-advanced-dialog-proxy-socks4-menuitem", 82 }, 83 { 84 value: TorProxyType.Socks5, 85 l10nId: "tor-advanced-dialog-proxy-socks5-menuitem", 86 }, 87 { 88 value: TorProxyType.HTTPS, 89 l10nId: "tor-advanced-dialog-proxy-http-menuitem", 90 }, 91 ]; 92 this._proxyTypeMenulist = document.querySelector(selectors.proxyTypeList); 93 this._proxyTypeMenulist.addEventListener("command", () => { 94 this.updateProxyType(); 95 }); 96 for (const currentProxy of mockProxies) { 97 let menuEntry = window.document.createXULElement("menuitem"); 98 menuEntry.setAttribute("value", currentProxy.value); 99 menuEntry.setAttribute("data-l10n-id", currentProxy.l10nId); 100 this._proxyTypeMenulist.querySelector("menupopup").appendChild(menuEntry); 101 } 102 this._proxyTypeMenulist.value = currentSettings.proxy.enabled 103 ? currentSettings.proxy.type 104 : ""; 105 106 this._proxyAddressLabel = document.querySelector( 107 selectors.proxyAddressLabel 108 ); 109 this._proxyAddressTextbox = document.querySelector( 110 selectors.proxyAddressTextbox 111 ); 112 this._proxyAddressTextbox.addEventListener("blur", () => { 113 // If the address includes a port move it to the port input instead. 114 let value = this._proxyAddressTextbox.value.trim(); 115 let colon = value.lastIndexOf(":"); 116 if (colon != -1) { 117 let maybePort = this.parsePort(value.substr(colon + 1)); 118 if (maybePort !== null) { 119 this._proxyAddressTextbox.value = value.substr(0, colon); 120 this._proxyPortTextbox.value = maybePort; 121 this.updateProxy(); 122 } 123 } 124 }); 125 this._proxyPortLabel = document.querySelector(selectors.proxyPortLabel); 126 this._proxyPortTextbox = document.querySelector(selectors.proxyPortTextbox); 127 this._proxyUsernameLabel = document.querySelector( 128 selectors.proxyUsernameLabel 129 ); 130 this._proxyUsernameTextbox = document.querySelector( 131 selectors.proxyUsernameTextbox 132 ); 133 this._proxyPasswordLabel = document.querySelector( 134 selectors.proxyPasswordLabel 135 ); 136 this._proxyPasswordTextbox = document.querySelector( 137 selectors.proxyPasswordTextbox 138 ); 139 140 if (currentSettings.proxy.enabled) { 141 this._proxyAddressTextbox.value = currentSettings.proxy.address; 142 this._proxyPortTextbox.value = currentSettings.proxy.port; 143 this._proxyUsernameTextbox.value = currentSettings.proxy.username; 144 this._proxyPasswordTextbox.value = currentSettings.proxy.password; 145 } else { 146 this._proxyAddressTextbox.value = ""; 147 this._proxyPortTextbox.value = ""; 148 this._proxyUsernameTextbox.value = ""; 149 this._proxyPasswordTextbox.value = ""; 150 } 151 152 for (const el of [ 153 this._proxyAddressTextbox, 154 this._proxyPortTextbox, 155 this._proxyUsernameTextbox, 156 this._proxyPasswordTextbox, 157 ]) { 158 el.addEventListener("input", () => { 159 this.updateProxy(); 160 }); 161 } 162 163 // Local firewall 164 this._useFirewallCheckbox = document.querySelector( 165 selectors.useFirewallCheckbox 166 ); 167 this._useFirewallCheckbox.checked = currentSettings.firewall.enabled; 168 this._useFirewallCheckbox.addEventListener("command", () => { 169 this.updateFirewallEnabled(); 170 }); 171 this._allowedPortsLabel = document.querySelector( 172 selectors.firewallAllowedPortsLabel 173 ); 174 this._allowedPortsTextbox = document.querySelector( 175 selectors.firewallAllowedPortsTextbox 176 ); 177 this._allowedPortsTextbox.value = currentSettings.firewall.enabled 178 ? currentSettings.firewall.allowed_ports.join(",") 179 : "80,443"; 180 181 this._allowedPortsTextbox.addEventListener("input", () => { 182 this.updateFirewall(); 183 }); 184 185 this.updateProxyType(); 186 this.updateFirewallEnabled(); 187 }, 188 189 /** 190 * Convert a string into a port number. 191 * 192 * @param {string} portStr - The string to convert. 193 * @returns {?integer} - The port number, or `null` if the given string could 194 * not be converted. 195 */ 196 parsePort(portStr) { 197 const portRegex = /^[1-9][0-9]*$/; // Strictly-positive decimal integer. 198 if (!portRegex.test(portStr)) { 199 return null; 200 } 201 const port = parseInt(portStr, 10); 202 if (TorSettings.validPort(port)) { 203 return port; 204 } 205 return null; 206 }, 207 208 /** 209 * Update the disabled state of the accept button. 210 */ 211 updateAcceptButton() { 212 this._acceptButton.disabled = 213 !this._settings.proxy || !this._settings.firewall; 214 }, 215 216 /** 217 * Update the UI when the proxy setting is enabled or disabled, or the proxy 218 * type changes. 219 */ 220 updateProxyType() { 221 const enabled = this._useProxyCheckbox.checked; 222 const haveType = enabled && Boolean(this._proxyTypeMenulist.value); 223 const type = parseInt(this._proxyTypeMenulist.value, 10); 224 225 this._proxyTypeLabel.disabled = !enabled; 226 this._proxyTypeMenulist.disabled = !enabled; 227 this._proxyAddressLabel.disabled = !haveType; 228 this._proxyAddressTextbox.disabled = !haveType; 229 this._proxyPortLabel.disabled = !haveType; 230 this._proxyPortTextbox.disabled = !haveType; 231 this._proxyUsernameTextbox.disabled = 232 !haveType || type === TorProxyType.Socks4; 233 this._proxyPasswordTextbox.disabled = 234 !haveType || type === TorProxyType.Socks4; 235 if (type === TorProxyType.Socks4) { 236 // Clear unused value. 237 this._proxyUsernameTextbox.value = ""; 238 this._proxyPasswordTextbox.value = ""; 239 } 240 241 this.updateProxy(); 242 }, 243 244 /** 245 * Update the dialog's stored proxy values. 246 */ 247 updateProxy() { 248 if (this._useProxyCheckbox.checked) { 249 const typeStr = this._proxyTypeMenulist.value; 250 const type = parseInt(typeStr, 10); 251 // TODO: Validate the address. See tor-browser#43467. 252 const address = this._proxyAddressTextbox.value; 253 const portStr = this._proxyPortTextbox.value; 254 const username = 255 type === TorProxyType.Socks4 ? "" : this._proxyUsernameTextbox.value; 256 const password = 257 type === TorProxyType.Socks4 ? "" : this._proxyPasswordTextbox.value; 258 const port = parseInt(portStr, 10); 259 if ( 260 !typeStr || 261 !address || 262 !portStr || 263 !TorSettings.validPort(port) || 264 // SOCKS5 needs either both username and password, or neither. 265 (type === TorProxyType.Socks5 && 266 !TorSettings.validSocks5Credentials(username, password)) 267 ) { 268 // Invalid. 269 this._settings.proxy = null; 270 } else { 271 this._settings.proxy = { 272 enabled: true, 273 type, 274 address, 275 port, 276 username, 277 password, 278 }; 279 } 280 } else { 281 this._settings.proxy = { enabled: false }; 282 } 283 284 this.updateAcceptButton(); 285 }, 286 287 /** 288 * Update the UI when the firewall setting is enabled or disabled. 289 */ 290 updateFirewallEnabled() { 291 const enabled = this._useFirewallCheckbox.checked; 292 this._allowedPortsLabel.disabled = !enabled; 293 this._allowedPortsTextbox.disabled = !enabled; 294 295 this.updateFirewall(); 296 }, 297 298 /** 299 * Update the dialog's stored firewall values. 300 */ 301 updateFirewall() { 302 if (this._useFirewallCheckbox.checked) { 303 const portList = []; 304 let listInvalid = false; 305 for (const portStr of this._allowedPortsTextbox.value.split( 306 /(?:\s*,\s*)+/g 307 )) { 308 if (!portStr) { 309 // Trailing or leading comma. 310 continue; 311 } 312 const port = this.parsePort(portStr); 313 if (port === null) { 314 listInvalid = true; 315 break; 316 } 317 portList.push(port); 318 } 319 if (!listInvalid && portList.length) { 320 this._settings.firewall = { 321 enabled: true, 322 allowed_ports: portList, 323 }; 324 } else { 325 this._settings.firewall = null; 326 } 327 } else { 328 this._settings.firewall = { enabled: false }; 329 } 330 331 this.updateAcceptButton(); 332 }, 333 }; 334 335 // Initial focus is not visible, even if opened with a keyboard. We avoid the 336 // default handler and manage the focus ourselves, which will paint the focus 337 // ring by default. 338 // NOTE: A side effect is that the focus ring will show even if the user opened 339 // with a mouse event. 340 // TODO: Remove this once bugzilla bug 1708261 is resolved. 341 document.subDialogSetDefaultFocus = () => { 342 document.getElementById("torPreferences-connection-toggleProxy").focus(); 343 }; 344 345 window.addEventListener( 346 "DOMContentLoaded", 347 () => { 348 gConnectionSettingsDialog.init(); 349 }, 350 { once: true } 351 );