chromium/chrome/browser/resources/password_manager/dialogs/move_passwords_dialog.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_dialog/cr_dialog.js';
import '../shared_style.css.js';
import '../user_utils_mixin.js';
import './password_preview_item.js';

import type {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
import type {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import {assert} from 'chrome://resources/js/assert.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {PasswordManagerImpl} from '../password_manager_proxy.js';
import {UserUtilMixin} from '../user_utils_mixin.js';

import {getTemplate} from './move_passwords_dialog.html.js';

/**
 * This should be kept in sync with the enum in
 * components/password_manager/core/browser/password_manager_metrics_util.h.
 * These values are persisted to logs. Entries should not be renumbered and
 * numeric values should never be reused.
 * @enum {number}
 */
export enum MoveToAccountStoreTrigger {
  // LINT.IfChange
  SUCCESSFUL_LOGIN_WITH_PROFILE_STORE_PASSWORD = 0,
  EXPLICITLY_TRIGGERED_IN_SETTINGS = 1,
  EXPLICITLY_TRIGGERED_FOR_MULTIPLE_PASSWORDS_IN_SETTINGS = 2,
  USER_OPTED_IN_AFTER_SAVING_LOCALLY = 3,
  EXPLICITLY_TRIGGERED_FOR_SINGLE_PASSWORD_IN_DETAILS_IN_SETTINGS = 4,
  COUNT = 5,
  // LINT.ThenChange(//tools/metrics/histograms/metadata/password/enums.xml)
}


export interface MovePasswordsDialogElement {
  $: {
    accountEmail: HTMLElement,
    avatar: HTMLImageElement,
    cancel: CrButtonElement,
    dialog: CrDialogElement,
    move: CrButtonElement,
  };
}

const MovePasswordsDialogElementBase = UserUtilMixin(PolymerElement);

export class MovePasswordsDialogElement extends MovePasswordsDialogElementBase {
  static get is() {
    return 'move-passwords-dialog';
  }

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

  static get properties() {
    return {
      /**
       * Password groups displayed in the UI.
       */
      passwords: {
        type: Array,
        value: () => [],
      },

      selectedPasswordIds_: {
        type: Array,
        value: () => [],
      },

      trigger: {
        type: MoveToAccountStoreTrigger,
        value: MoveToAccountStoreTrigger
                   .EXPLICITLY_TRIGGERED_FOR_MULTIPLE_PASSWORDS_IN_SETTINGS,
      },
    };
  }

  passwords: chrome.passwordsPrivate.PasswordUiEntry[];
  private selectedPasswordIds_: number[];
  private trigger: MoveToAccountStoreTrigger;

  override connectedCallback() {
    super.connectedCallback();

    chrome.metricsPrivate.recordEnumerationValue(
        'PasswordManager.AccountStorage.MoveToAccountStoreFlowOffered',
        this.trigger, MoveToAccountStoreTrigger.COUNT);

    this.selectedPasswordIds_ = this.passwords.map(item => item.id);
    PasswordManagerImpl.getInstance()
        .requestCredentialsDetails(this.selectedPasswordIds_)
        .then(entries => {
          this.passwords = entries;
          this.$.dialog.showModal();
        })
        .catch(() => {
          this.$.dialog.close();
          this.dispatchEvent(
              new CustomEvent('close', {bubbles: true, composed: true}));
        });
  }

  private onCancel_() {
    this.$.dialog.cancel();
  }

  private onMoveButtonClick_() {
    assert(this.isOptedInForAccountStorage);
    PasswordManagerImpl.getInstance().movePasswordsToAccount(
        this.selectedPasswordIds_);
    this.dispatchEvent(new CustomEvent('passwords-moved', {
      bubbles: true,
      composed: true,
      detail: {
        accountEmail: this.accountEmail,
        numberOfPasswords: this.selectedPasswordIds_.length,
      },
    }));

    this.$.dialog.close();
  }

  private getUrl_(password: chrome.passwordsPrivate.PasswordUiEntry): string {
    assert(password.affiliatedDomains);
    assert(password.affiliatedDomains.length > 0);
    return password.affiliatedDomains[0].name;
  }

  private passwordSelected_() {
    this.selectedPasswordIds_ =
        Array.from(this.shadowRoot!.querySelectorAll('password-preview-item'))
            .filter(item => item.checked)
            .map(item => item.passwordId);
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'move-passwords-dialog': MovePasswordsDialogElement;
  }
}

customElements.define(
    MovePasswordsDialogElement.is, MovePasswordsDialogElement);