1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5cr.define('options', function() { 6 /** @const */ var OptionsPage = options.OptionsPage; 7 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; 8 9 ///////////////////////////////////////////////////////////////////////////// 10 // PasswordManager class: 11 12 /** 13 * Encapsulated handling of password and exceptions page. 14 * @constructor 15 */ 16 function PasswordManager() { 17 this.activeNavTab = null; 18 OptionsPage.call(this, 19 'passwords', 20 loadTimeData.getString('passwordsPageTabTitle'), 21 'password-manager'); 22 } 23 24 cr.addSingletonGetter(PasswordManager); 25 26 PasswordManager.prototype = { 27 __proto__: OptionsPage.prototype, 28 29 /** 30 * The saved passwords list. 31 * @type {DeletableItemList} 32 * @private 33 */ 34 savedPasswordsList_: null, 35 36 /** 37 * The password exceptions list. 38 * @type {DeletableItemList} 39 * @private 40 */ 41 passwordExceptionsList_: null, 42 43 /** 44 * The timer id of the timer set on search query change events. 45 * @type {number} 46 * @private 47 */ 48 queryDelayTimerId_: 0, 49 50 /** 51 * The most recent search query, or null if the query is empty. 52 * @type {?string} 53 * @private 54 */ 55 lastQuery_: null, 56 57 /** @override */ 58 initializePage: function() { 59 OptionsPage.prototype.initializePage.call(this); 60 61 $('password-manager-confirm').onclick = function() { 62 OptionsPage.closeOverlay(); 63 }; 64 65 $('password-search-box').addEventListener('search', 66 this.handleSearchQueryChange_.bind(this)); 67 68 this.createSavedPasswordsList_(); 69 this.createPasswordExceptionsList_(); 70 }, 71 72 /** @override */ 73 canShowPage: function() { 74 return !(cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()); 75 }, 76 77 /** @override */ 78 didShowPage: function() { 79 // Updating the password lists may cause a blocking platform dialog pop up 80 // (Mac, Linux), so we delay this operation until the page is shown. 81 chrome.send('updatePasswordLists'); 82 $('password-search-box').focus(); 83 }, 84 85 /** 86 * Creates, decorates and initializes the saved passwords list. 87 * @private 88 */ 89 createSavedPasswordsList_: function() { 90 this.savedPasswordsList_ = $('saved-passwords-list'); 91 options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_); 92 this.savedPasswordsList_.autoExpands = true; 93 }, 94 95 /** 96 * Creates, decorates and initializes the password exceptions list. 97 * @private 98 */ 99 createPasswordExceptionsList_: function() { 100 this.passwordExceptionsList_ = $('password-exceptions-list'); 101 options.passwordManager.PasswordExceptionsList.decorate( 102 this.passwordExceptionsList_); 103 this.passwordExceptionsList_.autoExpands = true; 104 }, 105 106 /** 107 * Handles search query changes. 108 * @param {!Event} e The event object. 109 * @private 110 */ 111 handleSearchQueryChange_: function(e) { 112 if (this.queryDelayTimerId_) 113 window.clearTimeout(this.queryDelayTimerId_); 114 115 // Searching cookies uses a timeout of 500ms. We use a shorter timeout 116 // because there are probably fewer passwords and we want the UI to be 117 // snappier since users will expect that it's "less work." 118 this.queryDelayTimerId_ = window.setTimeout( 119 this.searchPasswords_.bind(this), 250); 120 }, 121 122 /** 123 * Search passwords using text in |password-search-box|. 124 * @private 125 */ 126 searchPasswords_: function() { 127 this.queryDelayTimerId_ = 0; 128 var filter = $('password-search-box').value; 129 filter = (filter == '') ? null : filter; 130 if (this.lastQuery_ != filter) { 131 this.lastQuery_ = filter; 132 // Searching for passwords has the side effect of requerying the 133 // underlying password store. This is done intentionally, as on OS X and 134 // Linux they can change from outside and we won't be notified of it. 135 chrome.send('updatePasswordLists'); 136 } 137 }, 138 139 /** 140 * Updates the visibility of the list and empty list placeholder. 141 * @param {!List} list The list to toggle visilibility for. 142 */ 143 updateListVisibility_: function(list) { 144 var empty = list.dataModel.length == 0; 145 var listPlaceHolderID = list.id + '-empty-placeholder'; 146 list.hidden = empty; 147 $(listPlaceHolderID).hidden = !empty; 148 }, 149 150 /** 151 * Updates the data model for the saved passwords list with the values from 152 * |entries|. 153 * @param {Array} entries The list of saved password data. 154 */ 155 setSavedPasswordsList_: function(entries) { 156 if (this.lastQuery_) { 157 // Implement password searching here in javascript, rather than in C++. 158 // The number of saved passwords shouldn't be too big for us to handle. 159 var query = this.lastQuery_; 160 var filter = function(entry, index, list) { 161 // Search both URL and username. 162 if (entry[0].toLowerCase().indexOf(query.toLowerCase()) >= 0 || 163 entry[1].toLowerCase().indexOf(query.toLowerCase()) >= 0) { 164 // Keep the original index so we can delete correctly. See also 165 // deleteItemAtIndex() in password_manager_list.js that uses this. 166 entry[3] = index; 167 return true; 168 } 169 return false; 170 }; 171 entries = entries.filter(filter); 172 } 173 this.savedPasswordsList_.dataModel = new ArrayDataModel(entries); 174 this.updateListVisibility_(this.savedPasswordsList_); 175 }, 176 177 /** 178 * Updates the data model for the password exceptions list with the values 179 * from |entries|. 180 * @param {Array} entries The list of password exception data. 181 */ 182 setPasswordExceptionsList_: function(entries) { 183 this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries); 184 this.updateListVisibility_(this.passwordExceptionsList_); 185 }, 186 187 /** 188 * Reveals the password for a saved password entry. This is called by the 189 * backend after it has authenticated the user. 190 * @param {number} index The original index of the entry in the model. 191 * @param {string} password The saved password. 192 */ 193 showPassword_: function(index, password) { 194 var model = this.savedPasswordsList_.dataModel; 195 if (this.lastQuery_) { 196 // When a filter is active, |index| does not represent the current 197 // index in the model, but each entry stores its original index, so 198 // we can find the item using a linear search. 199 for (var i = 0; i < model.length; ++i) { 200 if (model.item(i)[3] == index) { 201 index = i; 202 break; 203 } 204 } 205 } 206 207 // Reveal the password in the UI. 208 var item = this.savedPasswordsList_.getListItemByIndex(index); 209 item.showPassword(password); 210 }, 211 }; 212 213 /** 214 * Removes a saved password. 215 * @param {number} rowIndex indicating the row to remove. 216 */ 217 PasswordManager.removeSavedPassword = function(rowIndex) { 218 chrome.send('removeSavedPassword', [String(rowIndex)]); 219 }; 220 221 /** 222 * Removes a password exception. 223 * @param {number} rowIndex indicating the row to remove. 224 */ 225 PasswordManager.removePasswordException = function(rowIndex) { 226 chrome.send('removePasswordException', [String(rowIndex)]); 227 }; 228 229 PasswordManager.requestShowPassword = function(index) { 230 chrome.send('requestShowPassword', [index]); 231 }; 232 233 // Forward public APIs to private implementations on the singleton instance. 234 [ 235 'setSavedPasswordsList', 236 'setPasswordExceptionsList', 237 'showPassword' 238 ].forEach(function(name) { 239 PasswordManager[name] = function() { 240 var instance = PasswordManager.getInstance(); 241 return instance[name + '_'].apply(instance, arguments); 242 }; 243 }); 244 245 // Export 246 return { 247 PasswordManager: PasswordManager 248 }; 249 250}); 251