Skip to content

Commit

Permalink
[Password Manager] Edit password dialog
Browse files Browse the repository at this point in the history
This change introduces edit password dialog. The dialog is currently
shown only from password details page. This dialog doesn't support
editing credential yet. Show/Hide password button works.

Mock: https://screenshot.googleplex.com/6gPmjqNhKYjCB7d
Impl: https://screenshot.googleplex.com/8ewBRzDBvKxomLN

Bug: 1410744
Change-Id: If871800070dc1f5818634716aad7e8247ad12817
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4199026
Commit-Queue: Viktor Semeniuk <vsemeniuk@google.com>
Reviewed-by: Mohamed Amir Yosef <mamir@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1100331}
  • Loading branch information
Viktor Semeniuk authored and Chromium LUCI CQ committed Feb 2, 2023
1 parent 1f69c58 commit 4e3ffff
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 13 deletions.
7 changes: 6 additions & 1 deletion chrome/app/password_manager_ui_strings.grdp
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@
<message name="IDS_PASSWORD_MANAGER_UI_HELP" desc="Aria label for the help button in the toolbar.">
Help
</message>

<if expr="is_macosx">
<message name="IDS_PASSWORD_MANAGER_UI_BIOMETRIC_AUTHENTICATION_FOR_FILLING_TOGGLE_LABEL_MAC" desc="Label for a toggle that allows users to be authenticate using their TouchID or any other means of authentication when logging into webpages.">
Use your screen lock when filling passwords
Expand All @@ -268,4 +267,10 @@
Use Windows Hello when filling passwords
</message>
</if>
<message name="IDS_PASSWORD_MANAGER_UI_EDIT_PASSWORD" desc="The title for a dialog, which allows a user to edit existing password entry. This dialog includes other details, such as username and website.">
Edit password
</message>
<message name="IDS_PASSWORD_MANAGER_UI_PASSWORD_EDIT_FOOTNOTE" desc="A footnote for the password edit dialog.">
Make sure the password you are saving matches your password for <ph name="WEBSITE">$1<ex>airbnb.com</ex></ph>
</message>
</grit-part>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d1179cf635f57aa899f570d7e77f0e4883b0c764
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d1179cf635f57aa899f570d7e77f0e4883b0c764
1 change: 1 addition & 0 deletions chrome/browser/resources/password_manager/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ build_webui("build") {
"checkup_details_section.ts",
"checkup_list_item.ts",
"dialogs/add_password_dialog.ts",
"dialogs/edit_password_dialog.ts",
"dialogs/passwords_export_dialog.ts",
"password_details_card.ts",
"password_details_section.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<style include="shared-style cr-shared-style">
cr-input:not(:first-of-type) {
margin-top: var(--cr-form-field-bottom-spacing);
}

cr-icon-button {
--cr-icon-button-icon-size: 16px;
--cr-icon-button-size: 32px;
--cr-icon-button-margin-start: 0;
--cr-icon-button-margin-end: 0;
}

cr-input {
--cr-input-error-display: none;
}

cr-textarea {
--settings-textarea-footer-display: flex;
}

#usernameInput,
#passwordNote {
margin-top: var(--cr-form-field-bottom-spacing);
}

#footnote {
margin-inline-start: 2px;
margin-top: 16px;
}
</style>
<cr-dialog id="dialog" show-on-attach>
<h1 slot="title" id="title" class="dialog-title">$i18n{editPasswordTitle}</h1>
<div slot="body">
<div class="cr-form-field-label">$i18n{sitesLabel}</div>
<template id="links" is="dom-repeat"
items="[[password.affiliatedDomains]]">
<div class="elide-left">
<a href="[[item.url]]" class="site-link" target="_blank">
[[item.name]]
</a>
</div>
</template>
<!-- TODO(crbug.com/1410744): Show error when username already exists -->
<!-- TODO(crbug.com/1410744): Show a link to existing password -->
<cr-input id="usernameInput" label="$i18n{usernameLabel}"
value="[[password.username]]">
</cr-input>
<!-- TODO(crbug.com/1410744): Show error message if password is empty -->
<cr-input id="passwordInput" label="$i18n{passwordLabel}" required
type="[[getPasswordInputType(isPasswordVisible)]]"
value="[[password.password]]">
<cr-icon-button id="showPasswordButton" slot="inline-suffix"
class$="[[getShowHideButtonIconClass(isPasswordVisible)]]"
title="[[getShowHideButtonLabel(isPasswordVisible)]]"
on-click="onShowHidePasswordButtonClick">
</cr-icon-button>
</cr-input>
<div id="footnote">
[[getFootnote_(password)]]
</div>
<cr-textarea label="$i18n{notesLabel}" id="passwordNote"
value="[[password.note]]" autogrow>
</cr-textarea>
</div>
<div slot="button-container">
<cr-button id="cancelButton" class="cancel-button" on-click="onCancel_">
$i18n{cancel}
</cr-button>
<!-- TODO(crbug.com/1410744): Support editing. -->
<cr-button id="saveButton" class="action-button" disabled>
$i18n{save}
</cr-button>
</div>
</cr-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/cr_elements/cr_textarea/cr_textarea.js';
import 'chrome://resources/cr_elements/cr_icons.css.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import '../shared_style.css.js';

