chromium/chrome/browser/resources/settings/about_page/about_page.ts

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview 'settings-about-page' contains version and OS related
 * information.
 */

import '../icons.html.js';
import '/shared/settings/prefs/prefs.js';
// <if expr="not chromeos_ash">
import '../relaunch_confirmation_dialog.js';
// </if>
import '../settings_page/settings_section.js';
import '../settings_page_styles.css.js';
import '../settings_shared.css.js';
// <if expr="_google_chrome">
import './get_most_chrome_section.js';
// </if>
import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
import 'chrome://resources/cr_elements/icons.html.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';

import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
import {assert} from 'chrome://resources/js/assert.js';
// <if expr="_google_chrome">
import {OpenWindowProxyImpl} from 'chrome://resources/js/open_window_proxy.js';
// </if>
import {sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {loadTimeData} from '../i18n_setup.js';
import {RelaunchMixin, RestartType} from '../relaunch_mixin.js';

import {getTemplate} from './about_page.html.js';
import type {AboutPageBrowserProxy, UpdateStatusChangedEvent} from './about_page_browser_proxy.js';
import {AboutPageBrowserProxyImpl, UpdateStatus} from './about_page_browser_proxy.js';
// clang-format off
// <if expr="_google_chrome and is_macosx">
import type {PromoteUpdaterStatus} from './about_page_browser_proxy.js';
// </if>
// clang-format on

// <if expr="_google_chrome">
export const ABOUT_PAGE_PRIVACY_POLICY_URL: string =
    'https://policies.google.com/privacy';
// </if>

const SettingsAboutPageElementBase =
    RelaunchMixin(WebUiListenerMixin(I18nMixin(PolymerElement)));

export class SettingsAboutPageElement extends SettingsAboutPageElementBase {
  static get is() {
    return 'settings-about-page';
  }

  static get template() {
    return getTemplate();
  }

  static get properties() {
    return {
      currentUpdateStatusEvent_: {
        type: Object,
        value: {
          message: '',
          progress: 0,
          rollback: false,
          status: UpdateStatus.DISABLED,
        },
      },

      /**
       * Whether the browser/ChromeOS is managed by their organization
       * through enterprise policies.
       */
      isManaged_: {
        type: Boolean,
        value() {
          return loadTimeData.getBoolean('isManaged');
        },
      },

      /**
       * The name of the icon to display in the management card.
       * Should only be read if isManaged_ is true.
       */
      managedByIcon_: {
        type: String,
        value() {
          return loadTimeData.getString('managedByIcon');
        },
      },

      /**
       * Whether to show the "Get the most out of Chrome" section.
       */
      showGetTheMostOutOfChromeSection_: {
        type: Boolean,
        value() {
          let result = false;
          // <if expr="_google_chrome">
          result =
              loadTimeData.getBoolean('showGetTheMostOutOfChromeSection') &&
              !loadTimeData.getBoolean('isGuest');
          // </if>
          return result;
        },
      },

      // <if expr="_google_chrome and is_macosx">
      promoteUpdaterStatus_: Object,
      // </if>

      // <if expr="not chromeos_ash">
      obsoleteSystemInfo_: {
        type: Object,
        value() {
          return {
            obsolete: loadTimeData.getBoolean('aboutObsoleteNowOrSoon'),
            endOfLine: loadTimeData.getBoolean('aboutObsoleteEndOfTheLine'),
          };
        },
      },

      showUpdateStatus_: {
        type: Boolean,
        value: false,
      },

      showButtonContainer_: Boolean,

      showRelaunch_: {
        type: Boolean,
        value: false,
      },
      // </if>
    };
  }

  // <if expr="not chromeos_ash">
  static get observers() {
    return [
      'updateShowUpdateStatus_(' +
          'obsoleteSystemInfo_, currentUpdateStatusEvent_)',
      'updateShowRelaunch_(currentUpdateStatusEvent_)',
      'updateShowButtonContainer_(showRelaunch_)',
    ];
  }
  // </if>

  private currentUpdateStatusEvent_: UpdateStatusChangedEvent|null;
  private isManaged_: boolean;
  private showGetTheMostOutOfChromeSection_: boolean;

  // <if expr="_google_chrome and is_macosx">
  private promoteUpdaterStatus_: PromoteUpdaterStatus;
  // </if>

  // <if expr="not chromeos_ash">
  private obsoleteSystemInfo_: {obsolete: boolean, endOfLine: boolean};
  private showUpdateStatus_: boolean;
  private showButtonContainer_: boolean;
  private showRelaunch_: boolean;
  // </if>

  private aboutBrowserProxy_: AboutPageBrowserProxy =
      AboutPageBrowserProxyImpl.getInstance();

  override connectedCallback() {
    super.connectedCallback();

    this.aboutBrowserProxy_.pageReady();

    // <if expr="not chromeos_ash">
    this.startListening_();
    // </if>
  }

  private getPromoteUpdaterClass_(): string {
    // <if expr="_google_chrome and is_macosx">
    if (this.promoteUpdaterStatus_.disabled) {
      return 'cr-secondary-text';
    }
    // </if>

    return '';
  }

  // <if expr="not chromeos_ash">
  private startListening_() {
    this.addWebUiListener(
        'update-status-changed', this.onUpdateStatusChanged_.bind(this));
    // <if expr="_google_chrome and is_macosx">
    this.addWebUiListener(
        'promotion-state-changed',
        this.onPromoteUpdaterStatusChanged_.bind(this));
    // </if>
    this.aboutBrowserProxy_.refreshUpdateStatus();
  }

  private onUpdateStatusChanged_(event: UpdateStatusChangedEvent) {
    this.currentUpdateStatusEvent_! = event;
  }
  // </if>

  // <if expr="_google_chrome and is_macosx">
  private onPromoteUpdaterStatusChanged_(status: PromoteUpdaterStatus) {
    this.promoteUpdaterStatus_ = status;
  }

  /**
   * If #promoteUpdater isn't disabled, trigger update promotion.
   */
  private onPromoteUpdaterClick_() {
    // This is necessary because #promoteUpdater is not a button, so by default
    // disable doesn't do anything.
    if (this.promoteUpdaterStatus_.disabled) {
      return;
    }
    this.aboutBrowserProxy_.promoteUpdater();
  }
  // </if>

  private onLearnMoreClick_(event: Event) {
    // Stop the propagation of events, so that clicking on links inside
    // actionable items won't trigger action.
    event.stopPropagation();
  }

  private onHelpClick_() {
    this.aboutBrowserProxy_.openHelpPage();
  }

  private onRelaunchClick_() {
    this.performRestart(RestartType.RELAUNCH);
  }

  // <if expr="not chromeos_ash">
  private updateShowUpdateStatus_() {
    if (this.obsoleteSystemInfo_.endOfLine) {
      this.showUpdateStatus_ = false;
      return;
    }
    this.showUpdateStatus_ =
        this.currentUpdateStatusEvent_!.status !== UpdateStatus.DISABLED;
  }

  /**
   * Hide the button container if all buttons are hidden, otherwise the
   * container displays an unwanted border (see separator class).
   */
  private updateShowButtonContainer_() {
    this.showButtonContainer_ = this.showRelaunch_;
  }

  private updateShowRelaunch_() {
    this.showRelaunch_ = this.checkStatus_(UpdateStatus.NEARLY_UPDATED);
  }

  private shouldShowLearnMoreLink_(): boolean {
    return this.currentUpdateStatusEvent_!.status === UpdateStatus.FAILED;
  }

  private getUpdateStatusMessage_(): TrustedHTML {
    switch (this.currentUpdateStatusEvent_!.status) {
      case UpdateStatus.CHECKING:
      case UpdateStatus.NEED_PERMISSION_TO_UPDATE:
        return this.i18nAdvanced('aboutUpgradeCheckStarted');
      case UpdateStatus.NEARLY_UPDATED:
        return this.i18nAdvanced('aboutUpgradeRelaunch');
      case UpdateStatus.UPDATED:
        return this.i18nAdvanced('aboutUpgradeUpToDate');
      case UpdateStatus.UPDATING:
        assert(typeof this.currentUpdateStatusEvent_!.progress === 'number');
        const progressPercent = this.currentUpdateStatusEvent_!.progress + '%';

        if (this.currentUpdateStatusEvent_!.progress! > 0) {
          // NOTE(dbeam): some platforms (i.e. Mac) always send 0% while
          // updating (they don't support incremental upgrade progress). Though
          // it's certainly quite possible to validly end up here with 0% on
          // platforms that support incremental progress, nobody really likes
          // seeing that they're 0% done with something.
          return this.i18nAdvanced('aboutUpgradeUpdatingPercent', {
            substitutions: [progressPercent],
          });
        }
        return this.i18nAdvanced('aboutUpgradeUpdating');
      default:
        let result = '';
        const message = this.currentUpdateStatusEvent_!.message;
        if (message) {
          result += message;
        }
        const connectMessage = this.currentUpdateStatusEvent_!.connectionTypes;
        if (connectMessage) {
          result += `<div>${connectMessage}</div>`;
        }

        return sanitizeInnerHtml(result, {tags: ['br', 'pre']});
    }
  }

  private getUpdateStatusIcon_(): string|null {
    // If this platform has reached the end of the line, display an error icon
    // and ignore UpdateStatus.
    if (this.obsoleteSystemInfo_.endOfLine) {
      return 'cr:error';
    }

    switch (this.currentUpdateStatusEvent_!.status) {
      case UpdateStatus.DISABLED_BY_ADMIN:
        return 'cr20:domain';
      case UpdateStatus.FAILED:
        return 'cr:error';
      case UpdateStatus.UPDATED:
      case UpdateStatus.NEARLY_UPDATED:
        return 'settings:check-circle';
      default:
        return null;
    }
  }

  private getThrobberSrcIfUpdating_(): string|null {
    if (this.obsoleteSystemInfo_.endOfLine) {
      return null;
    }

    switch (this.currentUpdateStatusEvent_!.status) {
      case UpdateStatus.CHECKING:
      case UpdateStatus.UPDATING:
        return 'chrome://resources/images/throbber_small.svg';
      default:
        return null;
    }
  }
  // </if>

  private checkStatus_(status: UpdateStatus): boolean {
    return this.currentUpdateStatusEvent_!.status === status;
  }

  private onManagementPageClick_() {
    window.location.href = loadTimeData.getString('managementPageUrl');
  }

  private onProductLogoClick_() {
    this.$['product-logo'].animate(
        {
          transform: ['none', 'rotate(-10turn)'],
        },
        {
          duration: 500,
          easing: 'cubic-bezier(1, 0, 0, 1)',
        });
  }

  // <if expr="_google_chrome">
  private onReportIssueClick_() {
    this.aboutBrowserProxy_.openFeedbackDialog();
  }

  private onPrivacyPolicyClick_() {
    OpenWindowProxyImpl.getInstance().openUrl(ABOUT_PAGE_PRIVACY_POLICY_URL);
  }
  // </if>

  // <if expr="not chromeos_ash">
  private shouldShowIcons_(): boolean {
    if (this.obsoleteSystemInfo_.endOfLine) {
      return true;
    }
    return this.showUpdateStatus_;
  }
  // </if>
}

declare global {
  interface HTMLElementTagNameMap {
    'settings-about-page': SettingsAboutPageElement;
  }
}

customElements.define(SettingsAboutPageElement.is, SettingsAboutPageElement);