chromium/chrome/browser/resources/chromeos/parent_access/parent_access_app.ts

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

// strings.m.js is generated when we enable it via UseStringsJs() in webUI
// controller. When loading it, it will populate data such as localized strings
// into |loadTimeData|.
import './strings.m.js';
import './parent_access_after.js';
import './parent_access_before.js';
import './parent_access_disabled.js';
import './parent_access_error.js';
import './parent_access_offline.js';
import './parent_access_ui.js';
import 'chrome://resources/ash/common/cr_elements/cros_color_overrides.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_view_manager/cr_view_manager.js';

import {CrViewManagerElement} from 'chrome://resources/ash/common/cr_elements/cr_view_manager/cr_view_manager.js';
import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {getTemplate} from './parent_access_app.html.js';
import {ParentAccessParams_FlowType, ParentAccessResult} from './parent_access_ui.mojom-webui.js';
import {getParentAccessParams, getParentAccessUiHandler} from './parent_access_ui_handler.js';

export interface ParentAccessApp {
  $: {
    viewManager: CrViewManagerElement,
  };
}

export enum Screens {
  AUTHENTICATION_FLOW = 'parent-access-ui',
  BEFORE_FLOW = 'parent-access-before',
  AFTER_FLOW = 'parent-access-after',
  DISABLED = 'parent-access-disabled',
  ERROR = 'parent-access-error',
  OFFLINE = 'parent-access-offline',
}

export enum ParentAccessEvent {
  SHOW_AFTER = 'show-after',
  SHOW_AUTHENTICATION_FLOW = 'show-authentication-flow',
  SHOW_ERROR = 'show-error',
  // Individual screens can listen for this event to be notified when the screen
  // becomes active.
  ON_SCREEN_SWITCHED = 'on-screen-switched',
}

/**
 * Returns true if the Parent Access Jelly feature flag is enabled.
 * @return {boolean}
 */
export function isParentAccessJellyEnabled() {
  return loadTimeData.valueExists('isParentAccessJellyEnabled') &&
      loadTimeData.getBoolean('isParentAccessJellyEnabled');
}

export class ParentAccessApp extends PolymerElement {
  static get is() {
    return 'parent-access-app';
  }

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

  private currentScreen: Screens;

  override ready() {
    super.ready();

    // TODO (b/297564545): Clean up Jelly flag logic after Jelly is enabled.
    if (isParentAccessJellyEnabled()) {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = 'chrome://theme/colors.css?sets=legacy,sys';
      document.head.appendChild(link);
      document.body.classList.add('jelly-enabled');
      /** @suppress {checkTypes} */
      (function() {
        ColorChangeUpdater.forDocument().start();
      })();
    }

    this.addEventListeners();
    this.getInitialScreen().then((initialScreen: Screens) => {
      this.switchScreen(navigator.onLine ? initialScreen : Screens.OFFLINE);
    });
  }

  getCurrentScreenForTest(): Screens {
    return this.currentScreen;
  }

  private addEventListeners() {
    this.addEventListener(ParentAccessEvent.SHOW_AFTER, () => {
      this.switchScreen(Screens.AFTER_FLOW);
    });

    this.addEventListener(ParentAccessEvent.SHOW_AUTHENTICATION_FLOW, () => {
      this.switchScreen(Screens.AUTHENTICATION_FLOW);
      getParentAccessUiHandler().onBeforeScreenDone();
    });

    this.addEventListener(ParentAccessEvent.SHOW_ERROR, () => {
      this.onError();
    });

    window.addEventListener('online', () => {
      // If the app comes back online, start from the initial screen.
      this.getInitialScreen().then((initialScreen: Screens) => {
        this.switchScreen(initialScreen);
      });
    });

    window.addEventListener('offline', () => {
      this.switchScreen(Screens.OFFLINE);
    });
  }

  private async getInitialScreen() {
    const response = await getParentAccessParams();
    if (response!.params.isDisabled) {
      return Screens.DISABLED;
    }
    switch (response!.params.flowType) {
      case ParentAccessParams_FlowType.kExtensionAccess:
        return Screens.BEFORE_FLOW;
      case ParentAccessParams_FlowType.kWebsiteAccess:
      default:
        return Screens.AUTHENTICATION_FLOW;
    }
  }

  /** Shows an error screen, which is a terminal state for the flow. */
  private onError() {
    this.switchScreen(Screens.ERROR);
    getParentAccessUiHandler().onParentAccessDone(ParentAccessResult.kError);
  }

  private switchScreen(screen: Screens) {
    if (this.isAppInTerminalState()) {
      return;
    }
    this.currentScreen = screen;
    this.$.viewManager.switchView(this.currentScreen);
    this.shadowRoot!.querySelector(screen)!.dispatchEvent(
        new CustomEvent(ParentAccessEvent.ON_SCREEN_SWITCHED));
  }

  /** Returns if the app can navigate away from the current screen. */
  private isAppInTerminalState(): boolean {
    return this.currentScreen === Screens.ERROR ||
        this.currentScreen === Screens.DISABLED;
  }
}
customElements.define(ParentAccessApp.is, ParentAccessApp);