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