chromium/chrome/browser/resources/ash/settings/os_about_page/channel_switcher_dialog.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-channel-switcher-dialog' is a component allowing the
 * user to switch between release channels (dev, beta, stable). A
 * |target-channel-changed| event is fired if the user does select a different
 * release channel to notify parents of this dialog.
 */

import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import 'chrome://resources/ash/common/cr_elements/cr_radio_button/cr_radio_button.js';
import 'chrome://resources/ash/common/cr_elements/cr_radio_group/cr_radio_group.js';
import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
import '../settings_shared.css.js';

import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {CrRadioGroupElement} from 'chrome://resources/ash/common/cr_elements/cr_radio_group/cr_radio_group.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {IronSelectorElement} from 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {castExists} from '../assert_extras.js';

import {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, BrowserChannel, isTargetChannelMoreStable} from './about_page_browser_proxy.js';
import {getTemplate} from './channel_switcher_dialog.html.js';

const WarningMessage = {
  NONE: -1,
  ENTERPRISE_MANAGED: 0,
  POWERWASH: 1,
  UNSTABLE: 2,
};

export interface SettingsChannelSwitcherDialogElement {
  $: {
    changeChannel: HTMLElement,
    changeChannelAndPowerwash: HTMLElement,
    dialog: CrDialogElement,
    warningSelector: IronSelectorElement,
  };
}

export class SettingsChannelSwitcherDialogElement extends PolymerElement {
  static get is() {
    return 'settings-channel-switcher-dialog';
  }

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

  static get properties() {
    return {
      browserChannelEnum_: {
        type: Object,
        value: BrowserChannel,
      },

      currentChannel_: String,

      targetChannel_: String,

      /**
       * Controls which of the two action buttons is visible.
       */
      shouldShowButtons_: {
        type: Object,
        value: null,
      },
    };
  }

  private browserChannelEnum_: typeof BrowserChannel;
  private currentChannel_: BrowserChannel;
  private targetChannel_: BrowserChannel;
  private shouldShowButtons_: {
    changeChannel: boolean,
    changeChannelAndPowerwash: boolean,
  }|null;

  private browserProxy_: AboutPageBrowserProxy;

  constructor() {
    super();

    this.browserProxy_ = AboutPageBrowserProxyImpl.getInstance();
  }

  override ready(): void {
    super.ready();

    this.browserProxy_.getChannelInfo().then(info => {
      this.currentChannel_ = info.currentChannel;
      this.targetChannel_ = info.targetChannel;
      // Pre-populate radio group with target channel.
      const radioGroup = this.getRadioGroup_();
      radioGroup.selected = this.targetChannel_;
      radioGroup.focus();
    });
  }

  override connectedCallback(): void {
    super.connectedCallback();

    this.$.dialog.showModal();
  }

  private getRadioGroup_(): CrRadioGroupElement {
    return castExists(this.shadowRoot!.querySelector('cr-radio-group'));
  }

  private onCancelClick_(): void {
    this.$.dialog.close();
  }

  private onChangeChannelClick_(): void {
    const selectedChannel = this.getRadioGroup_().selected as BrowserChannel;
    this.browserProxy_.setChannel(selectedChannel, false);
    this.$.dialog.close();
    this.fireTargetChannelChangedEvent_(selectedChannel);
  }

  private onChangeChannelAndPowerwashClick_(): void {
    const selectedChannel = this.getRadioGroup_().selected as BrowserChannel;
    this.browserProxy_.setChannel(selectedChannel, true);
    this.$.dialog.close();
    this.fireTargetChannelChangedEvent_(selectedChannel);
  }

  private fireTargetChannelChangedEvent_(detail = {}): void {
    const event = new CustomEvent(
        'target-channel-changed', {bubbles: true, composed: true, detail});
    this.dispatchEvent(event);
  }

  /**
   * @param changeChannel Whether the changeChannel button should be visible.
   * @param changeChannelAndPowerwash Whether the changeChannelAndPowerwash
   *    button should be visible.
   */
  private updateButtons_(
      changeChannel: boolean, changeChannelAndPowerwash: boolean): void {
    if (changeChannel || changeChannelAndPowerwash) {
      // Ensure that at most one button is visible at any given time.
      assert(changeChannel !== changeChannelAndPowerwash);
    }

    this.shouldShowButtons_ = {
      changeChannel: changeChannel,
      changeChannelAndPowerwash: changeChannelAndPowerwash,
    };
  }

  private onChannelSelectionChanged_(): void {
    const selectedChannel = this.getRadioGroup_().selected as BrowserChannel;

    // Selected channel is the same as the target channel so only show 'cancel'.
    if (selectedChannel === this.targetChannel_) {
      this.shouldShowButtons_ = null;
      this.$.warningSelector.select(WarningMessage.NONE);
      return;
    }

    // Selected channel is the same as the current channel, allow the user to
    // change without warnings.
    if (selectedChannel === this.currentChannel_) {
      this.updateButtons_(true, false);
      this.$.warningSelector.select(WarningMessage.NONE);
      return;
    }

    if (isTargetChannelMoreStable(this.currentChannel_, selectedChannel)) {
      // More stable channel selected. For non managed devices, notify the user
      // about powerwash.
      if (loadTimeData.getBoolean('aboutEnterpriseManaged')) {
        this.$.warningSelector.select(WarningMessage.ENTERPRISE_MANAGED);
        this.updateButtons_(true, false);
      } else {
        this.$.warningSelector.select(WarningMessage.POWERWASH);
        this.updateButtons_(false, true);
      }
    } else {
      if (selectedChannel === BrowserChannel.DEV) {
        // Dev channel selected, warn the user.
        this.$.warningSelector.select(WarningMessage.UNSTABLE);
      } else {
        this.$.warningSelector.select(WarningMessage.NONE);
      }
      this.updateButtons_(true, false);
    }
  }

  private substituteString_(format: string, replacement: string): string {
    return loadTimeData.substituteString(format, replacement);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'settings-channel-switcher-dialog': SettingsChannelSwitcherDialogElement;
  }
}

customElements.define(
    SettingsChannelSwitcherDialogElement.is,
    SettingsChannelSwitcherDialogElement);