| // Copyright 2015 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 |
| * 'site-details-permission' handles showing the state of one permission, such |
| * as Geolocation, for a given origin. |
| */ |
| import 'chrome://resources/cr_elements/md_select_css.m.js'; |
| import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; |
| import '../settings_shared_css.js'; |
| import '../settings_vars_css.js'; |
| |
| import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js'; |
| import {I18nMixin, I18nMixinInterface} from 'chrome://resources/js/i18n_mixin.js'; |
| import {sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.m.js'; |
| import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js'; |
| import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; |
| |
| import {loadTimeData} from '../i18n_setup.js'; |
| import {routes} from '../route.js'; |
| |
| import {ContentSetting, ContentSettingsTypes, SiteSettingSource} from './constants.js'; |
| import {SiteSettingsMixin} from './site_settings_mixin.js'; |
| import {RawSiteException} from './site_settings_prefs_browser_proxy.js'; |
| |
| export interface SiteDetailsPermissionElement { |
| $: { |
| permission: HTMLSelectElement, |
| }; |
| } |
| |
| const SiteDetailsPermissionElementBase = |
| SiteSettingsMixin(WebUIListenerMixin(I18nMixin(PolymerElement))); |
| |
| export class SiteDetailsPermissionElement extends |
| SiteDetailsPermissionElementBase { |
| static get is() { |
| return 'site-details-permission'; |
| } |
| |
| static get template() { |
| return html`{__html_template__}`; |
| } |
| |
| static get properties() { |
| return { |
| /** |
| * If this is a sound content setting, then this controls whether it |
| * should use "Automatic" instead of "Allow" as the default setting |
| * allow label. |
| */ |
| useAutomaticLabel: {type: Boolean, value: false}, |
| |
| /** |
| * The site that this widget is showing details for, or null if this |
| * widget should be hidden. |
| */ |
| site: Object, |
| |
| /** |
| * The default setting for this permission category. |
| */ |
| defaultSetting_: String, |
| |
| label: String, |
| |
| icon: String, |
| |
| /** |
| * Expose ContentSetting enum to HTML bindings. |
| */ |
| contentSettingEnum_: { |
| type: Object, |
| value: ContentSetting, |
| }, |
| }; |
| } |
| |
| static get observers() { |
| return ['siteChanged_(site)']; |
| } |
| |
| useAutomaticLabel: boolean; |
| site: RawSiteException; |
| private defaultSetting_: ContentSetting; |
| label: string; |
| icon: string; |
| |
| connectedCallback() { |
| super.connectedCallback(); |
| |
| this.addWebUIListener( |
| 'contentSettingCategoryChanged', |
| (category: ContentSettingsTypes) => |
| this.onDefaultSettingChanged_(category)); |
| } |
| |
| /** |
| * Updates the drop-down value after |site| has changed. If |site| is null, |
| * this element will hide. |
| * @param site The site to display. |
| */ |
| private siteChanged_(site: RawSiteException|null) { |
| if (!site) { |
| return; |
| } |
| |
| if (site.source === SiteSettingSource.DEFAULT) { |
| this.defaultSetting_ = site.setting; |
| this.$.permission.value = ContentSetting.DEFAULT; |
| } else { |
| // The default setting is unknown, so consult the C++ backend for it. |
| this.updateDefaultPermission_(); |
| this.$.permission.value = site.setting; |
| } |
| |
| if (this.isNonDefaultAsk_(site.setting, site.source)) { |
| assert( |
| this.$.permission.value === ContentSetting.ASK, |
| '\'Ask\' should only show up when it\'s currently selected.'); |
| } |
| } |
| |
| /** |
| * Updates the default permission setting for this permission category. |
| */ |
| private updateDefaultPermission_() { |
| this.browserProxy.getDefaultValueForContentType(this.category) |
| .then((defaultValue) => { |
| this.defaultSetting_ = defaultValue.setting; |
| }); |
| } |
| |
| /** |
| * Handles the category permission changing for this origin. |
| * @param category The permission category that has changed default |
| * permission. |
| */ |
| private onDefaultSettingChanged_(category: ContentSettingsTypes) { |
| if (category === this.category) { |
| this.updateDefaultPermission_(); |
| } |
| } |
| |
| /** |
| * Handles the category permission changing for this origin. |
| */ |
| private onPermissionSelectionChange_() { |
| this.browserProxy.setOriginPermissions( |
| this.site.origin, this.category, |
| this.$.permission.value as ContentSetting); |
| } |
| |
| /** |
| * @param category The permission type. |
| * @return if we should use the custom labels for the sound type. |
| */ |
| private useCustomSoundLabels_(category: ContentSettingsTypes): boolean { |
| return category === ContentSettingsTypes.SOUND; |
| } |
| |
| /** |
| * Updates the string used for this permission category's default setting. |
| * @param defaultSetting Value of the default setting for this permission |
| * category. |
| * @param category The permission type. |
| * @param useAutomaticLabel Whether to use the automatic label if the default |
| * setting value is allow. |
| */ |
| private defaultSettingString_( |
| defaultSetting: ContentSetting, category: ContentSettingsTypes, |
| useAutomaticLabel: boolean): string { |
| if (defaultSetting === undefined || category === undefined || |
| useAutomaticLabel === undefined) { |
| return ''; |
| } |
| |
| if (defaultSetting === ContentSetting.ASK || |
| defaultSetting === ContentSetting.IMPORTANT_CONTENT) { |
| return this.i18n('siteSettingsActionAskDefault'); |
| } else if (defaultSetting === ContentSetting.ALLOW) { |
| if (this.useCustomSoundLabels_(category) && useAutomaticLabel) { |
| return this.i18n('siteSettingsActionAutomaticDefault'); |
| } |
| return this.i18n('siteSettingsActionAllowDefault'); |
| } else if (defaultSetting === ContentSetting.BLOCK) { |
| if (this.useCustomSoundLabels_(category)) { |
| return this.i18n('siteSettingsActionMuteDefault'); |
| } |
| return this.i18n('siteSettingsActionBlockDefault'); |
| } |
| assertNotReached( |
| `No string for ${this.category}'s default of ${defaultSetting}`); |
| return ''; |
| } |
| |
| /** |
| * Updates the string used for this permission category's block setting. |
| * @param category The permission type. |
| * @param blockString 'Block' label. |
| * @param muteString 'Mute' label. |
| */ |
| private blockSettingString_( |
| category: ContentSettingsTypes, blockString: string, |
| muteString: string): string { |
| if (this.useCustomSoundLabels_(category)) { |
| return muteString; |
| } |
| return blockString; |
| } |
| |
| /** |
| * @return true if |this| should be hidden. |
| */ |
| private shouldHideCategory_() { |
| return !this.site; |
| } |
| |
| /** |
| * Returns true if there's a string to display that provides more information |
| * about this permission's setting. Currently, this only gets called when |
| * |this.site| is updated. |
| * @param source The source of the permission. |
| * @param category The permission type. |
| * @param setting The permission setting. |
| * @param settingDetail A sublabel for the permission. |
| * @return Whether the permission will have a source string to display. |
| */ |
| private hasPermissionInfoString_( |
| source: SiteSettingSource, category: ContentSettingsTypes, |
| setting: ContentSetting, settingDetail: string|null): boolean { |
| // This method assumes that an empty string will be returned for categories |
| // that have no permission info string. |
| return this.permissionInfoString_( |
| source, category, setting, settingDetail, |
| // Set all permission info string arguments as null. This is OK |
| // because there is no need to know what the information string |
| // will be, just whether there is one or not. |
| null, null, null, null, null, null, null, null, null, null, null, |
| null) !== ''; |
| } |
| |
| /** |
| * Checks if there's a additional information to display, and returns the |
| * class name to apply to permissions if so. |
| * @param source The source of the permission. |
| * @param category The permission type. |
| * @param setting The permission setting. |
| * @param settingDetail A sublabel for the permission. |
| * @return CSS class applied when there is an additional description string. |
| */ |
| private permissionInfoStringClass_( |
| source: SiteSettingSource, category: ContentSettingsTypes, |
| setting: ContentSetting, settingDetail: string|null): string { |
| return this.hasPermissionInfoString_( |
| source, category, setting, settingDetail) ? |
| 'two-line' : |
| ''; |
| } |
| |
| /** |
| * @param source The source of the permission. |
| * @return Whether this permission can be controlled by the user. |
| */ |
| private isPermissionUserControlled_(source: SiteSettingSource): boolean { |
| return !( |
| source === SiteSettingSource.ALLOWLIST || |
| source === SiteSettingSource.POLICY || |
| source === SiteSettingSource.EXTENSION || |
| source === SiteSettingSource.KILL_SWITCH || |
| source === SiteSettingSource.INSECURE_ORIGIN); |
| } |
| |
| /** |
| * @param category The permission type. |
| * @return Whether if the 'allow' option should be shown. |
| */ |
| private showAllowedSetting_(category: ContentSettingsTypes) { |
| return !( |
| category === ContentSettingsTypes.SERIAL_PORTS || |
| category === ContentSettingsTypes.USB_DEVICES || |
| category === ContentSettingsTypes.BLUETOOTH_SCANNING || |
| category === ContentSettingsTypes.FILE_SYSTEM_WRITE || |
| category === ContentSettingsTypes.HID_DEVICES || |
| category === ContentSettingsTypes.BLUETOOTH_DEVICES); |
| } |
| |
| /** |
| * @param category The permission type. |
| * @param setting The setting of the permission. |
| * @param source The source of the permission. |
| * @return Whether the 'ask' option should be shown. |
| */ |
| private showAskSetting_( |
| category: ContentSettingsTypes, setting: ContentSetting, |
| source: SiteSettingSource): boolean { |
| // For chooser-based permissions 'ask' takes the place of 'allow'. |
| if (category === ContentSettingsTypes.SERIAL_PORTS || |
| category === ContentSettingsTypes.USB_DEVICES || |
| category === ContentSettingsTypes.HID_DEVICES || |
| category === ContentSettingsTypes.BLUETOOTH_DEVICES) { |
| return true; |
| } |
| |
| // For Bluetooth scanning permission and File System write permission |
| // 'ask' takes the place of 'allow'. |
| if (category === ContentSettingsTypes.BLUETOOTH_SCANNING || |
| category === ContentSettingsTypes.FILE_SYSTEM_WRITE) { |
| return true; |
| } |
| |
| return this.isNonDefaultAsk_(setting, source); |
| } |
| |
| /** |
| * Returns true if the permission is set to a non-default 'ask'. Currently, |
| * this only gets called when |this.site| is updated. |
| * @param setting The setting of the permission. |
| * @param source The source of the permission. |
| */ |
| private isNonDefaultAsk_(setting: ContentSetting, source: SiteSettingSource) { |
| if (setting !== ContentSetting.ASK || |
| source === SiteSettingSource.DEFAULT) { |
| return false; |
| } |
| |
| assert( |
| source === SiteSettingSource.EXTENSION || |
| source === SiteSettingSource.POLICY || |
| source === SiteSettingSource.PREFERENCE, |
| 'Only extensions, enterprise policy or preferences can change ' + |
| 'the setting to ASK.'); |
| return true; |
| } |
| |
| /** |
| * Updates the information string for the current permission. |
| * Currently, this only gets called when |this.site| is updated. |
| * @param source The source of the permission. |
| * @param category The permission type. |
| * @param setting The permission setting. |
| * @param settingDetail If non-empty, the string to display as the |
| * permission info. This overrides other calculations made by this |
| * function, and is used for situations where extra data about the |
| * permission is required to compose the substring. |
| * @param allowlistString The string to show if the permission is |
| * allowlisted. |
| * @param adsBlacklistString The string to show if the site is |
| * blacklisted for showing bad ads. |
| * @param adsBlockString The string to show if ads are blocked, but |
| * the site is not blacklisted. |
| * @return The permission information string to display in the HTML. |
| */ |
| private permissionInfoString_( |
| source: SiteSettingSource, category: ContentSettingsTypes, |
| setting: ContentSetting, settingDetail: string|null, |
| allowlistString: string|null, adsBlacklistString: string|null, |
| adsBlockString: string|null, embargoString: string|null, |
| insecureOriginString: string|null, killSwitchString: string|null, |
| extensionAllowString: string|null, extensionBlockString: string|null, |
| extensionAskString: string|null, policyAllowString: string|null, |
| policyBlockString: string|null, |
| policyAskString: string|null): (string|null) { |
| if (source === undefined || category === undefined || |
| setting === undefined) { |
| return null; |
| } |
| |
| if (settingDetail) { |
| // For now, settingDetail is only used for file extensions. |
| // TODO(estade): assert in the other direction as well: the FILE_HANDLING |
| // category should always have detail text. |
| assert(category === ContentSettingsTypes.FILE_HANDLING); |
| return settingDetail; |
| } |
| |
| // TODO(crbug.com/1234307) Change "key: string" to "key: ContentSetting" |
| // when constants.js is migrated to TS (here and below). |
| const extensionStrings: {[key: string]: string|null} = {}; |
| extensionStrings[ContentSetting.ALLOW] = extensionAllowString; |
| extensionStrings[ContentSetting.BLOCK] = extensionBlockString; |
| extensionStrings[ContentSetting.ASK] = extensionAskString; |
| |
| const policyStrings: {[key: string]: string|null} = {}; |
| policyStrings[ContentSetting.ALLOW] = policyAllowString; |
| policyStrings[ContentSetting.BLOCK] = policyBlockString; |
| policyStrings[ContentSetting.ASK] = policyAskString; |
| |
| if (source === SiteSettingSource.ALLOWLIST) { |
| return allowlistString; |
| } else if (source === SiteSettingSource.ADS_FILTER_BLACKLIST) { |
| assert( |
| ContentSettingsTypes.ADS === category, |
| 'The ads filter blacklist only applies to Ads.'); |
| return adsBlacklistString; |
| } else if ( |
| category === ContentSettingsTypes.ADS && |
| setting === ContentSetting.BLOCK) { |
| return adsBlockString; |
| } else if (source === SiteSettingSource.EMBARGO) { |
| assert( |
| ContentSetting.BLOCK === setting, |
| 'Embargo is only used to block permissions.'); |
| return embargoString; |
| } else if (source === SiteSettingSource.EXTENSION) { |
| return extensionStrings[setting]; |
| } else if (source === SiteSettingSource.INSECURE_ORIGIN) { |
| assert( |
| ContentSetting.BLOCK === setting, |
| 'Permissions can only be blocked due to insecure origins.'); |
| return insecureOriginString; |
| } else if (source === SiteSettingSource.KILL_SWITCH) { |
| assert( |
| ContentSetting.BLOCK === setting, |
| 'The permissions kill switch can only be used to block permissions.'); |
| return killSwitchString; |
| } else if (source === SiteSettingSource.POLICY) { |
| return policyStrings[setting]; |
| } else if ( |
| source === SiteSettingSource.DEFAULT || |
| source === SiteSettingSource.PREFERENCE) { |
| return ''; |
| } |
| assertNotReached(`No string for ${category} setting source '${source}'`); |
| return ''; |
| } |
| } |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'site-details-permission': SiteDetailsPermissionElement; |
| } |
| } |
| |
| customElements.define( |
| SiteDetailsPermissionElement.is, SiteDetailsPermissionElement); |