chromium/chrome/browser/resources/password_manager/promo_cards/promo_card.ts

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/cr_elements/cr_icons.css.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/cr_shared_vars.css.js';

import type {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
import type {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {assertNotReached} from 'chrome://resources/js/assert.js';
import {sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {PasswordManagerImpl} from '../password_manager_proxy.js';
import {Page, Router, UrlParam} from '../router.js';

import {getTemplate} from './promo_card.html.js';
import type {PromoCard} from './promo_cards_browser_proxy.js';
import {PromoCardsProxyImpl} from './promo_cards_browser_proxy.js';

// WARNING: Keep synced with
// chrome/browser/ui/webui/password_manager/promo_cards_handler.cc.
export enum PromoCardId {
  CHECKUP = 'password_checkup_promo',
  WEB_PASSWORD_MANAGER = 'passwords_on_web_promo',
  SHORTCUT = 'password_shortcut_promo',
  ACCESS_ON_ANY_DEVICE = 'access_on_any_device_promo',
  RELAUNCH_CHROME = 'relaunch_chrome_promo',
  MOVE_PASSWORDS = 'move_passwords_promo',
  SCREENLOCK_REAUTH = 'screenlock_reauth_promo',
}

/**
 * These values are persisted to logs. Entries should not be renumbered and
 * numeric values should never be reused.
 *
 * Needs to stay in sync with PromoCardType in promo_card.h
 */
enum PromoCardMetricId {
  CHECKUP = 0,
  UNUSED_WEB_PASSWORD_MANAGER = 1,
  SHORTCUT = 2,
  UNUSED_ACCESS_ON_ANY_DEVICE = 3,
  RELAUNCH_CHROME = 4,
  MOVE_PASSWORDS = 5,
  SCREENLOCK_REAUTH = 6,
  // Must be last.
  COUNT = 7,
}

function recordPromoCardAction(card: PromoCardMetricId) {
  chrome.metricsPrivate.recordEnumerationValue(
      'PasswordManager.PromoCard.ActionButtonClicked', card,
      PromoCardMetricId.COUNT);
}

export interface PromoCardElement {
  $: {
    actionButton: CrButtonElement,
    closeButton: CrIconButtonElement,
    description: HTMLElement,
    title: HTMLElement,
  };
}

const isOpenedAsShortcut = window.matchMedia('(display-mode: standalone)');

const PromoCardElementBase = I18nMixin(PolymerElement);

export class PromoCardElement extends PromoCardElementBase {
  static get is() {
    return 'promo-card';
  }

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

  static get properties() {
    return {
      promoCard: Object,
    };
  }

  promoCard: PromoCard;

  override connectedCallback() {
    super.connectedCallback();
    // If this is a shortcut promo we should listen to display mode changes to
    // close it automatically when shortcut is installed from another place.
    // Check crbug.com/1493264 for more details when it can happen.
    if (this.promoCard.id === PromoCardId.SHORTCUT) {
      isOpenedAsShortcut.addEventListener('change', this.close_.bind(this));
    }
  }

  override disconnectedCallback() {
    super.disconnectedCallback();

    if (this.promoCard.id === PromoCardId.SHORTCUT) {
      isOpenedAsShortcut.removeEventListener('change', this.close_.bind(this));
    }
  }

  private getDescription_(): TrustedHTML {
    return sanitizeInnerHtml(this.promoCard.description);
  }

  private async onActionButtonClick_() {
    switch (this.promoCard.id) {
      case PromoCardId.CHECKUP:
        const params = new URLSearchParams();
        params.set(UrlParam.START_CHECK, 'true');
        Router.getInstance().navigateTo(Page.CHECKUP, null, params);
        recordPromoCardAction(PromoCardMetricId.CHECKUP);
        break;
      case PromoCardId.SHORTCUT:
        PasswordManagerImpl.getInstance().showAddShortcutDialog();
        recordPromoCardAction(PromoCardMetricId.SHORTCUT);
        break;
      case PromoCardId.RELAUNCH_CHROME:
        chrome.send('restartBrowser');
        recordPromoCardAction(PromoCardMetricId.RELAUNCH_CHROME);
        break;
      case PromoCardId.MOVE_PASSWORDS:
        this.dispatchEvent(new CustomEvent(
            'move-passwords-clicked', {bubbles: true, composed: true}));
        recordPromoCardAction(PromoCardMetricId.MOVE_PASSWORDS);
        return;
      case PromoCardId.SCREENLOCK_REAUTH:
        recordPromoCardAction(PromoCardMetricId.SCREENLOCK_REAUTH);
        await PasswordManagerImpl.getInstance()
            .switchBiometricAuthBeforeFillingState()
            .then(result => {
              if (result) {
                this.dispatchEvent(new CustomEvent(
                    'biometric-auth-before-filling-enabled',
                    {bubbles: true, composed: true}));
              }
            });
        break;
      default:
        assertNotReached();
    }
    this.close_();
  }

  private onCloseClick_() {
    PromoCardsProxyImpl.getInstance().recordPromoDismissed(this.promoCard.id);
    this.close_();
  }

  private close_() {
    this.dispatchEvent(
        new CustomEvent('promo-closed', {bubbles: true, composed: true}));
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'promo-card': PromoCardElement;
  }
}

customElements.define(PromoCardElement.is, PromoCardElement);