text: // 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 {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
  import {afterNextRender, 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 {IconLoader} from './icon_loader.js';
  
  Polymer({
      is: 'downloads-item',
  
      _template: html`{__html_template__}`,
  
      behaviors: [
        FocusRowBehavior,
      ],
  
      /** Used by FocusRowBehavior. */
      overrideCustomEquivalent: true,
  
      properties: {
        /** @type {!downloads.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 {downloads.mojom.PageHandlerInterface} */
      mojoHandler_: null,
  
      /** @private {boolean} */
      restoreFocusAfterCancel_: false,
  
      /** @override */
      attached() {
        afterNextRender(this, function() {
          IronA11yAnnouncer.requestAvailability();
        });
      },
  
      /** @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_() {
        const pieces = loadTimeData.getSubstitutedStringPieces(
            loadTimeData.getString('toastRemovedFromList'), this.data.fileName);
        pieces.forEach(p => {
          // Make the file name collapsible.
          p.collapsible = !!p.arg;
        });
        getToastManager().showForStringPieces(
            /**
             * @type {!Array<{collapsible: boolean,
             *                 value: string,
             *                 arg: (string|null)}>}
             */
            (pieces));
        this.fire('iron-announce', {
          text: loadTimeData.getString('undoDescription'),
        });
        this.mojoHandler_.remove(this.data.id);
      },
  
      /** @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();
          }
        });
      }
    });
  