| 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,
|
|
|