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 |