Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(405)

Side by Side Diff: ios/chrome/browser/passwords/resources/password_controller.js

Issue 1456983002: Move JS-related password manager code upstream (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698