blob: e208067a3712feddd42bfd13f4b6a9e535460707 [file] [log] [blame]
// 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.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, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserProxy} from './browser_proxy.js';
import {DangerType, States} from './constants.js';
import {MojomData} from './data.js';
import {PageHandlerInterface} from './downloads.mojom-webui.js';
import {IconLoader} from './icon_loader.js';
export interface DownloadsItemElement {
$: {
'controlled-by': HTMLElement,
'file-icon': HTMLImageElement,
'url': HTMLAnchorElement,
'remove': HTMLElement,
};
}
const DownloadsItemElementBase =
mixinBehaviors([FocusRowBehavior], PolymerElement) as
{new (): PolymerElement & FocusRowBehavior};
export class DownloadsItemElement extends DownloadsItemElementBase {
static get is() {
return 'downloads-item';
}
static get properties() {
return {
data: Object,
completelyOnDisk_: {
computed: 'computeCompletelyOnDisk_(' +
'data.state, data.fileExternallyRemoved)',
type: Boolean,
value: true,
},
hasShowInFolderLink_: {
computed: 'computeHasShowInFolderLink_(' +
'data.state, data.fileExternallyRemoved)',
type: Boolean,
value: true,
},
controlledBy_: {
computed: 'computeControlledBy_(data.byExtId, data.byExtName)',
type: String,
value: '',
},
controlRemoveFromListAriaLabel_: {
type: String,
computed: 'computeControlRemoveFromListAriaLabel_(data.fileName)',
},
isActive_: {
computed: 'computeIsActive_(' +
'data.state, data.fileExternallyRemoved)',
type: Boolean,
value: true,
},
isDangerous_: {
computed: 'computeIsDangerous_(data.state)',
type: Boolean,
value: false,
},
isMalware_: {
computed: 'computeIsMalware_(isDangerous_, data.dangerType)',
type: Boolean,
value: false,
},
isInProgress_: {
computed: 'computeIsInProgress_(data.state)',
type: Boolean,
value: false,
},
pauseOrResumeText_: {
computed: 'computePauseOrResumeText_(isInProgress_, data.resume)',
type: String,
observer: 'updatePauseOrResumeClass_',
},
showCancel_: {
computed: 'computeShowCancel_(data.state)',
type: Boolean,
value: false,
},
showProgress_: {
computed: 'computeShowProgress_(showCancel_, data.percent)',
type: Boolean,
value: false,
},
showOpenNow_: {
computed: 'computeShowOpenNow_(data.state)',
type: Boolean,
value: false,
},
useFileIcon_: Boolean,
};
}
static get observers() {
return [
// TODO(dbeam): this gets called way more when I observe data.byExtId
// and data.byExtName directly. Why?
'observeControlledBy_(controlledBy_)',
'observeIsDangerous_(isDangerous_, data)',
'restoreFocusAfterCancelIfNeeded_(data)',
];
}
data: MojomData;
private mojoHandler_: PageHandlerInterface|null = null;
private controlledBy_: string;
private isActive_: boolean;
private isDangerous_: boolean;
private isInProgress_: boolean;
private pauseOrResumeText_: string;
private showCancel_: boolean;
private showProgress_: boolean;
private useFileIcon_: boolean;
private restoreFocusAfterCancel_: boolean = false;
overrideCustomEquivalent: boolean;
constructor() {
super();
/** Used by FocusRowBehavior. */
this.overrideCustomEquivalent = true;
}
/** @override */
ready() {
super.ready();
this.setAttribute('role', 'row');
this.mojoHandler_ = BrowserProxy.getInstance().handler;
}
focusOnRemoveButton() {
focusWithoutInk(this.$.remove);
}
/** Overrides FocusRowBehavior. */
getCustomEquivalent(sampleElement: HTMLElement): HTMLElement|null {
if (sampleElement.getAttribute('focus-type') === 'cancel') {
return this.shadowRoot!.querySelector('[focus-type="retry"]');
}
if (sampleElement.getAttribute('focus-type') === 'retry') {
return this.shadowRoot!.querySelector('[focus-type="pauseOrResume"]');
}
return null;
}
getFileIcon(): HTMLImageElement {
return this.$['file-icon'];
}
/**
* @return A reasonably long URL.
*/
private chopUrl_(url: string): string {
return url.slice(0, 300);
}
private computeClass_(): string {
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(' ');
}
private computeCompletelyOnDisk_(): boolean {
return this.data.state === States.COMPLETE &&
!this.data.fileExternallyRemoved;
}
private computeHasShowInFolderLink_(): boolean {
return loadTimeData.getBoolean('hasShowInFolder') &&
this.computeCompletelyOnDisk_();
}
private computeControlledBy_(): string {
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));
}
private computeControlRemoveFromListAriaLabel_(): string {
return loadTimeData.getStringF(
'controlRemoveFromListAriaLabel', this.data.fileName);
}
private computeDate_(): string {
assert(typeof this.data.hideDate === 'boolean');
if (this.data.hideDate) {
return '';
}
return assert(this.data.sinceString || this.data.dateString);
}
private computeDescriptionVisible_(): boolean {
return this.computeDescription_() !== '';
}
private computeDescription_(): string {
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');
case DangerType.DANGEROUS_ACCOUNT_COMPROMISE:
return loadTimeData.getString('accountCompromiseDownloadDesc');
}
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 '';
}
private computeIcon_(): string {
if (this.data) {
const dangerType = this.data.dangerType as 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 '';
}
private computeIconColor_(): string {
if (this.data) {
const dangerType = this.data.dangerType as 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 '';
}
private computeIsActive_(): boolean {
return this.data.state !== States.CANCELLED &&
this.data.state !== States.INTERRUPTED &&
!this.data.fileExternallyRemoved;
}
private computeIsDangerous_(): boolean {
return this.data.state === States.DANGEROUS ||
this.data.state === States.MIXED_CONTENT;
}
private computeIsInProgress_(): boolean {
return this.data.state === States.IN_PROGRESS;
}
private computeIsMalware_(): boolean {
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 ||
this.data.dangerType === DangerType.DANGEROUS_ACCOUNT_COMPROMISE);
}
private toggleButtonClass_() {
this.shadowRoot!.querySelector('#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_());
}
private computePauseOrResumeText_(): string {
if (this.data === undefined) {
return '';
}
if (this.isInProgress_) {
return loadTimeData.getString('controlPause');
}
if (this.data.resume) {
return loadTimeData.getString('controlResume');
}
return '';
}
private computeRemoveStyle_(): string {
const canDelete = loadTimeData.getBoolean('allowDeletingHistory');
const hideRemove = this.isDangerous_ || this.showCancel_ || !canDelete;
return hideRemove ? 'visibility: hidden' : '';
}
private computeShowCancel_(): boolean {
return this.data.state === States.IN_PROGRESS ||
this.data.state === States.PAUSED ||
this.data.state === States.ASYNC_SCANNING;
}
private computeShowProgress_(): boolean {
return this.showCancel_ && this.data.percent >= -1 &&
this.data.state !== States.ASYNC_SCANNING;
}
private computeShowOpenNow_(): boolean {
const allowOpenNow = loadTimeData.getBoolean('allowOpenNow');
return this.data.state === States.ASYNC_SCANNING && allowOpenNow;
}
private computeTag_(): string {
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 '';
}
private isIndeterminate_(): boolean {
return this.data.percent === -1;
}
private observeControlledBy_() {
this.$['controlled-by'].innerHTML = this.controlledBy_;
if (this.controlledBy_) {
const link = this.shadowRoot!.querySelector('#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 as 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 onDragStart_(e: Event) {
e.preventDefault();
this.mojoHandler_!.drag(this.data.id);
}
private onFileLinkTap_(e: Event) {
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_(e: Event) {
this.mojoHandler_!.remove(this.data.id);
const pieces = loadTimeData.getSubstitutedStringPieces(
loadTimeData.getString('toastRemovedFromList'),
this.data.fileName) as unknown as
Array<{collapsible: boolean, value: string, arg?: string}>;
pieces.forEach(p => {
// Make the file name collapsible.
p.collapsible = !!p.arg;
});
const canUndo = !this.data.isDangerous && !this.data.isMixedContent;
getToastManager().showForStringPieces(pieces, /* hideSlotted= */ !canUndo);
// Stop propagating a click to the document to remove toast.
e.stopPropagation();
e.preventDefault();
}
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 as HTMLElement).focus();
}
});
}
static get template() {
return html`{__html_template__}`;
}
}
customElements.define(DownloadsItemElement.is, DownloadsItemElement);