| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview 'settings-secure-dns' is a setting that allows the secure DNS |
| * mode and secure DNS resolvers to be configured. |
| * |
| * The underlying secure DNS prefs are not read directly since the setting is |
| * meant to represent the current state of the host resolver, which depends not |
| * only on the prefs but also a few other factors (e.g. whether we've detected a |
| * managed environment, whether we've detected parental controls, etc). Instead, |
| * the setting listens for secure-dns-setting-changed events, which are sent |
| * by PrivacyPageBrowserProxy and describe the new host resolver configuration. |
| */ |
| |
| import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js'; |
| import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js'; |
| import 'chrome://resources/cr_elements/md_select_css.m.js'; |
| import '../controls/settings_toggle_button.js'; |
| import '../prefs/prefs.js'; |
| import '../settings_shared_css.js'; |
| import './secure_dns_input.js'; |
| |
| import {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js'; |
| import {assertNotReached} from 'chrome://resources/js/assert.m.js'; |
| import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js'; |
| import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {SettingsToggleButtonElement} from '../controls/settings_toggle_button.js'; |
| import {loadTimeData} from '../i18n_setup.js'; |
| import {PrefsMixin} from '../prefs/prefs_mixin.js'; |
| |
| import {PrivacyPageBrowserProxy, PrivacyPageBrowserProxyImpl, ResolverOption, SecureDnsMode, SecureDnsSetting, SecureDnsUiManagementMode} from './privacy_page_browser_proxy.js'; |
| import {getTemplate} from './secure_dns.html.js'; |
| import {SecureDnsInputElement} from './secure_dns_input.js'; |
| |
| export interface SettingsSecureDnsElement { |
| $: { |
| privacyPolicy: HTMLElement, |
| secureDnsInput: SecureDnsInputElement, |
| secureDnsRadioGroup: CrRadioGroupElement, |
| secureDnsToggle: SettingsToggleButtonElement, |
| secureResolverSelect: HTMLSelectElement, |
| }; |
| } |
| |
| const SettingsSecureDnsElementBase = |
| WebUIListenerMixin(PrefsMixin(PolymerElement)); |
| |
| export class SettingsSecureDnsElement extends SettingsSecureDnsElementBase { |
| static get is() { |
| return 'settings-secure-dns'; |
| } |
| |
| static get template() { |
| return getTemplate(); |
| } |
| |
| static get properties() { |
| return { |
| /** |
| * Preferences state. |
| */ |
| prefs: { |
| type: Object, |
| notify: true, |
| }, |
| |
| /** |
| * Mirroring the secure DNS mode enum so that it can be used from HTML |
| * bindings. |
| */ |
| secureDnsModeEnum_: { |
| type: Object, |
| value: SecureDnsMode, |
| }, |
| |
| /** |
| * The setting sublabel. |
| */ |
| secureDnsDescription_: String, |
| |
| /** |
| * Represents whether the main toggle for the secure DNS setting is |
| * switched on or off. |
| */ |
| secureDnsToggle_: { |
| type: Object, |
| value() { |
| return { |
| type: chrome.settingsPrivate.PrefType.BOOLEAN, |
| value: false, |
| }; |
| }, |
| }, |
| |
| /** |
| * Whether the radio buttons should be shown. |
| */ |
| showRadioGroup_: Boolean, |
| |
| /** |
| * Represents the selected radio button. Should always have a value of |
| * 'automatic' or 'secure'. |
| */ |
| secureDnsRadio_: { |
| type: String, |
| value: SecureDnsMode.AUTOMATIC, |
| }, |
| |
| /** |
| * List of secure DNS resolvers to display in dropdown menu. |
| */ |
| resolverOptions_: Array, |
| |
| /** |
| * Track the selected dropdown option so that it can be logged when a |
| * user- initiated UI dropdown selection change event occurs. |
| */ |
| lastResolverOption_: String, |
| |
| /** |
| * String displaying the privacy policy of the resolver selected in the |
| * dropdown menu. |
| */ |
| privacyPolicyString_: String, |
| |
| /** |
| * String to display in the custom text field. |
| */ |
| secureDnsInputValue_: String, |
| }; |
| } |
| |
| private secureDnsDescription_: string; |
| private secureDnsToggle_: chrome.settingsPrivate.PrefObject; |
| private showRadioGroup_: boolean; |
| private secureDnsRadio_: SecureDnsMode; |
| private resolverOptions_: Array<ResolverOption>; |
| private lastResolverOption_: string; |
| private privacyPolicyString_: string; |
| private secureDnsInputValue_: string; |
| private browserProxy_: PrivacyPageBrowserProxy = |
| PrivacyPageBrowserProxyImpl.getInstance(); |
| |
| connectedCallback() { |
| super.connectedCallback(); |
| |
| // Fetch the options for the dropdown menu before configuring the setting |
| // to match the underlying host resolver configuration. |
| this.browserProxy_.getSecureDnsResolverList().then(resolvers => { |
| this.resolverOptions_ = resolvers; |
| this.lastResolverOption_ = this.resolverOptions_[0].value; |
| this.browserProxy_.getSecureDnsSetting().then( |
| (setting: SecureDnsSetting) => |
| this.onSecureDnsPrefsChanged_(setting)); |
| |
| // Listen to changes in the host resolver configuration and update the |
| // UI representation to match. (Changes to the host resolver configuration |
| // may be generated in ways other than direct UI manipulation). |
| this.addWebUIListener( |
| 'secure-dns-setting-changed', |
| (setting: SecureDnsSetting) => |
| this.onSecureDnsPrefsChanged_(setting)); |
| }); |
| } |
| |
| /** |
| * Update the UI representation to match the underlying host resolver |
| * configuration. |
| */ |
| private onSecureDnsPrefsChanged_(setting: SecureDnsSetting) { |
| switch (setting.mode) { |
| case SecureDnsMode.SECURE: |
| this.set('secureDnsToggle_.value', true); |
| this.secureDnsRadio_ = SecureDnsMode.SECURE; |
| // Only update the selected dropdown item if the user is in secure |
| // mode. Otherwise, we may be losing a selection that hasn't been |
| // pushed yet to prefs. |
| this.updateConfigRepresentation_(setting.config); |
| this.updatePrivacyPolicyLine_(); |
| break; |
| case SecureDnsMode.AUTOMATIC: |
| this.set('secureDnsToggle_.value', true); |
| this.secureDnsRadio_ = SecureDnsMode.AUTOMATIC; |
| break; |
| case SecureDnsMode.OFF: |
| this.set('secureDnsToggle_.value', false); |
| break; |
| default: |
| assertNotReached('Received unknown secure DNS mode'); |
| } |
| |
| this.updateManagementView_(setting.managementMode); |
| } |
| |
| /** |
| * Updates the underlying secure DNS mode pref based on the new toggle |
| * selection (and the underlying radio button if the toggle has just been |
| * enabled). |
| */ |
| onToggleChanged_() { |
| this.showRadioGroup_ = this.secureDnsToggle_.value; |
| if (this.secureDnsRadio_ === SecureDnsMode.SECURE && |
| !this.$.secureResolverSelect.value) { |
| this.$.secureDnsInput.focus(); |
| } |
| this.updateDnsPrefs_( |
| this.secureDnsToggle_.value ? this.secureDnsRadio_ : SecureDnsMode.OFF); |
| } |
| |
| /** |
| * Updates the underlying secure DNS prefs based on the newly selected radio |
| * button. This should only be called from the HTML. Focuses the custom text |
| * field if the custom option has been selected. |
| */ |
| private onRadioSelectionChanged_(event: CustomEvent<{value: SecureDnsMode}>) { |
| if (event.detail.value === SecureDnsMode.SECURE && |
| !this.$.secureResolverSelect.value) { |
| this.$.secureDnsInput.focus(); |
| } |
| this.updateDnsPrefs_(event.detail.value); |
| } |
| |
| /** |
| * Helper method for updating the underlying secure DNS prefs based on the |
| * provided mode and templates (if the latter is specified). The templates |
| * param should only be specified when the underlying prefs are being updated |
| * after a custom entry has been validated. |
| */ |
| private updateDnsPrefs_(mode: SecureDnsMode, templates: string = '') { |
| switch (mode) { |
| case SecureDnsMode.SECURE: |
| // If going to secure mode, set the templates pref first to prevent the |
| // stub resolver config from being momentarily invalid. If the user has |
| // selected the custom dropdown option, only update the underlying |
| // prefs if the templates param was specified. If the templates param |
| // was not specified, the custom entry may be invalid or may not |
| // have passed validation yet, and we should not update either the |
| // underlying mode or templates prefs. |
| if (!this.$.secureResolverSelect.value) { |
| if (!templates) { |
| return; |
| } |
| this.setPrefValue('dns_over_https.templates', templates); |
| } else { |
| this.setPrefValue( |
| 'dns_over_https.templates', this.$.secureResolverSelect.value); |
| } |
| this.setPrefValue('dns_over_https.mode', mode); |
| break; |
| case SecureDnsMode.AUTOMATIC: |
| case SecureDnsMode.OFF: |
| // If going to automatic or off mode, set the mode pref first to avoid |
| // clearing the dropdown selection when the templates pref is cleared. |
| this.setPrefValue('dns_over_https.mode', mode); |
| this.setPrefValue('dns_over_https.templates', ''); |
| break; |
| default: |
| assertNotReached('Received unknown secure DNS mode'); |
| } |
| } |
| |
| /** |
| * Prevent interactions with the dropdown menu or custom text field from |
| * causing the corresponding radio button to be selected. |
| */ |
| private stopEventPropagation_(event: Event) { |
| event.stopPropagation(); |
| } |
| |
| /** |
| * Updates the underlying secure DNS templates pref based on the selected |
| * resolver and displays the corresponding privacy policy. Focuses the custom |
| * text field if the custom option has been selected. |
| */ |
| onDropdownSelectionChanged_() { |
| // If we're already in secure mode, update the prefs. |
| if (this.secureDnsRadio_ === SecureDnsMode.SECURE) { |
| this.updateDnsPrefs_(SecureDnsMode.SECURE); |
| } |
| this.updatePrivacyPolicyLine_(); |
| |
| if (!this.$.secureResolverSelect.value) { |
| this.$.secureDnsInput.focus(); |
| } |
| |
| this.browserProxy_.recordUserDropdownInteraction( |
| this.lastResolverOption_, this.$.secureResolverSelect.value); |
| this.lastResolverOption_ = this.$.secureResolverSelect.value; |
| } |
| |
| /** |
| * Updates the setting to communicate the type of management, if any. The |
| * setting is always collapsed if there is any management. |
| */ |
| private updateManagementView_(managementMode: SecureDnsUiManagementMode) { |
| if (this.prefs === undefined) { |
| return; |
| } |
| // If the underlying secure DNS mode pref has an enforced value, communicate |
| // that via the toggle pref. |
| const pref: chrome.settingsPrivate.PrefObject = { |
| key: '', |
| type: chrome.settingsPrivate.PrefType.BOOLEAN, |
| value: this.secureDnsToggle_.value, |
| }; |
| if (this.getPref('dns_over_https.mode').enforcement === |
| chrome.settingsPrivate.Enforcement.ENFORCED) { |
| pref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED; |
| pref.controlledBy = this.getPref('dns_over_https.mode').controlledBy; |
| this.secureDnsDescription_ = |
| loadTimeData.getString('secureDnsDescription'); |
| } else { |
| // If the secure DNS mode was forcefully overridden by Chrome, provide an |
| // explanation in the setting subtitle. |
| switch (managementMode) { |
| case SecureDnsUiManagementMode.NO_OVERRIDE: |
| this.secureDnsDescription_ = |
| loadTimeData.getString('secureDnsDescription'); |
| break; |
| case SecureDnsUiManagementMode.DISABLED_MANAGED: |
| pref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED; |
| this.secureDnsDescription_ = |
| loadTimeData.getString('secureDnsDisabledForManagedEnvironment'); |
| break; |
| case SecureDnsUiManagementMode.DISABLED_PARENTAL_CONTROLS: |
| pref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED; |
| this.secureDnsDescription_ = |
| loadTimeData.getString('secureDnsDisabledForParentalControl'); |
| break; |
| default: |
| assertNotReached( |
| 'Received unknown secure DNS management mode ' + managementMode); |
| } |
| } |
| this.secureDnsToggle_ = pref; |
| |
| if (this.secureDnsToggle_.enforcement === |
| chrome.settingsPrivate.Enforcement.ENFORCED) { |
| this.showRadioGroup_ = false; |
| } else { |
| this.showRadioGroup_ = this.secureDnsToggle_.value; |
| } |
| } |
| |
| /** |
| * Updates the UI to represent the given secure DNS config. |
| * @param secureDnsConfig The current host resolver configuration. |
| */ |
| private updateConfigRepresentation_(secureDnsConfig: string) { |
| // If it is one of the non-custom dropdown options, select that option. |
| const resolver = |
| this.resolverOptions_.slice(1).find(r => r.value === secureDnsConfig); |
| if (resolver) { |
| this.$.secureResolverSelect.value = resolver.value; |
| this.lastResolverOption_ = resolver.value; |
| return; |
| } |
| |
| // Otherwise, select the custom option. |
| this.$.secureResolverSelect.value = ''; |
| this.lastResolverOption_ = ''; |
| |
| // Only update the custom input field if the config string is non-empty. |
| // Otherwise, we may be clearing a previous value that the user wishes to |
| // reuse. |
| if (secureDnsConfig.length > 0) { |
| this.secureDnsInputValue_ = secureDnsConfig; |
| } |
| } |
| |
| /** |
| * Displays the privacy policy corresponding to the selected dropdown resolver |
| * or hides the privacy policy line if a custom resolver is selected. |
| */ |
| private updatePrivacyPolicyLine_() { |
| // If the selected item is the custom provider option, hide the privacy |
| // policy line. |
| if (!this.$.secureResolverSelect.value) { |
| this.$.privacyPolicy.style.display = 'none'; |
| this.$.secureDnsInput.style.display = 'block'; |
| return; |
| } |
| |
| // Otherwise, display the corresponding privacy policy. |
| this.$.privacyPolicy.style.display = 'block'; |
| this.$.secureDnsInput.style.display = 'none'; |
| const resolver = this.resolverOptions_.find( |
| r => r.value === this.$.secureResolverSelect.value); |
| if (!resolver) { |
| return; |
| } |
| |
| this.privacyPolicyString_ = loadTimeData.substituteString( |
| loadTimeData.getString('secureDnsSecureDropdownModePrivacyPolicy'), |
| resolver.policy); |
| } |
| |
| /** |
| * Updates the underlying prefs if a custom entry was determined to be valid. |
| * If the custom entry was determined to be invalid, moves the selected radio |
| * button away from 'secure' if necessary. |
| */ |
| private onSecureDnsInputEvaluated_( |
| event: CustomEvent<{text: string, isValid: boolean}>) { |
| if (event.detail.isValid) { |
| this.updateDnsPrefs_(this.secureDnsRadio_, event.detail.text); |
| } |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'settings-secure-dns': SettingsSecureDnsElement; |
| } |
| } |
| |
| customElements.define(SettingsSecureDnsElement.is, SettingsSecureDnsElement); |