import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
import {CrIconButtonElement} from 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
import {assert} from 'chrome://resources/js/assert_ts.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {ShowPasswordMixin} from '../show_password_mixin.js';

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

export interface EditPasswordDialogElement {
$: {
dialog: CrDialogElement,
saveButton: CrButtonElement,
cancelButton: CrButtonElement,
usernameInput: CrInputElement,
passwordInput: CrInputElement,
showPasswordButton: CrIconButtonElement,
};
}

const EditPasswordDialogElementBase =
ShowPasswordMixin(I18nMixin(PolymerElement));

export class EditPasswordDialogElement extends EditPasswordDialogElementBase {
static get is() {
return 'edit-password-dialog';
}

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

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

password: chrome.passwordsPrivate.PasswordUiEntry;

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

private getFootnote_(): string {
assert(this.password.affiliatedDomains);
return this.i18n(
'editPasswordFootnote', this.password.affiliatedDomains[0]?.name ?? '');
}
}

declare global {
interface HTMLElementTagNameMap {
'edit-password-dialog': EditPasswordDialogElement;
}
}

customElements.define(EditPasswordDialogElement.is, EditPasswordDialogElement);
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,8 @@
--cr-icon-button-margin-end: 0;
}

.site-link {
color: var(--cr-primary-text-color);
display: block;
height: 20px;
a.site-link {
max-width: 324px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.cr-form-field-label {
Expand Down Expand Up @@ -110,8 +104,8 @@
</div>
</div>
<div class="button-container">
<!-- TODO(crbug.com/1350947): Support editing. -->
<cr-button id="editButton" hidden="[[isFederated_(password)]]">
<cr-button id="editButton" hidden="[[isFederated_(password)]]"
on-click="onEditClicked_">
$i18n{editPassword}
</cr-button>
<!-- TODO(crbug.com/1350947): Support deleting. -->
Expand All @@ -123,3 +117,8 @@
<cr-toast id="toast" duration="5000">
<span>[[toastMessage_]]</span>
</cr-toast>
<template is="dom-if" if="[[showEditPasswordDialog_]]" restamp>
<edit-password-dialog on-close="onEditPasswordDialogClosed_"
id="editPasswordDialog" password="[[password]]">
</edit-password-dialog>
</template>
11 changes: 11 additions & 0 deletions chrome/browser/resources/password_manager/password_details_card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'chrome://resources/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/cr_elements/cr_shared_style.css.js';
import 'chrome://resources/cr_elements/cr_toast/cr_toast.js';
import './shared_style.css.js';
import './dialogs/edit_password_dialog.js';

import {CrToastElement} from '//resources/cr_elements/cr_toast/cr_toast.js';
import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
Expand Down Expand Up @@ -49,11 +50,13 @@ export class PasswordDetailsCardElement extends PasswordDetailsCardElementBase {
return {
password: Object,
toastMessage_: String,
showEditPasswordDialog_: Boolean,
};
}

password: chrome.passwordsPrivate.PasswordUiEntry;
private toastMessage_: string;
private showEditPasswordDialog_: boolean;

private isFederated_(): boolean {
return !!this.password.federationText;
Expand Down Expand Up @@ -90,6 +93,14 @@ export class PasswordDetailsCardElement extends PasswordDetailsCardElementBase {
this.toastMessage_ = message;
this.$.toast.show();
}

private onEditClicked_() {
this.showEditPasswordDialog_ = true;
}

private onEditPasswordDialogClosed_() {
this.showEditPasswordDialog_ = false;
}
}

declare global {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {CrExpandButtonElement} from 'chrome://resources/cr_elements/cr_expand_bu
export {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.js';
export {OpenWindowProxyImpl} from 'chrome://resources/js/open_window_proxy.js';
export {AddPasswordDialogElement} from './dialogs/add_password_dialog.js';
export {EditPasswordDialogElement} from './dialogs/edit_password_dialog.js';
export {PasswordsExportDialogElement} from './dialogs/passwords_export_dialog.js';
export {PasswordDetailsCardElement} from './password_details_card.js';
export {PasswordDetailsSectionElement} from './password_details_section.js';
Expand Down
9 changes: 9 additions & 0 deletions chrome/browser/resources/password_manager/shared_style.css
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,12 @@
cr-link-row[hide-icon]::part(icon) {
display: none;
}

.site-link {
color: var(--cr-primary-text-color);
display: block;
height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ content::WebUIDataSource* CreateAndAddPasswordsUIHTMLSource(
{"deletePassword", IDS_DELETE},
{"downloadFile", IDS_PASSWORD_MANAGER_UI_DOWNLOAD_FILE},
{"editPassword", IDS_EDIT},
{"editPasswordFootnote", IDS_PASSWORD_MANAGER_UI_PASSWORD_EDIT_FOOTNOTE},
{"editPasswordTitle", IDS_PASSWORD_MANAGER_UI_EDIT_PASSWORD},
{"emptyNote", IDS_PASSWORD_MANAGER_UI_NO_NOTE_SAVED},
{"exportPasswords", IDS_PASSWORD_MANAGER_UI_EXPORT_TITLE},
{"exportPasswordsDescription",
Expand Down
1 change: 1 addition & 0 deletions chrome/test/data/webui/password_manager/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ build_webui_tests("build") {
files = [
"checkup_details_section_test.ts",
"checkup_section_test.ts",
"edit_password_dialog_test.ts",
"password_details_card_test.ts",
"password_details_section_test.ts",
"password_manager_app_test.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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://password-manager/password_manager.js';

import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';

import {createPasswordEntry} from './test_util.js';

suite('EditPasswordDialogTest', function() {
setup(function() {
document.body.innerHTML = window.trustedTypes!.emptyHTML;
return flushTasks();
});

test('password displayed correctly', async function() {
const password =
createPasswordEntry({id: 0, username: 'user1', password: 'sTr0nGp@@s'});
password.affiliatedDomains = [
{name: 'test.com', url: 'https://test.com/'},
{name: 'Test App', url: 'https://m.test.com/'},
];

const dialog = document.createElement('edit-password-dialog');
dialog.password = password;
document.body.appendChild(dialog);
await flushTasks();

assertEquals(password.username, dialog.$.usernameInput.value);
assertEquals(password.password, dialog.$.passwordInput.value);
assertEquals('password', dialog.$.passwordInput.type);

const listItemElements =
dialog.shadowRoot!.querySelectorAll<HTMLAnchorElement>('a.site-link');
assertEquals(listItemElements.length, password.affiliatedDomains.length);

password.affiliatedDomains.forEach((expectedDomain, i) => {
const listItemElement = listItemElements[i];

assertTrue(!!listItemElement);
assertEquals(expectedDomain.name, listItemElement.textContent!.trim());
assertEquals(expectedDomain.url, listItemElement.href);
});
});

test('show/hide password', async function() {
const password = createPasswordEntry(
{id: 1, url: 'test.com', username: 'vik', password: 'password69'});
password.affiliatedDomains = [{name: 'test.com', url: 'https://test.com/'}];
const dialog = document.createElement('edit-password-dialog');
dialog.password = password;
document.body.appendChild(dialog);
await flushTasks();

assertEquals(
loadTimeData.getString('showPassword'),
dialog.$.showPasswordButton.title);
assertEquals('password', dialog.$.passwordInput.type);
assertTrue(dialog.$.showPasswordButton.hasAttribute('class'));
assertEquals(
'icon-visibility', dialog.$.showPasswordButton.getAttribute('class'));

dialog.$.showPasswordButton.click();

assertEquals(
loadTimeData.getString('hidePassword'),
dialog.$.showPasswordButton.title);
assertEquals('text', dialog.$.passwordInput.type);
assertTrue(dialog.$.showPasswordButton.hasAttribute('class'));
assertEquals(
'icon-visibility-off',
dialog.$.showPasswordButton.getAttribute('class'));
});
});

0 comments on commit 4e3ffff

Please sign in to comment.