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.passwordManager', function() { 6 /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; 7 /** @const */ var DeletableItemList = options.DeletableItemList; 8 /** @const */ var DeletableItem = options.DeletableItem; 9 /** @const */ var List = cr.ui.List; 10 11 /** 12 * Creates a new passwords list item. 13 * @param {ArrayDataModel} dataModel The data model that contains this item. 14 * @param {Array} entry An array of the form [url, username, password]. When 15 * the list has been filtered, a fourth element [index] may be present. 16 * @param {boolean} showPasswords If true, add a button to the element to 17 * allow the user to reveal the saved password. 18 * @constructor 19 * @extends {cr.ui.ListItem} 20 */ 21 function PasswordListItem(dataModel, entry, showPasswords) { 22 var el = cr.doc.createElement('div'); 23 el.dataItem = entry; 24 el.dataModel = dataModel; 25 el.__proto__ = PasswordListItem.prototype; 26 el.decorate(showPasswords); 27 28 return el; 29 } 30 31 PasswordListItem.prototype = { 32 __proto__: DeletableItem.prototype, 33 34 /** @override */ 35 decorate: function(showPasswords) { 36 DeletableItem.prototype.decorate.call(this); 37 38 // The URL of the site. 39 var urlLabel = this.ownerDocument.createElement('div'); 40 urlLabel.classList.add('favicon-cell'); 41 urlLabel.classList.add('weakrtl'); 42 urlLabel.classList.add('url'); 43 urlLabel.setAttribute('title', this.url); 44 urlLabel.textContent = this.url; 45 46 // The favicon URL is prefixed with "origin/", which essentially removes 47 // the URL path past the top-level domain and ensures that a scheme (e.g., 48 // http) is being used. This ensures that the favicon returned is the 49 // default favicon for the domain and that the URL has a scheme if none 50 // is present in the password manager. 51 urlLabel.style.backgroundImage = getFaviconImageSet( 52 'origin/' + this.url, 16); 53 this.contentElement.appendChild(urlLabel); 54 55 // The stored username. 56 var usernameLabel = this.ownerDocument.createElement('div'); 57 usernameLabel.className = 'name'; 58 usernameLabel.textContent = this.username; 59 usernameLabel.title = this.username; 60 this.contentElement.appendChild(usernameLabel); 61 62 // The stored password. 63 var passwordInputDiv = this.ownerDocument.createElement('div'); 64 passwordInputDiv.className = 'password'; 65 66 // The password input field. 67 var passwordInput = this.ownerDocument.createElement('input'); 68 passwordInput.type = 'password'; 69 passwordInput.className = 'inactive-password'; 70 passwordInput.readOnly = true; 71 passwordInput.value = showPasswords ? this.password : '********'; 72 passwordInputDiv.appendChild(passwordInput); 73 this.passwordField = passwordInput; 74 75 // The show/hide button. 76 if (showPasswords) { 77 var button = this.ownerDocument.createElement('button'); 78 button.hidden = true; 79 button.className = 'list-inline-button custom-appearance'; 80 button.textContent = loadTimeData.getString('passwordShowButton'); 81 button.addEventListener('click', this.onClick_.bind(this), true); 82 button.addEventListener('mousedown', function(event) { 83 // Don't focus on this button by mousedown. 84 event.preventDefault(); 85 // Don't handle list item selection. It causes focus change. 86 event.stopPropagation(); 87 }, false); 88 passwordInputDiv.appendChild(button); 89 this.passwordShowButton = button; 90 } 91 92 this.contentElement.appendChild(passwordInputDiv); 93 }, 94 95 /** @override */ 96 selectionChanged: function() { 97 var input = this.passwordField; 98 var button = this.passwordShowButton; 99 // The button doesn't exist when passwords can't be shown. 100 if (!button) 101 return; 102 103 if (this.selected) { 104 input.classList.remove('inactive-password'); 105 button.hidden = false; 106 } else { 107 input.classList.add('inactive-password'); 108 button.hidden = true; 109 } 110 }, 111 112 /** 113 * Reveals the plain text password of this entry. 114 */ 115 showPassword: function(password) { 116 this.passwordField.value = password; 117 this.passwordField.type = 'text'; 118 119 var button = this.passwordShowButton; 120 if (button) 121 button.textContent = loadTimeData.getString('passwordHideButton'); 122 }, 123 124 /** 125 * Hides the plain text password of this entry. 126 */ 127 hidePassword: function() { 128 this.passwordField.type = 'password'; 129 130 var button = this.passwordShowButton; 131 if (button) 132 button.textContent = loadTimeData.getString('passwordShowButton'); 133 }, 134 135 /** 136 * Get the original index of this item in the data model. 137 * @return {number} The index. 138 * @private 139 */ 140 getOriginalIndex_: function() { 141 var index = this.dataItem[3]; 142 return index ? index : this.dataModel.indexOf(this.dataItem); 143 }, 144 145 /** 146 * On-click event handler. Swaps the type of the input field from password 147 * to text and back. 148 * @private 149 */ 150 onClick_: function(event) { 151 if (this.passwordField.type == 'password') { 152 // After the user is authenticated, showPassword() will be called. 153 PasswordManager.requestShowPassword(this.getOriginalIndex_()); 154 } else { 155 this.hidePassword(); 156 } 157 }, 158 159 /** 160 * Get and set the URL for the entry. 161 * @type {string} 162 */ 163 get url() { 164 return this.dataItem[0]; 165 }, 166 set url(url) { 167 this.dataItem[0] = url; 168 }, 169 170 /** 171 * Get and set the username for the entry. 172 * @type {string} 173 */ 174 get username() { 175 return this.dataItem[1]; 176 }, 177 set username(username) { 178 this.dataItem[1] = username; 179 }, 180 181 /** 182 * Get and set the password for the entry. 183 * @type {string} 184 */ 185 get password() { 186 return this.dataItem[2]; 187 }, 188 set password(password) { 189 this.dataItem[2] = password; 190 }, 191 }; 192 193 /** 194 * Creates a new PasswordExceptions list item. 195 * @param {Array} entry A pair of the form [url, username]. 196 * @constructor 197 * @extends {Deletable.ListItem} 198 */ 199 function PasswordExceptionsListItem(entry) { 200 var el = cr.doc.createElement('div'); 201 el.dataItem = entry; 202 el.__proto__ = PasswordExceptionsListItem.prototype; 203 el.decorate(); 204 205 return el; 206 } 207 208 PasswordExceptionsListItem.prototype = { 209 __proto__: DeletableItem.prototype, 210 211 /** 212 * Call when an element is decorated as a list item. 213 */ 214 decorate: function() { 215 DeletableItem.prototype.decorate.call(this); 216 217 // The URL of the site. 218 var urlLabel = this.ownerDocument.createElement('div'); 219 urlLabel.className = 'url'; 220 urlLabel.classList.add('favicon-cell'); 221 urlLabel.classList.add('weakrtl'); 222 urlLabel.textContent = this.url; 223 224 // The favicon URL is prefixed with "origin/", which essentially removes 225 // the URL path past the top-level domain and ensures that a scheme (e.g., 226 // http) is being used. This ensures that the favicon returned is the 227 // default favicon for the domain and that the URL has a scheme if none 228 // is present in the password manager. 229 urlLabel.style.backgroundImage = getFaviconImageSet( 230 'origin/' + this.url, 16); 231 this.contentElement.appendChild(urlLabel); 232 }, 233 234 /** 235 * Get the url for the entry. 236 * @type {string} 237 */ 238 get url() { 239 return this.dataItem; 240 }, 241 set url(url) { 242 this.dataItem = url; 243 }, 244 }; 245 246 /** 247 * Create a new passwords list. 248 * @constructor 249 * @extends {cr.ui.List} 250 */ 251 var PasswordsList = cr.ui.define('list'); 252 253 PasswordsList.prototype = { 254 __proto__: DeletableItemList.prototype, 255 256 /** 257 * Whether passwords can be revealed or not. 258 * @type {boolean} 259 * @private 260 */ 261 showPasswords_: true, 262 263 /** @override */ 264 decorate: function() { 265 DeletableItemList.prototype.decorate.call(this); 266 Preferences.getInstance().addEventListener( 267 'profile.password_manager_allow_show_passwords', 268 this.onPreferenceChanged_.bind(this)); 269 }, 270 271 /** 272 * Listener for changes on the preference. 273 * @param {Event} event The preference update event. 274 * @private 275 */ 276 onPreferenceChanged_: function(event) { 277 this.showPasswords_ = event.value.value; 278 this.redraw(); 279 }, 280 281 /** @override */ 282 createItem: function(entry) { 283 var showPasswords = this.showPasswords_; 284 285 if (loadTimeData.getBoolean('disableShowPasswords')) 286 showPasswords = false; 287 288 return new PasswordListItem(this.dataModel, entry, showPasswords); 289 }, 290 291 /** @override */ 292 deleteItemAtIndex: function(index) { 293 var item = this.dataModel.item(index); 294 if (item && item.length > 3) { 295 // The fourth element, if present, is the original index to delete. 296 index = item[3]; 297 } 298 PasswordManager.removeSavedPassword(index); 299 }, 300 301 /** 302 * The length of the list. 303 */ 304 get length() { 305 return this.dataModel.length; 306 }, 307 }; 308 309 /** 310 * Create a new passwords list. 311 * @constructor 312 * @extends {cr.ui.List} 313 */ 314 var PasswordExceptionsList = cr.ui.define('list'); 315 316 PasswordExceptionsList.prototype = { 317 __proto__: DeletableItemList.prototype, 318 319 /** @override */ 320 createItem: function(entry) { 321 return new PasswordExceptionsListItem(entry); 322 }, 323 324 /** @override */ 325 deleteItemAtIndex: function(index) { 326 PasswordManager.removePasswordException(index); 327 }, 328 329 /** 330 * The length of the list. 331 */ 332 get length() { 333 return this.dataModel.length; 334 }, 335 }; 336 337 return { 338 PasswordListItem: PasswordListItem, 339 PasswordExceptionsListItem: PasswordExceptionsListItem, 340 PasswordsList: PasswordsList, 341 PasswordExceptionsList: PasswordExceptionsList, 342 }; 343}); 344