Chromium Code Reviews| 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++) { | |
|
Eugene But (OOO till 7-30)
2015/11/18 17:06:48
Optional NIT: the body of this for-loop is large,
vabr (Chromium)
2015/11/19 10:02:27
Sorry, I don't understand. Do you mean to rename 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 j = 0; j < fields.length; j++) { | |
|
Eugene But (OOO till 7-30)
2015/11/18 17:06:48
Optional NIT: s/j/fieldIndex
vabr (Chromium)
2015/11/19 10:02:27
Done.
| |
| 80 var name = fields[j]['name']; | |
| 81 var value = fields[j]['value']; | |
| 82 // The first field in |formData| is always the username field, | |
| 83 // the second is always the password field. | |
| 84 var findingUsername = j == 0; | |
| 85 var findingPassword = j == 1; | |
| 86 var foundField = false; | |
| 87 for (var k = 0; k < inputs.length; k++) { | |
|
Eugene But (OOO till 7-30)
2015/11/18 17:06:48
Optional NIT: s/k/inputElementIndex
vabr (Chromium)
2015/11/19 10:02:27
I considered this, but because |input| instead of
| |
| 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 |