chromium/chrome/browser/resources/password_manager/side_bar.ts

// Copyright 2022 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_menu_selector/cr_menu_selector.js';
import 'chrome://resources/cr_elements/cr_nav_menu_item_style.css.js';
import 'chrome://resources/cr_elements/cr_ripple/cr_ripple.js';
import 'chrome://resources/cr_elements/icons.html.js';
import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
import './icons.html.js';

import {HelpBubbleMixin} from 'chrome://resources/cr_components/help_bubble/help_bubble_mixin.js';
import type {CrMenuSelector} from 'chrome://resources/cr_elements/cr_menu_selector/cr_menu_selector.js';
import {assert} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import type {CredentialsChangedListener} from './password_manager_proxy.js';
import {PasswordManagerImpl} from './password_manager_proxy.js';
import type {Route} from './router.js';
import {Page, RouteObserverMixin, Router, UrlParam} from './router.js';
import {getTemplate} from './side_bar.html.js';

/**
 * Represents different referrers when navigating to the Password Check page.
 *
 * These values are persisted to logs. Entries should not be renumbered and
 * numeric values should never be reused.
 *
 * Needs to stay in sync with PasswordCheckReferrer in enums.xml and
 * password_check_referrer.h.
 */
enum PasswordCheckReferrer {
  SAFETY_CHECK = 0,            // Web UI, recorded in JavaScript.
  PASSWORD_SETTINGS = 1,       // Web UI, recorded in JavaScript.
  PHISH_GUARD_DIALOG = 2,      // Native UI, recorded in C++.
  PASSWORD_BREACH_DIALOG = 3,  // Native UI, recorded in C++.
  // Must be last.
  COUNT = 4,
}

export interface PasswordManagerSideBarElement {
  $: {
    menu: CrMenuSelector,
    compromisedPasswords: HTMLElement,
    settings: HTMLElement,
  };
}

const PASSWORD_MANAGER_SETTINGS_MENU_ITEM_ELEMENT_ID =
    'PasswordManagerUI::kSettingsMenuItemElementId';

const PasswordManagerSideBarElementBase =
    HelpBubbleMixin(RouteObserverMixin(PolymerElement));

export class PasswordManagerSideBarElement extends
    PasswordManagerSideBarElementBase {
  static get is() {
    return 'password-manager-side-bar';
  }

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

  static get properties() {
    return {
      // The id of the currently selected page.
      selectedPage_: String,

      // The count of compromised passwords currently known to the password
      // manager.
      compromisedPasswords_: Number,
    };
  }

  private selectedPage_: Page;
  private compromisedPasswords_: number;
  private insecureCredentialsChangedListener_: CredentialsChangedListener|null =
      null;

  override connectedCallback() {
    super.connectedCallback();
    this.insecureCredentialsChangedListener_ = insecureCredentials => {
      const compromisedTypes = [
        chrome.passwordsPrivate.CompromiseType.LEAKED,
        chrome.passwordsPrivate.CompromiseType.PHISHED,
      ];
      this.compromisedPasswords_ =
          insecureCredentials
              .filter(cred => {
                return !cred.compromisedInfo!.isMuted &&
                    cred.compromisedInfo!.compromiseTypes.some(type => {
                      return compromisedTypes.includes(type);
                    });
              })
              .length;
      this.registerHelpBubble(
          PASSWORD_MANAGER_SETTINGS_MENU_ITEM_ELEMENT_ID, this.$.settings);
    };

    PasswordManagerImpl.getInstance().getInsecureCredentials().then(
        this.insecureCredentialsChangedListener_);
    PasswordManagerImpl.getInstance().addInsecureCredentialsListener(
        this.insecureCredentialsChangedListener_);
  }

  override disconnectedCallback() {
    super.disconnectedCallback();
    assert(this.insecureCredentialsChangedListener_);
    PasswordManagerImpl.getInstance().removeInsecureCredentialsListener(
        this.insecureCredentialsChangedListener_);
    this.insecureCredentialsChangedListener_ = null;
  }

  override currentRouteChanged(route: Route, _: Route): void {
    this.selectedPage_ = route.page;
  }

  private onSelectorActivate_(event: CustomEvent<{selected: Page}>) {
    Router.getInstance().navigateTo(event.detail.selected);
    if (event.detail.selected === Page.CHECKUP) {
      const params = new URLSearchParams();
      params.set(UrlParam.START_CHECK, 'true');
      Router.getInstance().updateRouterParams(params);
      chrome.metricsPrivate.recordEnumerationValue(
          'PasswordManager.BulkCheck.PasswordCheckReferrer',
          PasswordCheckReferrer.PASSWORD_SETTINGS, PasswordCheckReferrer.COUNT);
    }
    this.dispatchEvent(
        new CustomEvent('close-drawer', {bubbles: true, composed: true}));
  }

  private getSelectedPage_(): string {
    switch (this.selectedPage_) {
      case Page.CHECKUP_DETAILS:
        return Page.CHECKUP;
      case Page.PASSWORD_DETAILS:
        return Page.PASSWORDS;
      default:
        return this.selectedPage_;
    }
  }

  /**
   * Prevent clicks on sidebar items from navigating. These are only links for
   * accessibility purposes, taps are handled separately.
   */
  private onItemClick_(e: Event) {
    e.preventDefault();
  }

  private getCompromisedPasswordsBadge_(): string {
    if (this.compromisedPasswords_ > 99) {
      return '99+';
    }
    return String(this.compromisedPasswords_);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'password-manager-side-bar': PasswordManagerSideBarElement;
  }
}

customElements.define(
    PasswordManagerSideBarElement.is, PasswordManagerSideBarElement);