// 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. import './icons.js'; import 'chrome://resources/cr_elements/cr_button/cr_button.m.js'; import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js'; import 'chrome://resources/cr_elements/cr_icons_css.m.js'; import 'chrome://resources/cr_elements/hidden_style_css.m.js'; import 'chrome://resources/cr_elements/icons.m.js'; import 'chrome://resources/cr_elements/shared_vars_css.m.js'; import 'chrome://resources/js/action_link.js'; import 'chrome://resources/cr_elements/action_link_css.m.js'; import './strings.m.js'; import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; import 'chrome://resources/polymer/v3_0/paper-progress/paper-progress.js'; import 'chrome://resources/polymer/v3_0/paper-styles/color.js'; import {getToastManager} from 'chrome://resources/cr_elements/cr_toast/cr_toast_manager.m.js'; import {assert} from 'chrome://resources/js/assert.m.js'; import {FocusRowBehavior} from 'chrome://resources/js/cr/ui/focus_row_behavior.m.js'; import {focusWithoutInk} from 'chrome://resources/js/cr/ui/focus_without_ink.m.js'; import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; import {HTMLEscape} from 'chrome://resources/js/util.m.js'; import {beforeNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {BrowserProxy} from './browser_proxy.js'; import {DangerType, States} from './constants.js'; import {Data} from './data.js'; import {PageHandlerInterface} from './downloads.mojom-webui.js'; import {IconLoader} from './icon_loader.js'; Polymer({ is: 'downloads-item', _template: html`{__html_template__}`, behaviors: [ FocusRowBehavior, ], /** Used by FocusRowBehavior. */ overrideCustomEquivalent: true, properties: { /** @type {!Data} */ data: Object, /** @private */ completelyOnDisk_: { computed: 'computeCompletelyOnDisk_(' + 'data.state, data.fileExternallyRemoved)', type: Boolean, value: true, }, /** @private */ controlledBy_: { computed: 'computeControlledBy_(data.byExtId, data.byExtName)', type: String, value: '', }, /** @private */ controlRemoveFromListAriaLabel_: { type: String, computed: 'computeControlRemoveFromListAriaLabel_(data.fileName)', }, /** @private */ isActive_: { computed: 'computeIsActive_(' + 'data.state, data.fileExternallyRemoved)', type: Boolean, value: true, }, /** @private */ isDangerous_: { computed: 'computeIsDangerous_(data.state)', type: Boolean, value: false, }, /** @private */ isMalware_: { computed: 'computeIsMalware_(isDangerous_, data.dangerType)', type: Boolean, value: false, }, /** @private */ isInProgress_: { computed: 'computeIsInProgress_(data.state)', type: Boolean, value: false, }, /** @private */ pauseOrResumeText_: { computed: 'computePauseOrResumeText_(isInProgress_, data.resume)', type: String, observer: 'updatePauseOrResumeClass_', }, /** @private */ showCancel_: { computed: 'computeShowCancel_(data.state)', type: Boolean, value: false, }, /** @private */ showProgress_: { computed: 'computeShowProgress_(showCancel_, data.percent)', type: Boolean, value: false, }, /** @private */ showOpenNow_: { computed: 'computeShowOpenNow_(data.state)', type: Boolean, value: false, }, useFileIcon_: Boolean, }, hostAttributes: { role: 'row', }, observers: [ // TODO(dbeam): this gets called way more when I observe data.byExtId // and data.byExtName directly. Why? 'observeControlledBy_(controlledBy_)', 'observeIsDangerous_(isDangerous_, data)', 'restoreFocusAfterCancelIfNeeded_(data)', ], /** @private {PageHandlerInterface} */ mojoHandler_: null, /** @private {boolean} */ restoreFocusAfterCancel_: false, /** @override */ ready() { this.mojoHandler_ = BrowserProxy.getInstance().handler; this.content = this.$.content; }, focusOnRemoveButton() { focusWithoutInk(this.$.remove); }, /** Overrides FocusRowBehavior. */ getCustomEquivalent(sampleElement) { if (sampleElement.getAttribute('focus-type') === 'cancel') { return this.$$('[focus-type="retry"]'); } if (sampleElement.getAttribute('focus-type') === 'retry') { return this.$$('[focus-type="pauseOrResume"]'); } return null; }, /** @return {!HTMLElement} */ getFileIcon() { return /** @type {!HTMLElement} */ (this.$['file-icon']); }, /** * @param {string} url * @return {string} A reasonably long URL. * @private */ chopUrl_(url) { return url.slice(0, 300); }, /** @private */ computeClass_() { const classes = []; if (this.isActive_) { classes.push('is-active'); } if (this.isDangerous_) { classes.push('dangerous'); } if (this.showProgress_) { classes.push('show-progress'); } return classes.join(' '); }, /** * @return {boolean} * @private */ computeCompletelyOnDisk_() { return this.data.state === States.COMPLETE && !this.data.fileExternallyRemoved; }, /** * @return {string} * @private */ computeControlledBy_() { if (!this.data.byExtId || !this.data.byExtName) { return ''; } const url = `chrome://extensions/?id=${this.data.byExtId}`; const name = this.data.byExtName; return loadTimeData.getStringF('controlledByUrl', url, HTMLEscape(name)); }, /** * @return {string} * @private */ computeControlRemoveFromListAriaLabel_() { return loadTimeData.getStringF( 'controlRemoveFromListAriaLabel', this.data.fileName); }, /** * @return {string} * @private */ computeDate_() { assert(typeof this.data.hideDate === 'boolean'); if (this.data.hideDate) { return ''; } return assert(this.data.sinceString || this.data.dateString); }, /** @private @return {boolean} */ computeDescriptionVisible_() { return this.computeDescription_() !== ''; }, /** * @return {string} * @private */ computeDescription_() { const data = this.data; switch (data.state) { case States.COMPLETE: switch (data.dangerType) { case DangerType.DEEP_SCANNED_SAFE: return loadTimeData.getString('deepScannedSafeDesc'); case DangerType.DEEP_SCANNED_OPENED_DANGEROUS: return loadTimeData.getString('deepScannedOpenedDangerousDesc'); } break; case States.MIXED_CONTENT: return loadTimeData.getString('mixedContentDownloadDesc'); case States.DANGEROUS: const fileName = data.fileName; switch (data.dangerType) { case DangerType.DANGEROUS_FILE: return loadTimeData.getString('dangerFileDesc'); case DangerType.DANGEROUS_URL: case DangerType.DANGEROUS_CONTENT: case DangerType.DANGEROUS_HOST: return loadTimeData.getString('dangerDownloadDesc'); case DangerType.UNCOMMON_CONTENT: return loadTimeData.getString('dangerUncommonDesc'); case DangerType.POTENTIALLY_UNWANTED: return loadTimeData.getString('dangerSettingsDesc'); case DangerType.SENSITIVE_CONTENT_WARNING: return loadTimeData.getString('sensitiveContentWarningDesc'); } break; case States.ASYNC_SCANNING: return loadTimeData.getString('asyncScanningDownloadDesc'); case States.IN_PROGRESS: case States.PAUSED: // Fallthrough. return data.progressStatusText; case States.INTERRUPTED: switch (data.dangerType) { case DangerType.SENSITIVE_CONTENT_BLOCK: return loadTimeData.getString('sensitiveContentBlockedDesc'); case DangerType.BLOCKED_TOO_LARGE: return loadTimeData.getString('blockedTooLargeDesc'); case DangerType.BLOCKED_PASSWORD_PROTECTED: return loadTimeData.getString('blockedPasswordProtectedDesc'); } } return ''; }, /** * @return {string} * @private */ computeIcon_() { if (this.data) { const dangerType = this.data.dangerType; if ((loadTimeData.getBoolean('requestsApVerdicts') && dangerType === DangerType.UNCOMMON_CONTENT) || dangerType === DangerType.SENSITIVE_CONTENT_WARNING) { return 'cr:warning'; } const ERROR_TYPES = [ DangerType.SENSITIVE_CONTENT_BLOCK, DangerType.BLOCKED_TOO_LARGE, DangerType.BLOCKED_PASSWORD_PROTECTED, ]; if (ERROR_TYPES.includes(dangerType)) { return 'cr:error'; } if (this.data.state === States.ASYNC_SCANNING) { return 'cr:info'; } } if (this.isDangerous_) { return 'cr:error'; } if (!this.useFileIcon_) { return 'cr:insert-drive-file'; } return ''; }, /** * @return {string} * @private */ computeIconColor_() { if (this.data) { const dangerType = this.data.dangerType; if ((loadTimeData.getBoolean('requestsApVerdicts') && dangerType === DangerType.UNCOMMON_CONTENT) || dangerType === DangerType.SENSITIVE_CONTENT_WARNING) { return 'yellow'; } const WARNING_TYPES = [ DangerType.SENSITIVE_CONTENT_BLOCK, DangerType.BLOCKED_TOO_LARGE, DangerType.BLOCKED_PASSWORD_PROTECTED, ]; if (WARNING_TYPES.includes(dangerType)) { return 'red'; } if (this.data.state === States.ASYNC_SCANNING) { return 'grey'; } } if (this.isDangerous_) { return 'red'; } if (!this.useFileIcon_) { return 'paper-grey'; } return ''; }, /** * @return {boolean} * @private */ computeIsActive_() { return this.data.state !== States.CANCELLED && this.data.state !== States.INTERRUPTED && !this.data.fileExternallyRemoved; }, /** * @return {boolean} * @private */ computeIsDangerous_() { return this.data.state === States.DANGEROUS || this.data.state === States.MIXED_CONTENT; }, /** * @return {boolean} * @private */ computeIsInProgress_() { return this.data.state === States.IN_PROGRESS; }, /** * @return {boolean} * @private */ computeIsMalware_() { return this.isDangerous_ && (this.data.dangerType === DangerType.DANGEROUS_CONTENT || this.data.dangerType === DangerType.DANGEROUS_HOST || this.data.dangerType === DangerType.DANGEROUS_URL || this.data.dangerType === DangerType.POTENTIALLY_UNWANTED); }, /** @private */ toggleButtonClass_() { this.$$('#pauseOrResume') .classList.toggle( 'action-button', this.pauseOrResumeText_ === loadTimeData.getString('controlResume')); }, /** @private */ updatePauseOrResumeClass_() { if (!this.pauseOrResumeText_) { return; } // Wait for dom-if to switch to true, in case the text has just changed // from empty. beforeNextRender(this, () => this.toggleButtonClass_()); }, /** * @return {string} * @private */ computePauseOrResumeText_() { if (this.data === undefined) { return ''; } if (this.isInProgress_) { return loadTimeData.getString('controlPause'); } if (this.data.resume) { return loadTimeData.getString('controlResume'); } return ''; }, /** * @return {string} * @private */ computeRemoveStyle_() { const canDelete = loadTimeData.getBoolean('allowDeletingHistory'); const hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete; return hideRemove ? 'visibility: hidden' : ''; }, /** * @return {boolean} * @private */ computeShowCancel_() { return this.data.state === States.IN_PROGRESS || this.data.state === States.PAUSED || this.data.state === States.ASYNC_SCANNING; }, /** * @return {boolean} * @private */ computeShowProgress_() { return this.showCancel_ && this.data.percent >= -1 && this.data.state !== States.ASYNC_SCANNING; }, /** * @return {boolean} * @private */ computeShowOpenNow_() { const allowOpenNow = loadTimeData.getBoolean('allowOpenNow'); return this.data.state === States.ASYNC_SCANNING && allowOpenNow; }, /** * @return {string} * @private */ computeTag_() { switch (this.data.state) { case States.CANCELLED: return loadTimeData.getString('statusCancelled'); case States.INTERRUPTED: return this.data.lastReasonText; case States.COMPLETE: return this.data.fileExternallyRemoved ? loadTimeData.getString('statusRemoved') : ''; } return ''; }, /** * @return {boolean} * @private */ isIndeterminate_() { return this.data.percent === -1; }, /** @private */ observeControlledBy_() { this.$['controlled-by'].innerHTML = this.controlledBy_; if (this.controlledBy_) { const link = this.$$('#controlled-by a'); link.setAttribute('focus-row-control', ''); link.setAttribute('focus-type', 'controlledBy'); } }, /** @private */ observeIsDangerous_() { if (!this.data) { return; } const OVERRIDDEN_ICON_TYPES = [ DangerType.SENSITIVE_CONTENT_BLOCK, DangerType.BLOCKED_TOO_LARGE, DangerType.BLOCKED_PASSWORD_PROTECTED, ]; if (this.isDangerous_) { this.$.url.removeAttribute('href'); this.useFileIcon_ = false; } else if (OVERRIDDEN_ICON_TYPES.includes(this.data.dangerType)) { this.useFileIcon_ = false; } else if (this.data.state === States.ASYNC_SCANNING) { this.useFileIcon_ = false; } else { this.$.url.href = assert(this.data.url); const path = this.data.filePath; IconLoader.getInstance() .loadIcon(this.$['file-icon'], path) .then(success => { if (path === this.data.filePath && this.data.state !== States.ASYNC_SCANNING) { this.useFileIcon_ = success; } }); } }, /** @private */ onCancelTap_() { this.restoreFocusAfterCancel_ = true; this.mojoHandler_.cancel(this.data.id); }, /** @private */ onDiscardDangerousTap_() { this.mojoHandler_.discardDangerous(this.data.id); }, /** @private */ onOpenNowTap_() { this.mojoHandler_.openDuringScanningRequiringGesture(this.data.id); }, /** * @private * @param {Event} e */ onDragStart_(e) { e.preventDefault(); this.mojoHandler_.drag(this.data.id); }, /** * @param {Event} e * @private */ onFileLinkTap_(e) { e.preventDefault(); this.mojoHandler_.openFileRequiringGesture(this.data.id); }, /** @private */ onUrlTap_() { chrome.send( 'metricsHandler:recordAction', ['Downloads_OpenUrlOfDownloadedItem']); }, /** @private */ onPauseOrResumeTap_() { if (this.isInProgress_) { this.mojoHandler_.pause(this.data.id); } else { this.mojoHandler_.resume(this.data.id); } }, /** @private */ onRemoveTap_() { this.mojoHandler_.remove(this.data.id); const pieces = loadTimeData.getSubstitutedStringPieces( loadTimeData.getString('toastRemovedFromList'), this.data.fileName); pieces.forEach(p => { // Make the file name collapsible. p.collapsible = !!p.arg; }); const canUndo = !this.data.isDangerous && !this.data.isMixedContent; getToastManager().showForStringPieces( /** * @type {!Array<{collapsible: boolean, * value: string, * arg: (string|null)}>} */ (pieces), /* hideSlotted= */ !canUndo); }, /** @private */ onRetryTap_() { this.mojoHandler_.retryDownload(this.data.id); }, /** @private */ onSaveDangerousTap_() { this.mojoHandler_.saveDangerousRequiringGesture(this.data.id); }, /** @private */ onShowTap_() { this.mojoHandler_.show(this.data.id); }, /** @private */ restoreFocusAfterCancelIfNeeded_() { if (!this.restoreFocusAfterCancel_) { return; } this.restoreFocusAfterCancel_ = false; setTimeout(() => { const element = this.getFocusRow().getFirstFocusable('retry'); if (element) { element.focus(); } }); }, });