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