// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. cr.define('options', function() { /** @const */ var Page = cr.ui.pageManager.Page; /** @const */ var PageManager = cr.ui.pageManager.PageManager; /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; ///////////////////////////////////////////////////////////////////////////// // PasswordManager class: /** * Encapsulated handling of password and exceptions page. * @constructor * @extends {cr.ui.pageManager.Page} */ function PasswordManager() { this.activeNavTab = null; Page.call(this, 'passwords', loadTimeData.getString('passwordsPageTabTitle'), 'password-manager'); } cr.addSingletonGetter(PasswordManager); PasswordManager.prototype = { __proto__: Page.prototype, /** * The saved passwords list. * @type {options.DeletableItemList} * @private */ savedPasswordsList_: null, /** * The password exceptions list. * @type {options.DeletableItemList} * @private */ passwordExceptionsList_: null, /** * The timer id of the timer set on search query change events. * @type {number} * @private */ queryDelayTimerId_: 0, /** * The most recent search query, or null if the query is empty. * @type {?string} * @private */ lastQuery_: null, /** @override */ initializePage: function() { Page.prototype.initializePage.call(this); $('auto-signin-block').hidden = !loadTimeData.getBoolean('enableCredentialManagerAPI'); $('password-manager-confirm').onclick = function() { PageManager.closeOverlay(); }; $('password-search-box').addEventListener('search', this.handleSearchQueryChange_.bind(this)); $('exceptions-learn-more').onclick = function() { chrome.send('coreOptionsUserMetricsAction', ['Options_PasswordManagerExceptionsLearnMore']); return true; // Always follow the href }; this.createSavedPasswordsList_(); this.createPasswordExceptionsList_(); }, /** @override */ canShowPage: function() { return !(cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()); }, /** @override */ didShowPage: function() { // Updating the password lists may cause a blocking platform dialog pop up // (Mac, Linux), so we delay this operation until the page is shown. chrome.send('updatePasswordLists'); $('password-search-box').focus(); }, /** * Creates, decorates and initializes the saved passwords list. * @private */ createSavedPasswordsList_: function() { var savedPasswordsList = $('saved-passwords-list'); options.passwordManager.PasswordsList.decorate(savedPasswordsList); this.savedPasswordsList_ = assertInstanceof(savedPasswordsList, options.DeletableItemList); this.savedPasswordsList_.autoExpands = true; }, /** * Creates, decorates and initializes the password exceptions list. * @private */ createPasswordExceptionsList_: function() { var passwordExceptionsList = $('password-exceptions-list'); options.passwordManager.PasswordExceptionsList.decorate( passwordExceptionsList); this.passwordExceptionsList_ = assertInstanceof(passwordExceptionsList, options.DeletableItemList); this.passwordExceptionsList_.autoExpands = true; }, /** * Handles search query changes. * @param {!Event} e The event object. * @private */ handleSearchQueryChange_: function(e) { if (this.queryDelayTimerId_) window.clearTimeout(this.queryDelayTimerId_); // Searching cookies uses a timeout of 500ms. We use a shorter timeout // because there are probably fewer passwords and we want the UI to be // snappier since users will expect that it's "less work." this.queryDelayTimerId_ = window.setTimeout( this.searchPasswords_.bind(this), 250); chrome.send('coreOptionsUserMetricsAction', ['Options_PasswordManagerSearch']); }, /** * Search passwords using text in |password-search-box|. * @private */ searchPasswords_: function() { this.queryDelayTimerId_ = 0; var filter = $('password-search-box').value; filter = (filter == '') ? null : filter; if (this.lastQuery_ != filter) { this.lastQuery_ = filter; // Searching for passwords has the side effect of requerying the // underlying password store. This is done intentionally, as on OS X and // Linux they can change from outside and we won't be notified of it. chrome.send('updatePasswordLists'); } }, /** * Updates the visibility of the list and empty list placeholder. * @param {!cr.ui.List} list The list to toggle visilibility for. */ updateListVisibility_: function(list) { var empty = list.dataModel.length == 0; var listPlaceHolderID = list.id + '-empty-placeholder'; list.hidden = empty; $(listPlaceHolderID).hidden = !empty; }, /** * Updates the data model for the saved passwords list with the values from * |entries|. * @param {!Array} entries The list of saved password data. */ setSavedPasswordsList_: function(entries) { if (this.lastQuery_) { // Implement password searching here in javascript, rather than in C++. // The number of saved passwords shouldn't be too big for us to handle. var query = this.lastQuery_; var filter = function(entry, index, list) { // Search both shown URL and username. var shownUrl = entry[options.passwordManager.SHOWN_URL_FIELD]; var username = entry[options.passwordManager.USERNAME_FIELD]; if (shownUrl.toLowerCase().indexOf(query.toLowerCase()) >= 0 || username.toLowerCase().indexOf(query.toLowerCase()) >= 0) { // Keep the original index so we can delete correctly. See also // deleteItemAtIndex() in password_manager_list.js that uses this. entry[options.passwordManager.ORIGINAL_INDEX_FIELD] = index; return true; } return false; }; entries = entries.filter(filter); } this.savedPasswordsList_.dataModel = new ArrayDataModel(entries); this.updateListVisibility_(this.savedPasswordsList_); }, /** * Updates the data model for the password exceptions list with the values * from |entries|. * @param {!Array} entries The list of password exception data. */ setPasswordExceptionsList_: function(entries) { this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries); this.updateListVisibility_(this.passwordExceptionsList_); }, /** * Reveals the password for a saved password entry. This is called by the * backend after it has authenticated the user. * @param {number} index The original index of the entry in the model. * @param {string} password The saved password. */ showPassword_: function(index, password) { var model = this.savedPasswordsList_.dataModel; if (this.lastQuery_) { // When a filter is active, |index| does not represent the current // index in the model, but each entry stores its original index, so // we can find the item using a linear search. for (var i = 0; i < model.length; ++i) { if (model.item(i)[options.passwordManager.ORIGINAL_INDEX_FIELD] == index) { index = i; break; } } } // Reveal the password in the UI. var item = this.savedPasswordsList_.getListItemByIndex(index); item.showPassword(password); }, }; /** * Removes a saved password. * @param {number} rowIndex indicating the row to remove. */ PasswordManager.removeSavedPassword = function(rowIndex) { chrome.send('removeSavedPassword', [String(rowIndex)]); chrome.send('coreOptionsUserMetricsAction', ['Options_PasswordManagerDeletePassword']); }; /** * Removes a password exception. * @param {number} rowIndex indicating the row to remove. */ PasswordManager.removePasswordException = function(rowIndex) { chrome.send('removePasswordException', [String(rowIndex)]); }; PasswordManager.requestShowPassword = function(index) { chrome.send('requestShowPassword', [index]); }; // Forward public APIs to private implementations on the singleton instance. cr.makePublic(PasswordManager, [ 'setSavedPasswordsList', 'setPasswordExceptionsList', 'showPassword' ]); // Export return { PasswordManager: PasswordManager }; });