OLD | NEW |
(Empty) | |
| 1 // Copyright 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 |
| 5 // This file adheres to closure-compiler conventions in order to enable |
| 6 // compilation with ADVANCED_OPTIMIZATIONS. See http://goo.gl/FwOgy |
| 7 // |
| 8 // Installs password management functions on the |__gCrWeb| object. |
| 9 // |
| 10 // Finds all password forms in the current document and extracts |
| 11 // their attributes and elements using the same logic as |
| 12 // third_party/WebKit/Source/WebCore/html/HTMLFormElement.cpp |
| 13 // |
| 14 // Returns a JSON string representing an array of objects, |
| 15 // where each object represents a password form with the discovered |
| 16 // elements and their values. |
| 17 // |
| 18 // The search for password form fields follows the same algorithm |
| 19 // as the WebKit implementation, see http://goo.gl/4hwh6 |
| 20 |
| 21 // Only install the password management functions once. |
| 22 if (__gCrWeb && !__gCrWeb['fillPasswordForm']) { |
| 23 |
| 24 /** |
| 25 * Finds all password forms in the window and returns form data as a JSON |
| 26 * string. |
| 27 * @return {string} Form data as a JSON string. |
| 28 */ |
| 29 __gCrWeb['findPasswordForms'] = function() { |
| 30 var formDataList = []; |
| 31 if (__gCrWeb.hasPasswordField()) { |
| 32 __gCrWeb.getPasswordFormDataList(formDataList, window); |
| 33 } |
| 34 return __gCrWeb.stringify(formDataList); |
| 35 }; |
| 36 |
| 37 /** |
| 38 * Returns the password form with the given |name| as a JSON string. |
| 39 * @param {string} name The name of the form to extract. |
| 40 * @return {string} The password form. |
| 41 */ |
| 42 __gCrWeb['getPasswordForm'] = function(name) { |
| 43 var el = __gCrWeb.common.getFormElementFromIdentifier(name); |
| 44 if (!el) |
| 45 return 'noPasswordsFound'; |
| 46 var formData = __gCrWeb.getPasswordFormData(el); |
| 47 if (!formData) |
| 48 return 'noPasswordsFound'; |
| 49 return __gCrWeb.stringify(formData); |
| 50 }; |
| 51 |
| 52 /** |
| 53 * Returns an array of forms on the page that match the structure described by |
| 54 * |formData|. The form matching logic follows that in |
| 55 * chrome/renderer/autofill/password_autofill_manager.h. |
| 56 * @param {Object} formData Form data. |
| 57 * @param {Object} doc A document containing formData. |
| 58 * @param {string=} opt_normalizedAction The action URL to compare to. |
| 59 * @return {Array.<Element>} Array of forms found. |
| 60 */ |
| 61 __gCrWeb.findMatchingPasswordForms = function(formData, doc, |
| 62 opt_normalizedAction) { |
| 63 var forms = doc.forms; |
| 64 var fields = formData['fields']; |
| 65 var matching = []; |
| 66 for (var i = 0; i < forms.length; i++) { |
| 67 var form = forms[i]; |
| 68 var normalizedFormAction = opt_normalizedAction || |
| 69 __gCrWeb.common.removeQueryAndReferenceFromURL( |
| 70 __gCrWeb.common.absoluteURL(doc, form.action)); |
| 71 if (formData.action != normalizedFormAction) { |
| 72 continue; |
| 73 } |
| 74 |
| 75 // We need to find all input fields matching |formData| in this form, |
| 76 // otherwise it is the wrong form. |
| 77 var inputs = form.getElementsByTagName('input'); |
| 78 var foundAllFields = true; |
| 79 for (var fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) { |
| 80 var name = fields[fieldIndex]['name']; |
| 81 var value = fields[fieldIndex]['value']; |
| 82 // The first field in |formData| is always the username field, |
| 83 // the second is always the password field. |
| 84 var findingUsername = fieldIndex == 0; |
| 85 var findingPassword = fieldIndex == 1; |
| 86 var foundField = false; |
| 87 for (var k = 0; k < inputs.length; k++) { |
| 88 var input = inputs[k]; |
| 89 |
| 90 // Ensure that the field is the right type. |
| 91 if (findingPassword && input.type != 'password') { |
| 92 continue; |
| 93 } |
| 94 if (!findingPassword && (input.type == 'password' || |
| 95 !__gCrWeb.common.isTextField(input))) { |
| 96 continue; |
| 97 } |
| 98 |
| 99 // Skip read-only fields without a value since they cannot be filled. |
| 100 if (input.readOnly && input.value == '') { |
| 101 continue; |
| 102 } |
| 103 |
| 104 // If more than one match is made, then we have an ambiguity (due to |
| 105 // misuse of 'name' attribute) and the form is considered a mismatch. |
| 106 if (input.name == name) { |
| 107 if (foundField) { |
| 108 foundField = false; |
| 109 break; |
| 110 } |
| 111 foundField = true; |
| 112 } |
| 113 } |
| 114 |
| 115 if (!foundField) { |
| 116 foundAllFields = false; |
| 117 break; |
| 118 } |
| 119 } |
| 120 |
| 121 if (foundAllFields) { |
| 122 matching.push(form); |
| 123 } |
| 124 } |
| 125 return matching; |
| 126 }; |
| 127 |
| 128 /** |
| 129 * Clears autofilled credentials in the form with the specified name. |
| 130 * @param {string} formName The name of the form to clear. |
| 131 * @return {boolean} Whether the form was successfully cleared. |
| 132 */ |
| 133 __gCrWeb['clearAutofilledPasswords'] = function(formName) { |
| 134 var el = __gCrWeb.common.getFormElementFromIdentifier(formName); |
| 135 if (!el) |
| 136 return false; |
| 137 var formData = __gCrWeb.getPasswordFormData(el); |
| 138 if (!formData) |
| 139 return false; |
| 140 var usernameElement = |
| 141 __gCrWeb.getElementByNameWithParent(el, formData.usernameElement); |
| 142 __gCrWeb.setAutofilled(usernameElement, false); |
| 143 formData.passwords.forEach(function(password) { |
| 144 var passwordElement = |
| 145 __gCrWeb.getElementByNameWithParent(el, password.element); |
| 146 if (__gCrWeb.isAutofilled(passwordElement)) { |
| 147 __gCrWeb.setAutofilled(passwordElement, false); |
| 148 passwordElement.value = ''; |
| 149 } |
| 150 }); |
| 151 return true; |
| 152 }; |
| 153 |
| 154 /** |
| 155 * Finds the form described by |formData| and fills in the |
| 156 * username and password values. |
| 157 * |
| 158 * This is a public function invoked by Chrome. There is no information |
| 159 * passed to this function that the page does not have access to anyway. |
| 160 * |
| 161 * @param {!Object.<string, *>} formData Dictionary of parameters, |
| 162 * including: |
| 163 * 'action': <string> The form action URL; |
| 164 * 'fields': {Array.{Object.<string, string>}} Field name/value pairs; |
| 165 * @param {string} username The username to fill. |
| 166 * @param {string} password The password to fill. |
| 167 * @param {string=} opt_normalizedOrigin The origin URL to compare to. |
| 168 * @return {boolean} Whether a form field has been filled. |
| 169 */ |
| 170 __gCrWeb['fillPasswordForm'] = function(formData, username, password, |
| 171 opt_normalizedOrigin) { |
| 172 return __gCrWeb.fillPasswordFormWithData( |
| 173 formData, username, password, window, opt_normalizedOrigin); |
| 174 }; |
| 175 |
| 176 /** |
| 177 * Returns the element with the specified name that is a child of the |
| 178 * specified parent element. |
| 179 * @param {Element} parent The parent of the desired element. |
| 180 * @param {string} name The name of the desired element. |
| 181 * @return {Element} The element if found, otherwise null; |
| 182 */ |
| 183 __gCrWeb['getElementByNameWithParent'] = function(parent, name) { |
| 184 if (parent.name === name) { |
| 185 return parent; |
| 186 } |
| 187 for (var i = 0; i < parent.children.length; i++) { |
| 188 var el = __gCrWeb.getElementByNameWithParent(parent.children[i], name); |
| 189 if (el) { |
| 190 return el; |
| 191 } |
| 192 } |
| 193 return null; |
| 194 }; |
| 195 |
| 196 /** |
| 197 * Given a description of a form (origin, action and input fields), |
| 198 * finds that form on the page and fills in the specified username |
| 199 * and password. |
| 200 * |
| 201 * @param {Object} formData Form data. |
| 202 * @param {string} username The username to fill. |
| 203 * @param {string} password The password to fill. |
| 204 * @param {Object} win A window or a frame containing formData. |
| 205 * @param {string=} opt_normalizedOrigin The origin URL to compare to. |
| 206 * @return {boolean} Whether a form field has been filled. |
| 207 */ |
| 208 __gCrWeb.fillPasswordFormWithData = |
| 209 function(formData, username, password, win, opt_normalizedOrigin) { |
| 210 var doc = win.document; |
| 211 |
| 212 // If unable to read the 'document' property from a frame in a different |
| 213 // origin, do nothing. |
| 214 if (!doc) { |
| 215 return false; |
| 216 } |
| 217 |
| 218 var origin = formData['origin']; |
| 219 var normalizedOrigin = opt_normalizedOrigin || |
| 220 __gCrWeb.common.removeQueryAndReferenceFromURL(win.location.href); |
| 221 if (origin != normalizedOrigin) { |
| 222 return false; |
| 223 } |
| 224 |
| 225 var filled = false; |
| 226 |
| 227 __gCrWeb.findMatchingPasswordForms(formData, doc, opt_normalizedOrigin). |
| 228 forEach(function(form) { |
| 229 var usernameInput = |
| 230 __gCrWeb.getElementByNameWithParent(form, formData.fields[0].name); |
| 231 var passwordInput = |
| 232 __gCrWeb.getElementByNameWithParent(form, formData.fields[1].name); |
| 233 if (!usernameInput.disabled && !passwordInput.disabled) { |
| 234 // If username was provided on a read-only field and it matches the |
| 235 // requested username, fill the form. |
| 236 if (usernameInput.readOnly && usernameInput.value) { |
| 237 if (usernameInput.value == username) { |
| 238 passwordInput.value = password; |
| 239 __gCrWeb.setAutofilled(passwordInput, true); |
| 240 filled = true; |
| 241 } |
| 242 } else { |
| 243 usernameInput.value = username; |
| 244 passwordInput.value = password; |
| 245 __gCrWeb.setAutofilled(passwordInput, true); |
| 246 __gCrWeb.setAutofilled(usernameInput, true); |
| 247 filled = true; |
| 248 } |
| 249 } |
| 250 }); |
| 251 |
| 252 // Recursively invoke for all frames/iframes. |
| 253 var frames = win.frames; |
| 254 for (var i = 0; i < frames.length; i++) { |
| 255 if (__gCrWeb.fillPasswordFormWithData( |
| 256 formData, username, password, frames[i], opt_normalizedOrigin)) { |
| 257 filled = true; |
| 258 } |
| 259 } |
| 260 |
| 261 return filled; |
| 262 }; |
| 263 |
| 264 /** |
| 265 * Returns true if the supplied field |inputElement| was autofilled. |
| 266 * @param {Element} inputElement The form field for which we need to |
| 267 * acquire the autofilled indicator. |
| 268 * @return {boolean} Whether inputElement was autofilled. |
| 269 */ |
| 270 __gCrWeb.isAutofilled = function(inputElement) { |
| 271 return inputElement['__gCrWebAutofilled']; |
| 272 }; |
| 273 |
| 274 /** |
| 275 * Marks the supplied field as autofilled or not depending on the |
| 276 * |value|. |
| 277 * @param {Element} inputElement The form field for which the indicator |
| 278 * needs to be set. |
| 279 * @param {boolean} value The new value of the indicator. |
| 280 */ |
| 281 __gCrWeb.setAutofilled = function(inputElement, value) { |
| 282 inputElement['__gCrWebAutofilled'] = value; |
| 283 }; |
| 284 |
| 285 /** |
| 286 * Selects text starting from |selectFrom| in the specified field. |
| 287 * @param {string} formName The name of the form to select in. |
| 288 * @param {string} fieldName The name of the field to select in. |
| 289 * @param {number} selectFrom The starting index for selection. |
| 290 * @return {boolean} Whether the operation was successful. |
| 291 */ |
| 292 __gCrWeb['selectText'] = function(formName, fieldName, selectFrom) { |
| 293 var form = __gCrWeb.common.getFormElementFromIdentifier(formName); |
| 294 var el = __gCrWeb.getElementByNameWithParent(form, fieldName); |
| 295 if (!el) |
| 296 return false; |
| 297 el.selectionStart = selectFrom; |
| 298 el.selectionEnd = el.value.length; |
| 299 return true; |
| 300 }; |
| 301 |
| 302 /** |
| 303 * Fills all password fields in the form identified by |formName| |
| 304 * with |password| and marks them as autofilled. |
| 305 * |
| 306 * @param {string} formName The name of the form to fill. |
| 307 * @param {string} password The password to fill. |
| 308 * @return {boolean} Whether a password field has been filled. |
| 309 */ |
| 310 __gCrWeb['fillPasswordFormWithGeneratedPassword'] = |
| 311 function(formName, password) { |
| 312 var form = __gCrWeb.common.getFormElementFromIdentifier(formName); |
| 313 if (!form) |
| 314 return false; |
| 315 var fields = form.querySelectorAll('input[type=password]'); |
| 316 for (var i = 0; i < fields.length; i++) { |
| 317 var field = fields[i]; |
| 318 field.value = password; |
| 319 __gCrWeb.setAutofilled(field, true); |
| 320 } |
| 321 return fields.length > 0; |
| 322 }; |
| 323 |
| 324 /** |
| 325 * Finds all forms with passwords in the supplied window or frame and appends |
| 326 * JS objects containing the form data to |formDataList|. |
| 327 * @param {!Array.<Object>} formDataList A list that this function populates |
| 328 * with descriptions of discovered forms. |
| 329 * @param {Window} win A window (or frame) in which the function should |
| 330 * look for password forms. |
| 331 */ |
| 332 __gCrWeb.getPasswordFormDataList = function(formDataList, win) { |
| 333 var doc = win.document; |
| 334 |
| 335 // We may not be allowed to read the 'document' property from a frame |
| 336 // that is in a different domain. |
| 337 if (!doc) { |
| 338 return; |
| 339 } |
| 340 |
| 341 var forms = doc.forms; |
| 342 for (var i = 0; i < forms.length; i++) { |
| 343 var formData = __gCrWeb.getPasswordFormData(forms[i]); |
| 344 if (formData) { |
| 345 formDataList.push(formData); |
| 346 } |
| 347 } |
| 348 |
| 349 // Recursively invoke for all frames/iframes. |
| 350 var frames = win.frames; |
| 351 for (var i = 0; i < frames.length; i++) { |
| 352 __gCrWeb.getPasswordFormDataList(formDataList, frames[i]); |
| 353 } |
| 354 }; |
| 355 |
| 356 /** |
| 357 * Returns a JS object containing the data from |formElement|. |
| 358 * @param {Element} formElement An HTML Form element. |
| 359 * @return {Object} Object of data from formElement. |
| 360 */ |
| 361 __gCrWeb.getPasswordFormData = function(formElement) { |
| 362 var inputs = formElement.getElementsByTagName('input'); |
| 363 |
| 364 var fields = []; |
| 365 var passwords = []; |
| 366 var firstPasswordIndex = 0; |
| 367 for (var j = 0; j < inputs.length; j++) { |
| 368 // TODO(dplotnikov): figure out a way to identify the activated |
| 369 // submit, which is the button that the user has already hit |
| 370 // before this code is called. |
| 371 |
| 372 var input = inputs[j]; |
| 373 |
| 374 fields.push({ |
| 375 'element': input.name, |
| 376 'type': input.type |
| 377 }); |
| 378 |
| 379 if (!input.disabled && input.type == 'password') { |
| 380 if (passwords.length == 0) { |
| 381 firstPasswordIndex = j; |
| 382 } |
| 383 passwords.push({ |
| 384 'element': input.name, |
| 385 'value': input.value |
| 386 }); |
| 387 } |
| 388 } |
| 389 |
| 390 if (passwords.length == 0) |
| 391 return null; |
| 392 |
| 393 var usernameElement = ''; |
| 394 var usernameValue = ''; |
| 395 for (var j = firstPasswordIndex - 1; j >= 0; j--) { |
| 396 var input = inputs[j]; |
| 397 if (!input.disabled && __gCrWeb.common.isTextField(input)) { |
| 398 usernameElement = input.name; |
| 399 usernameValue = input.value; |
| 400 break; |
| 401 } |
| 402 } |
| 403 |
| 404 var origin = __gCrWeb.common.removeQueryAndReferenceFromURL( |
| 405 formElement.ownerDocument.location.href); |
| 406 |
| 407 return { |
| 408 'action': formElement.getAttribute('action'), |
| 409 'method': formElement.getAttribute('method'), |
| 410 'name': __gCrWeb.common.getFormIdentifier(formElement), |
| 411 'origin': origin, |
| 412 'fields': fields, |
| 413 'usernameElement': usernameElement, |
| 414 'usernameValue': usernameValue, |
| 415 'passwords': passwords |
| 416 }; |
| 417 }; |
| 418 } |
OLD | NEW |