Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4140)

Unified Diff: chrome/browser/resources/options/password_manager_list.js

Issue 489103004: Allow editing passwords in settings/passwords (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixed a type mismatch in password_manager.js Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/options/password_manager_list.js
diff --git a/chrome/browser/resources/options/password_manager_list.js b/chrome/browser/resources/options/password_manager_list.js
index 78c9912b629381f11b1170e1d39995d8adb51f2f..c1b62de260ae7b9a539db59c148190577dd4a3e7 100644
--- a/chrome/browser/resources/options/password_manager_list.js
+++ b/chrome/browser/resources/options/password_manager_list.js
@@ -6,6 +6,8 @@ cr.define('options.passwordManager', function() {
/** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
/** @const */ var DeletableItemList = options.DeletableItemList;
/** @const */ var DeletableItem = options.DeletableItem;
+ /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
+ /** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var List = cr.ui.List;
/**
@@ -17,7 +19,7 @@ cr.define('options.passwordManager', function() {
* @param {boolean} showPasswords If true, add a button to the element to
* allow the user to reveal the saved password.
* @constructor
- * @extends {options.DeletableItem}
+ * @extends {options.InlineEditableItem}
*/
function PasswordListItem(dataModel, entry, showPasswords) {
var el = cr.doc.createElement('div');
@@ -30,110 +32,248 @@ cr.define('options.passwordManager', function() {
}
PasswordListItem.prototype = {
- __proto__: DeletableItem.prototype,
+ __proto__: InlineEditableItem.prototype,
/** @override */
decorate: function(showPasswords) {
- DeletableItem.prototype.decorate.call(this);
+ InlineEditableItem.prototype.decorate.call(this);
- // The URL of the site.
- var urlLabel = this.ownerDocument.createElement('div');
- urlLabel.classList.add('favicon-cell');
- urlLabel.classList.add('weakrtl');
- urlLabel.classList.add('url');
- urlLabel.setAttribute('title', this.url);
- urlLabel.textContent = this.url;
+ this.isPlaceholder = !this.url;
- // The favicon URL is prefixed with "origin/", which essentially removes
- // the URL path past the top-level domain and ensures that a scheme (e.g.,
- // http) is being used. This ensures that the favicon returned is the
- // default favicon for the domain and that the URL has a scheme if none
- // is present in the password manager.
- urlLabel.style.backgroundImage = getFaviconImageSet(
- 'origin/' + this.url, 16);
- this.contentElement.appendChild(urlLabel);
-
- // The stored username.
- var usernameLabel = this.ownerDocument.createElement('div');
- usernameLabel.className = 'name';
- usernameLabel.textContent = this.username;
- usernameLabel.title = this.username;
- this.contentElement.appendChild(usernameLabel);
+ this.contentElement.appendChild(this.createUrlElement());
+ this.contentElement.appendChild(this.createUsernameElement());
- // The stored password.
var passwordInputDiv = this.ownerDocument.createElement('div');
passwordInputDiv.className = 'password';
// The password input field.
var passwordInput = this.ownerDocument.createElement('input');
passwordInput.type = 'password';
- passwordInput.className = 'inactive-password';
- passwordInput.readOnly = true;
- passwordInput.value = showPasswords ? this.password : '********';
- passwordInputDiv.appendChild(passwordInput);
+ if (!this.isPlaceholder) {
+ passwordInput.className = 'inactive-password';
+ passwordInput.readOnly = true;
+ passwordInput.value = showPasswords ? this.password : '********';
+ }
this.passwordField = passwordInput;
- // The show/hide button.
- if (showPasswords) {
- var button = this.ownerDocument.createElement('button');
- button.hidden = true;
- button.className = 'list-inline-button custom-appearance';
- button.textContent = loadTimeData.getString('passwordShowButton');
- button.addEventListener('click', this.onClick_.bind(this), true);
- button.addEventListener('mousedown', function(event) {
+ // Makes the password input field editable.
+ this.addEditField(passwordInput, null);
+
+ // Keeps the password validity updated.
+ this.updatePasswordValidity_();
+ passwordInput.addEventListener('input', function(event) {
+ this.updatePasswordValidity_();
+ }.bind(this));
+
+ passwordInputDiv.appendChild(passwordInput);
+
+ // The list-inline buttons.
+ if (!this.isPlaceholder) {
+ // The container of the list-inline buttons.
+ var buttonsContainer = this.ownerDocument.createElement('div');
+ buttonsContainer.className = 'list-inline-buttons-container';
+
+ var mousedownEventHandler = function(event) {
// Don't focus on this button by mousedown.
event.preventDefault();
// Don't handle list item selection. It causes focus change.
event.stopPropagation();
- }, false);
- passwordInputDiv.appendChild(button);
- this.passwordShowButton = button;
+ };
+
+ // The overwrite button.
+ var overwriteButton = this.ownerDocument.createElement('button');
+ overwriteButton.className = 'list-inline-button custom-appearance';
+ overwriteButton.textContent = loadTimeData.getString(
+ 'passwordOverwriteButton');
+ overwriteButton.addEventListener(
+ 'click', this.onClickOverwriteButton_.bind(this), true);
+ overwriteButton.addEventListener(
+ 'mousedown', mousedownEventHandler, false);
+ buttonsContainer.appendChild(overwriteButton);
+ this.passwordOverwriteButton = overwriteButton;
+
+ // The show/hide button.
+ if (showPasswords) {
+ var button = this.ownerDocument.createElement('button');
+ button.className = 'list-inline-button custom-appearance';
+ button.textContent = loadTimeData.getString('passwordShowButton');
+ button.addEventListener(
+ 'click', this.onClickShowButton_.bind(this), true);
+ button.addEventListener('mousedown', mousedownEventHandler, false);
+ buttonsContainer.appendChild(button);
+ this.passwordShowButton = button;
+ }
+
+ passwordInputDiv.appendChild(buttonsContainer);
}
this.contentElement.appendChild(passwordInputDiv);
+
+ // Adds the event listeners for editing.
+ this.addEventListener('canceledit', this.resetInputs);
+ this.addEventListener('commitedit', this.finishEdit);
+ },
+
+ /**
+ * Constructs and returns the URL element for this item.
+ * @return {HTMLElement} The URL element.
+ * @protected
+ */
+ createUrlElement: function() {
+ var urlEl = this.ownerDocument.createElement('div');
+ urlEl.className = 'favicon-cell weakrtl url';
+ urlEl.setAttribute('title', this.url);
+ urlEl.textContent = this.url;
+
+ // The favicon URL is prefixed with "origin/", which essentially removes
+ // the URL path past the top-level domain and ensures that a scheme (e.g.,
+ // http) is being used. This ensures that the favicon returned is the
+ // default favicon for the domain and that the URL has a scheme if none is
+ // present in the password manager.
+ urlEl.style.backgroundImage = getFaviconImageSet(
+ 'origin/' + this.url, 16);
+ return urlEl;
+ },
+
+ /**
+ * Constructs and returns the username element for this item.
+ * @return {HTMLElement} The username element.
+ * @protected
+ */
+ createUsernameElement: function() {
+ var usernameEl = this.ownerDocument.createElement('div');
+ usernameEl.className = 'name';
+ usernameEl.textContent = this.username;
+ usernameEl.title = this.username;
+ return usernameEl;
},
/** @override */
selectionChanged: function() {
- var input = this.passwordField;
- var button = this.passwordShowButton;
- // The button doesn't exist when passwords can't be shown.
- if (!button)
+ InlineEditableItem.prototype.selectionChanged.call(this);
+
+ // Don't set 'inactive-password' class for the placeholder so that it
+ // shows the background and the borders.
+ if (this.isPlaceholder)
return;
- if (this.selected) {
- input.classList.remove('inactive-password');
- button.hidden = false;
- } else {
- input.classList.add('inactive-password');
- button.hidden = true;
- }
+ this.passwordField.classList.toggle('inactive-password', !this.selected);
+ },
+
+ /** @override */
+ get currentInputIsValid() {
+ return !!this.passwordField.value;
},
/**
- * Reveals the plain text password of this entry.
+ * Returns if the password has been edited.
+ * @return {boolean} Whether the password has been edited.
+ * @protected
+ */
+ passwordHasBeenEdited: function() {
+ return this.passwordField.value != this.password || this.overwriting;
+ },
+
+ /** @override */
+ get hasBeenEdited() {
+ return this.passwordHasBeenEdited();
+ },
+
+ /**
+ * Reveals the plain text password of this entry. Never called for the Add
+ * New Entry row.
+ * @param {string} password The plain text password.
*/
showPassword: function(password) {
- this.passwordField.value = password;
+ this.overwriting = false;
+ this.password = password;
+ this.setPasswordFieldValue_(password);
this.passwordField.type = 'text';
+ this.passwordField.readOnly = false;
+ this.passwordOverwriteButton.hidden = true;
var button = this.passwordShowButton;
if (button)
button.textContent = loadTimeData.getString('passwordHideButton');
},
/**
- * Hides the plain text password of this entry.
+ * Hides the plain text password of this entry. Never called for the Add
+ * New Entry row.
+ * @private
*/
- hidePassword: function() {
+ hidePassword_: function() {
this.passwordField.type = 'password';
+ this.passwordField.readOnly = true;
+ this.passwordOverwriteButton.hidden = false;
var button = this.passwordShowButton;
if (button)
button.textContent = loadTimeData.getString('passwordShowButton');
},
/**
+ * Resets the input fields to their original values and states.
+ * @protected
+ */
+ resetInputs: function() {
+ this.finishOverwriting_();
+ this.setPasswordFieldValue_(this.password);
+ },
+
+ /**
+ * Commits the new data to the browser.
+ * @protected
+ */
+ finishEdit: function() {
+ this.password = this.passwordField.value;
+ this.finishOverwriting_();
+ PasswordManager.updatePassword(
+ this.getOriginalIndex_(), this.passwordField.value);
+ },
+
+ /**
+ * Called with the response of the browser, which indicates the validity of
+ * the URL.
+ * @param {string} url The URL.
+ * @param {boolean} valid The validity of the URL.
+ */
+ originValidityCheckComplete: function(url, valid) {
+ assertNotReached();
+ },
+
+ /**
+ * Updates the custom validity of the password input field.
+ * @private
+ */
+ updatePasswordValidity_: function() {
+ this.passwordField.setCustomValidity(this.passwordField.value ?
+ '' : loadTimeData.getString('editPasswordInvalidPasswordTooltip'));
+ },
+
+ /**
+ * Finishes password overwriting.
+ * @private
+ */
+ finishOverwriting_: function() {
+ if (!this.overwriting)
+ return;
+ this.overwriting = false;
+ this.passwordOverwriteButton.hidden = false;
+ this.passwordField.readOnly = true;
+ },
+
+ /**
+ * Sets the value of the password input field.
+ * @param {string} password The new value.
+ * @private
+ */
+ setPasswordFieldValue_: function(password) {
+ this.passwordField.value = password;
+ this.updatePasswordValidity_();
+ },
+
+ /**
* Get the original index of this item in the data model.
* @return {number} The index.
* @private
@@ -144,16 +284,34 @@ cr.define('options.passwordManager', function() {
},
/**
+ * Called when clicking the overwrite button. Allows the user to overwrite
+ * the hidden password.
+ * @param {Event} event The click event.
+ * @private
+ */
+ onClickOverwriteButton_: function(event) {
+ this.overwriting = true;
+ this.passwordOverwriteButton.hidden = true;
+
+ this.setPasswordFieldValue_('');
+ this.passwordField.readOnly = false;
+ this.passwordField.focus();
+ },
+
+ /**
* On-click event handler. Swaps the type of the input field from password
* to text and back.
* @private
*/
- onClick_: function(event) {
+ onClickShowButton_: function(event) {
+ // Prevents committing an edit.
+ this.resetInputs();
+
if (this.passwordField.type == 'password') {
// After the user is authenticated, showPassword() will be called.
PasswordManager.requestShowPassword(this.getOriginalIndex_());
} else {
- this.hidePassword();
+ this.hidePassword_();
}
},
@@ -162,7 +320,7 @@ cr.define('options.passwordManager', function() {
* @type {string}
*/
get url() {
- return this.dataItem[0];
+ return this.dataItem[0] || '';
},
set url(url) {
this.dataItem[0] = url;
@@ -173,7 +331,7 @@ cr.define('options.passwordManager', function() {
* @type {string}
*/
get username() {
- return this.dataItem[1];
+ return this.dataItem[1] || '';
},
set username(username) {
this.dataItem[1] = username;
@@ -184,7 +342,7 @@ cr.define('options.passwordManager', function() {
* @type {string}
*/
get password() {
- return this.dataItem[2];
+ return this.dataItem[2] || '';
},
set password(password) {
this.dataItem[2] = password;
@@ -192,6 +350,125 @@ cr.define('options.passwordManager', function() {
};
/**
+ * Creates a new passwords list item for the Add New Entry row.
+ * @param {ArrayDataModel} dataModel The data model that contains this item.
+ * @constructor
+ * @extends {options.passwordManager.PasswordListItem}
+ */
+ function PasswordAddRowListItem(dataModel) {
+ var el = cr.doc.createElement('div');
+ el.dataItem = [];
+ el.dataModel = dataModel;
+ el.__proto__ = PasswordAddRowListItem.prototype;
+ el.decorate();
+
+ return el;
+ }
+
+ PasswordAddRowListItem.prototype = {
+ __proto__: PasswordListItem.prototype,
+
+ /** @override */
+ decorate: function() {
+ PasswordListItem.prototype.decorate.call(this, false);
+
+ this.urlField.placeholder = loadTimeData.getString(
+ 'newPasswordUrlFieldPlaceholder');
+ this.usernameField.placeholder = loadTimeData.getString(
+ 'newPasswordUsernameFieldPlaceholder');
+ this.passwordField.placeholder = loadTimeData.getString(
+ 'newPasswordPasswordFieldPlaceholder');
+
+ // Sets the validity of the URL initially.
+ this.setUrlValid_(false);
+ },
+
+ /** @override */
+ createUrlElement: function() {
+ var urlEl = this.createEditableTextCell('');
+ urlEl.className += ' favicon-cell weakrtl url';
+
+ var urlField = urlEl.querySelector('input');
+ urlField.addEventListener('input', this.onUrlInput_.bind(this));
+ this.urlField = urlField;
+
+ return urlEl;
+ },
+
+ /** @override */
+ createUsernameElement: function() {
+ var usernameEl = this.createEditableTextCell('');
+ usernameEl.className = 'name';
+
+ this.usernameField = usernameEl.querySelector('input');
+
+ return usernameEl;
+ },
+
+ /** @override */
+ get currentInputIsValid() {
+ return this.urlValidityKnown && this.urlIsValid &&
+ this.passwordField.value;
+ },
+
+ /** @override */
+ get hasBeenEdited() {
+ return this.urlField.value || this.usernameField.value ||
+ this.passwordHasBeenEdited();
+ },
+
+ /** @override */
+ resetInputs: function() {
+ PasswordListItem.prototype.resetInputs.call(this);
+
+ this.urlField.value = '';
+ this.usernameField.value = '';
+
+ this.setUrlValid_(false);
+ },
+
+ /** @override */
+ finishEdit: function() {
+ var newUrl = this.urlField.value;
+ var newUsername = this.usernameField.value;
+ var newPassword = this.passwordField.value;
+ this.resetInputs();
+
+ PasswordManager.addPassword(newUrl, newUsername, newPassword);
+ },
+
+ /** @override */
+ originValidityCheckComplete: function(url, valid) {
+ if (url == this.urlField.value)
+ this.setUrlValid_(valid);
+ },
+
+ /**
+ * Updates whether the URL in the input is valid.
+ * @param {boolean} valid The validity of the URL.
+ * @private
+ */
+ setUrlValid_: function(valid) {
+ this.urlIsValid = valid;
+ this.urlValidityKnown = true;
+ if (this.urlField) {
+ this.urlField.setCustomValidity(valid ?
+ '' : loadTimeData.getString('editPasswordInvalidUrlTooltip'));
+ }
+ },
+
+ /**
+ * Called when inputting to a URL input.
+ * @param {Event} event The input event.
+ * @private
+ */
+ onUrlInput_: function(event) {
+ this.urlValidityKnown = false;
+ PasswordManager.checkOriginValidityForAdding(this.urlField.value);
+ },
+ };
+
+ /**
* Creates a new PasswordExceptions list item.
* @param {Array} entry A pair of the form [url, username].
* @constructor
@@ -217,9 +494,7 @@ cr.define('options.passwordManager', function() {
// The URL of the site.
var urlLabel = this.ownerDocument.createElement('div');
- urlLabel.className = 'url';
- urlLabel.classList.add('favicon-cell');
- urlLabel.classList.add('weakrtl');
+ urlLabel.className = 'url favicon-cell weakrtl';
urlLabel.textContent = this.url;
// The favicon URL is prefixed with "origin/", which essentially removes
@@ -247,12 +522,12 @@ cr.define('options.passwordManager', function() {
/**
* Create a new passwords list.
* @constructor
- * @extends {options.DeletableItemList}
+ * @extends {options.InlineEditableItemList}
*/
var PasswordsList = cr.ui.define('list');
PasswordsList.prototype = {
- __proto__: DeletableItemList.prototype,
+ __proto__: InlineEditableItemList.prototype,
/**
* Whether passwords can be revealed or not.
@@ -263,7 +538,7 @@ cr.define('options.passwordManager', function() {
/** @override */
decorate: function() {
- DeletableItemList.prototype.decorate.call(this);
+ InlineEditableItemList.prototype.decorate.call(this);
Preferences.getInstance().addEventListener(
'profile.password_manager_allow_show_passwords',
this.onPreferenceChanged_.bind(this));
@@ -284,6 +559,9 @@ cr.define('options.passwordManager', function() {
* @param {Array} entry
*/
createItem: function(entry) {
+ if (!entry)
+ return new PasswordAddRowListItem(this.dataModel);
+
var showPasswords = this.showPasswords_;
if (loadTimeData.getBoolean('disableShowPasswords'))
@@ -343,6 +621,7 @@ cr.define('options.passwordManager', function() {
return {
PasswordListItem: PasswordListItem,
+ PasswordAddRowListItem: PasswordAddRowListItem,
PasswordExceptionsListItem: PasswordExceptionsListItem,
PasswordsList: PasswordsList,
PasswordExceptionsList: PasswordExceptionsList,
« no previous file with comments | « chrome/browser/resources/options/password_manager_list.css ('k') | chrome/browser/ui/passwords/password_manager_presenter.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698