OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 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 // Installs Autofill management functions on the |__gCrWeb| object. |
| 6 // |
| 7 // It scans the DOM, extracting and storing forms and returns a JSON string |
| 8 // representing an array of objects, each of which represents an Autofill form |
| 9 // with information about a form to be filled and/or submitted and it can be |
| 10 // translated to struct FormData |
| 11 // (chromium/src/components/autofill/common/form_data.h) for further processing. |
| 12 |
| 13 /** |
| 14 * Namespace for this file. It depends on |__gCrWeb| having already been |
| 15 * injected. |
| 16 */ |
| 17 __gCrWeb['autofill'] = {}; |
| 18 |
| 19 /** |
| 20 * The maximum length allowed for form data. |
| 21 * |
| 22 * This variable is from |
| 23 * chromium/src/components/autofill/renderer/form_autofill_util.cc |
| 24 */ |
| 25 __gCrWeb.autofill.MAX_DATA_LENGTH = 1024; |
| 26 |
| 27 /** |
| 28 * The maximum number of form fields we are willing to parse, due to |
| 29 * computational costs. Several examples of forms with lots of fields that are |
| 30 * not relevant to Autofill: (1) the Netflix queue; (2) the Amazon wishlist; |
| 31 * (3) router configuration pages; and (4) other configuration pages, e.g. for |
| 32 * Google code project settings. |
| 33 * |
| 34 * This variable is from |
| 35 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 36 */ |
| 37 __gCrWeb.autofill.MAX_PARSEABE_FIELDS = 100; |
| 38 |
| 39 /** |
| 40 * A bit field mask for form or form element requirements for requirement |
| 41 * none. |
| 42 * |
| 43 * This variable is from enum RequirementsMask in |
| 44 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 45 */ |
| 46 __gCrWeb.autofill.REQUIREMENTS_MASK_NONE = 0; |
| 47 |
| 48 /** |
| 49 * A bit field mask for form or form element requirements for requirement |
| 50 * autocomplete != off. |
| 51 * |
| 52 * This variable is from enum RequirementsMask in |
| 53 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 54 */ |
| 55 __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE = 1; |
| 56 |
| 57 /** |
| 58 * A bit field mask to extract data from WebFormControlElement for |
| 59 * extracting none value. |
| 60 * |
| 61 * This variable is from enum ExtractMask in |
| 62 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 63 */ |
| 64 __gCrWeb.autofill.EXTRACT_MASK_NONE = 0; |
| 65 |
| 66 /** |
| 67 * A bit field mask to extract data from WebFormControlElement for |
| 68 * extracting value from WebFormControlElement. |
| 69 * |
| 70 * This variable is from enum ExtractMask in |
| 71 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 72 */ |
| 73 __gCrWeb.autofill.EXTRACT_MASK_VALUE = 1 << 0; |
| 74 |
| 75 /** |
| 76 * A bit field mask to extract data from WebFormControlElement for |
| 77 * extracting option text from WebFormSelectElement. Only valid when |
| 78 * EXTRACT_MASK_VALUE is set. This is used for form submission where human |
| 79 * readable value is captured. |
| 80 * |
| 81 * This variable is from enum ExtractMask in |
| 82 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 83 */ |
| 84 __gCrWeb.autofill.EXTRACT_MASK_OPTION_TEXT = 1 << 1; |
| 85 |
| 86 /** |
| 87 * A bit field mask to extract data from WebFormControlElement for |
| 88 * extracting options from WebFormControlElement. |
| 89 * |
| 90 * This variable is from enum ExtractMask in |
| 91 * chromium/src/components/autofill/renderer/form_autofill_util.h |
| 92 */ |
| 93 __gCrWeb.autofill.EXTRACT_MASK_OPTIONS = 1 << 2; |
| 94 |
| 95 /** |
| 96 * The last element that was autofilled. |
| 97 */ |
| 98 __gCrWeb.autofill.lastAutoFilledElement = null; |
| 99 |
| 100 /** |
| 101 * Scans DOM and returns a JSON string representation of forms and form |
| 102 * extraction results. |
| 103 * @param {int} requiredFields The minimum number of fields forms must have to |
| 104 * be extracted. |
| 105 * @param {int} requirements The requirements mask for forms, e.g. autocomplete |
| 106 * attribute state. |
| 107 * @return {string} A JSON encoded object with object['forms'] containing the |
| 108 * forms data and object['has_more_forms'] indicating if there are more |
| 109 * forms to extract. |
| 110 */ |
| 111 __gCrWeb.autofill['extractForms'] = function(requiredFields, requirements) { |
| 112 var forms = []; |
| 113 // Protect against custom implementation of Array.toJSON in host pages. |
| 114 forms.toJSON = null; |
| 115 // TODO(chenyu): check if any preparation is needed for information such as |
| 116 // user_submitted or the one added in core.js is sufficient. |
| 117 var hasMoreForms = __gCrWeb.autofill.extractFormsAndFormElements( |
| 118 window, |
| 119 requiredFields, |
| 120 requirements, |
| 121 forms); |
| 122 var results = new __gCrWeb.common.JSONSafeObject; |
| 123 results['has_more_forms'] = hasMoreForms; |
| 124 results['forms'] = forms; |
| 125 return __gCrWeb.stringify(results); |
| 126 }; |
| 127 |
| 128 /** |
| 129 * Fills data into the active form field. |
| 130 * |
| 131 * @param {Object} data The data to fill in. |
| 132 */ |
| 133 __gCrWeb.autofill['fillActiveFormField'] = function(data) { |
| 134 var activeElement = document.activeElement; |
| 135 if (data['name'] !== __gCrWeb['common'].nameForAutofill(activeElement)) { |
| 136 return; |
| 137 } |
| 138 __gCrWeb.autofill.lastAutoFilledElement = activeElement; |
| 139 __gCrWeb.autofill.fillFormField(data, activeElement); |
| 140 }; |
| 141 |
| 142 /** |
| 143 * Fills a number of fields in the same named form. |
| 144 * |
| 145 * @param {Object} data The data to fill in. |
| 146 */ |
| 147 __gCrWeb.autofill['fillForm'] = function(data) { |
| 148 var form = __gCrWeb.common.getFormElementFromIdentifier(data.formName); |
| 149 var controlElements = __gCrWeb.common.getFormControlElements(form); |
| 150 for (var i = 0; i < controlElements.length; ++i) { |
| 151 var element = controlElements[i]; |
| 152 if (!__gCrWeb.autofill.isAutofillableElement(element)) { |
| 153 continue; |
| 154 } |
| 155 var value = data.fields[__gCrWeb['common'].nameForAutofill(element)]; |
| 156 if (value) { |
| 157 element.value = value; |
| 158 } |
| 159 } |
| 160 }; |
| 161 |
| 162 /** |
| 163 * Dispatch an autocomplete event to the named form. |
| 164 * |
| 165 * @param {String} name Identifier for form element (from getFormIdentifier). |
| 166 */ |
| 167 __gCrWeb.autofill['dispatchAutocompleteEvent'] = function(name) { |
| 168 var formElement = __gCrWeb.common.getFormElementFromIdentifier(name); |
| 169 var event = new CustomEvent('autocomplete', {}); |
| 170 formElement.dispatchEvent(event); |
| 171 }; |
| 172 |
| 173 /** |
| 174 * Dispatch an autocomplete error event to the named form. |
| 175 * |
| 176 * @param {String} name Identifier for form element (from getFormIdentifier). |
| 177 * @param {String} reason Reason to supply in event.reason; one of 'cancel', |
| 178 * 'invalid' or 'disabled'; see requestAutocomplete spec. |
| 179 */ |
| 180 __gCrWeb.autofill['dispatchAutocompleteErrorEvent'] = function(name, reason) { |
| 181 var formElement = __gCrWeb.common.getFormElementFromIdentifier(name); |
| 182 var event = new CustomEvent('autocompleteerror', {}); |
| 183 event.reason = reason; |
| 184 formElement.dispatchEvent(event); |
| 185 }; |
| 186 |
| 187 /** |
| 188 * Dispatch an autocomplete error event to the named form. |
| 189 * |
| 190 * @param {String} name Identifier for form element (from getFormIdentifier). |
| 191 * @param {String} reason Reason to supply in event.reason; one of 'cancel', |
| 192 * 'invalid' or 'disabled'; see requestAutocomplete spec. |
| 193 */ |
| 194 __gCrWeb.autofill['dispatchAutocompleteErrorEvent'] = function(name, reason) { |
| 195 var formElement = __gCrWeb.common.getFormElementFromIdentifier(name); |
| 196 var event = new CustomEvent('autocompleteerror', {}); |
| 197 event.reason = reason; |
| 198 formElement.dispatchEvent(event); |
| 199 }; |
| 200 |
| 201 /** |
| 202 * Scans the DOM in |frame| extracting and storing forms. Fills |forms| with |
| 203 * extracted forms. Returns true if there are unextracted forms due to |
| 204 * |minimumRequiredFields| limit, else false. |
| 205 * |
| 206 * This method is based on the logic in method |
| 207 * |
| 208 * bool FormCache::ExtractFormsAndFormElements( |
| 209 * const WebFrame& frame, |
| 210 * size_t minimum_required_fields, |
| 211 * std::vector<FormData>* forms, |
| 212 * std::vector<WebFormElement>* web_form_elements) |
| 213 * |
| 214 * in chromium/src/components/autofill/renderer/form_cache.cc. |
| 215 * |
| 216 * The difference is in this implementation, no form elements are returned in |
| 217 * |web_form_elements|, and cache is not considered. Initial values of select |
| 218 * and checkable elements are not recorded at the momemt. |
| 219 * |
| 220 * @param {Object} frame A window or a frame containing forms from which the |
| 221 * data will be extracted. |
| 222 * @param {int} minimumRequiredFields The minimum number of fields a form should |
| 223 * contain for autofill. |
| 224 * @param {int} requirements The requirements mask for forms, e.g. autocomplete |
| 225 * attribute state. |
| 226 * @param {Object} forms Forms that will be filled in data of forms in frame. |
| 227 * @return {boolean} Whether there are unextracted forms due to |
| 228 * |minimumRequiredFields| limit. |
| 229 */ |
| 230 __gCrWeb.autofill.extractFormsAndFormElements = function( |
| 231 frame, minimumRequiredFields, requirements, forms) { |
| 232 if (!frame) { |
| 233 return false; |
| 234 } |
| 235 var doc = frame.document; |
| 236 if (!doc) { |
| 237 return false; |
| 238 } |
| 239 |
| 240 var webForms = doc.forms; |
| 241 |
| 242 var numFieldsSeen = 0; |
| 243 var hasSkippedForms = false; |
| 244 for (var formIndex = 0; formIndex < webForms.length; ++formIndex) { |
| 245 var formElement = webForms[formIndex]; |
| 246 var controlElements = []; |
| 247 __gCrWeb.autofill.extractAutofillableElements( |
| 248 formElement, requirements, controlElements); |
| 249 var numEditableElements = 0; |
| 250 for (var elementIndex = 0; elementIndex < controlElements.length; |
| 251 ++elementIndex) { |
| 252 var element = controlElements[elementIndex]; |
| 253 if (!__gCrWeb.autofill.isCheckableElement(element)) { |
| 254 ++numEditableElements; |
| 255 } |
| 256 } |
| 257 |
| 258 // To avoid overly expensive computation, we impose a minimum number of |
| 259 // allowable fields. The corresponding maximum number of allowable |
| 260 // fields is imposed by webFormElementToFormData(). |
| 261 if (numEditableElements < minimumRequiredFields) { |
| 262 hasSkippedForms = true; |
| 263 continue; |
| 264 } |
| 265 |
| 266 var extractMask = __gCrWeb.autofill.EXTRACT_MASK_VALUE | |
| 267 __gCrWeb.autofill.EXTRACT_MASK_OPTIONS; |
| 268 var form = new __gCrWeb['common'].JSONSafeObject; |
| 269 if (!__gCrWeb.autofill.webFormElementToFormData( |
| 270 frame, formElement, null, requirements, extractMask, form)) { |
| 271 continue; |
| 272 } |
| 273 numFieldsSeen += form['fields'].length; |
| 274 if (numFieldsSeen > __gCrWeb.autofill.MAX_PARSEABE_FIELDS) { |
| 275 break; |
| 276 } |
| 277 |
| 278 if (form.fields.length >= minimumRequiredFields) { |
| 279 forms.push(form); |
| 280 } else { |
| 281 hasSkippedForms = true; |
| 282 } |
| 283 } |
| 284 |
| 285 // Recursively invoke for all frames/iframes. |
| 286 var frames = frame.frames; |
| 287 for (var i = 0; i < frames.length; i++) { |
| 288 var hasSkippedInframe = __gCrWeb.autofill.extractFormsAndFormElements( |
| 289 frames[i], minimumRequiredFields, requirements, forms); |
| 290 hasSkippedForms = hasSkippedForms || hasSkippedInframe; |
| 291 } |
| 292 return hasSkippedForms; |
| 293 }; |
| 294 |
| 295 /** |
| 296 * Fills |form| with the form data object corresponding to the |formElement|. |
| 297 * If |field| is non-NULL, also fills |field| with the FormField object |
| 298 * corresponding to the |formControlElement|. |
| 299 * |extract_mask| controls what data is extracted. |
| 300 * Returns true if |form| is filled out; it's possible that the |formElement| |
| 301 * won't meet the |requirements|. Also returns false if there are no fields or |
| 302 * too many fields in the |form|. |
| 303 * |
| 304 * It is based on the logic in |
| 305 * bool WebFormElementToFormData( |
| 306 * const blink::WebFormElement& form_element, |
| 307 * const blink::WebFormControlElement& form_control_element, |
| 308 * RequirementsMask requirements, |
| 309 * ExtractMask extract_mask, |
| 310 * FormData* form, |
| 311 * FormFieldData* field) |
| 312 * in chromium/src/components/autofill/renderer/form_autofill_util.cc |
| 313 * |
| 314 * @param {Element} frame The frame where the formElement is in. |
| 315 * @param {Element} formElement The form element that will be processed. |
| 316 * @param {Element} formControlElement A control element in formElment, the |
| 317 * FormField of which will be returned in field. |
| 318 * @param {int} requirements The requirement on formElement autocompletion. |
| 319 * @param {int} extractMask Mask controls what data is extracted from |
| 320 * formElement. |
| 321 * @param {Object} form Form to fill in the FormData information of formElement. |
| 322 * @param {Object} field Field to fill in the form field information of |
| 323 * formControlElement. |
| 324 * @return {boolean} Whether there are fields and not too many fields in the |
| 325 * form. |
| 326 */ |
| 327 __gCrWeb.autofill.webFormElementToFormData = function( |
| 328 frame, formElement, formControlElement, requirements, extractMask, form, |
| 329 field) { |
| 330 if (!frame) { |
| 331 return false; |
| 332 } |
| 333 |
| 334 if ((requirements & |
| 335 __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE) && |
| 336 !__gCrWeb['common'].autoComplete(formElement)) { |
| 337 return false; |
| 338 } |
| 339 |
| 340 form['name'] = __gCrWeb.common.getFormIdentifier(formElement); |
| 341 var method = formElement.getAttribute('method'); |
| 342 if (method) { |
| 343 form['method'] = method; |
| 344 } |
| 345 form['origin'] = __gCrWeb.common.removeQueryAndReferenceFromURL( |
| 346 frame.location.href); |
| 347 form['action'] = __gCrWeb.common.absoluteURL( |
| 348 frame.document, |
| 349 formElement.getAttribute('action')); |
| 350 // TODO(chenyu): fill form['userSubmitted'] (Issue 231264). |
| 351 |
| 352 // Note different from form_autofill_utill.cc version of this method, which |
| 353 // computes |form.action| using document.completeURL(form_element.action()) |
| 354 // and falls back to formElement.action() if the computed action is invalid, |
| 355 // here the action returned by |__gCrWeb.common.absoluteURL| is always |
| 356 // valid, which is computed by creating a <a> element, and we don't check if |
| 357 // the action is valid. |
| 358 |
| 359 // A map from a FormFieldData's name to the FormFieldData itself. |
| 360 var nameMap = {}; |
| 361 |
| 362 // The extracted FormFields. |
| 363 var formFields = []; |
| 364 |
| 365 var controlElements = __gCrWeb['common'].getFormControlElements(formElement); |
| 366 |
| 367 // A vector of bools that indicate whether each element in |controlElements| |
| 368 // meets the requirements and thus will be in the resulting |form|. |
| 369 var fieldsExtracted = []; |
| 370 |
| 371 for (var i = 0; i < controlElements.length; ++i) { |
| 372 fieldsExtracted[i] = false; |
| 373 |
| 374 var controlElement = controlElements[i]; |
| 375 if (!__gCrWeb.autofill.isAutofillableElement(controlElement)) { |
| 376 continue; |
| 377 } |
| 378 |
| 379 if (requirements & |
| 380 __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE && |
| 381 __gCrWeb.autofill.isAutofillableInputElement(controlElement) && |
| 382 !__gCrWeb.autofill.satisfiesRequireAutocomplete( |
| 383 controlElement, false)) { |
| 384 continue; |
| 385 } |
| 386 |
| 387 // Create a new FormFieldData, fill it out and map it to the field's name. |
| 388 var formField = new __gCrWeb['common'].JSONSafeObject; |
| 389 __gCrWeb.autofill.webFormControlElementToFormField( |
| 390 controlElement, extractMask, formField); |
| 391 formFields.push(formField); |
| 392 nameMap[formField['name']] = formField; |
| 393 fieldsExtracted[i] = true; |
| 394 |
| 395 // To avoid overly expensive computation, we impose a maximum number of |
| 396 // allowable fields. |
| 397 if (formFields.length > __gCrWeb.autofill.MAX_PARSEABE_FIELDS) { |
| 398 return false; |
| 399 } |
| 400 } |
| 401 |
| 402 // If we failed to extract any fields, give up. |
| 403 if (formFields.length === 0) { |
| 404 return false; |
| 405 } |
| 406 |
| 407 // Loop through the label elements inside the form element. For each label |
| 408 // element, get the corresponding form control element, use the form control |
| 409 // element's name as a key into the <name, FormFieldData> nameMap to find the |
| 410 // previously created FormFieldData and set the FormFieldData's label. |
| 411 var labels = formElement.getElementsByTagName('label'); |
| 412 for (var index = 0; index < labels.length; ++index) { |
| 413 var label = labels[index]; |
| 414 if (!label.control && !label.htmlFor) { |
| 415 continue; |
| 416 } |
| 417 var fieldElement = label.control; |
| 418 var elementName; |
| 419 if (!fieldElement) { |
| 420 // Sometimes site authors will incorrectly specify the corresponding |
| 421 // field element's name rather than its id, so we compensate here. |
| 422 elementName = label.htmlFor; |
| 423 } else if (fieldElement.form != formElement || |
| 424 fieldElement.type === 'hidden') { |
| 425 continue; |
| 426 } else { |
| 427 elementName = __gCrWeb['common'].nameForAutofill(fieldElement); |
| 428 } |
| 429 |
| 430 var fieldElementData = nameMap[elementName]; |
| 431 if (fieldElementData) { |
| 432 if (!fieldElementData['label']) { |
| 433 fieldElementData['label'] = ''; |
| 434 } |
| 435 var labelText = __gCrWeb.autofill.findChildText(label); |
| 436 // Concatenate labels because some sites might have multiple label |
| 437 // candidates. |
| 438 if (fieldElementData['label'].length > 0 && |
| 439 labelText.length > 0) { |
| 440 fieldElementData['label'] += ' '; |
| 441 } |
| 442 fieldElementData['label'] += labelText; |
| 443 } |
| 444 } |
| 445 |
| 446 // Loop through the form control elements, extracting the label text from |
| 447 // the DOM. We use the |fieldsExtracted| vector to make sure we assign the |
| 448 // extracted label to the correct field, as it's possible |form_fields| will |
| 449 // not contain all of the elements in |control_elements|. |
| 450 for (var i = 0, fieldIdx = 0; |
| 451 i < controlElements.length && fieldIdx < formFields.length; ++i) { |
| 452 // This field didn't meet the requirements, so don't try to find a label |
| 453 // for it. |
| 454 if (!fieldsExtracted[i]) |
| 455 continue; |
| 456 |
| 457 var controlElement = controlElements[i]; |
| 458 var fieldLabel = formFields[fieldIdx]['label']; |
| 459 if (!fieldLabel || fieldLabel.length === 0) { |
| 460 formFields[fieldIdx]['label'] = |
| 461 __gCrWeb.autofill.inferLabelForElement(controlElement); |
| 462 } |
| 463 if (controlElement === formControlElement) |
| 464 field = formField; |
| 465 ++fieldIdx; |
| 466 } |
| 467 |
| 468 form['fields'] = formFields; |
| 469 // Protect against custom implementation of Array.toJSON in host pages. |
| 470 form['fields'].toJSON = null; |
| 471 return true; |
| 472 }; |
| 473 |
| 474 /** |
| 475 * Returns is the tag of an |element| is tag. |
| 476 * |
| 477 * It is based on the logic in |
| 478 * bool HasTagName(const WebNode& node, const blink::WebString& tag) |
| 479 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 480 * |
| 481 * @param {Element} node Node to examine. |
| 482 * @param {string} tag Tag name. |
| 483 * @return {boolean} Whether the tag of node is tag. |
| 484 */ |
| 485 __gCrWeb.autofill.hasTagName = function(node, tag) { |
| 486 return node.nodeType === document.ELEMENT_NODE && |
| 487 node.tagName === tag.toUpperCase(); |
| 488 }; |
| 489 |
| 490 /** |
| 491 * Checks if an element is autofillable. |
| 492 * |
| 493 * It is based on the logic in |
| 494 * bool IsAutofillableElement(const WebFormControlElement& element) |
| 495 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 496 * |
| 497 * @param {Element} element An element to examine. |
| 498 * @return {boolean} Whether element is one of the element types that can be |
| 499 * autofilled. |
| 500 */ |
| 501 __gCrWeb.autofill.isAutofillableElement = function(element) { |
| 502 return __gCrWeb.autofill.isAutofillableInputElement(element) || |
| 503 __gCrWeb.autofill.isSelectElement(element) || |
| 504 __gCrWeb.autofill.isTextAreaElement(element); |
| 505 }; |
| 506 |
| 507 /** |
| 508 * Check whether the given field satisfies the |
| 509 * __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE requirement. When |
| 510 * Autocheckout is enabled, all fields are considered to satisfy this |
| 511 * requirement. |
| 512 * |
| 513 * It is based on the logic in |
| 514 * bool SatisfiesRequireAutocomplete(const WebInputElement& input_element) |
| 515 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 516 * |
| 517 * @param {Element} element The element to be examined. |
| 518 * @param {boolean} isExperimentalFormFillingEnabled Boolean from |
| 519 * switches::kEnableExperimentalFormFilling. |
| 520 * @return {boolean} Whether the inputElement satisfies the requirement. |
| 521 */ |
| 522 __gCrWeb.autofill.satisfiesRequireAutocomplete = function( |
| 523 element, isExperimentalFormFillingEnabled) { |
| 524 return __gCrWeb.common.autoComplete(element) || |
| 525 isExperimentalFormFillingEnabled; |
| 526 }; |
| 527 |
| 528 /** |
| 529 * Trims whitespace from the start of the input string. |
| 530 * Simplified version of string_util::TrimWhitespace. |
| 531 * @param {string} input String to trim. |
| 532 * @return {string} The |input| string without leading whitespace. |
| 533 */ |
| 534 __gCrWeb.autofill.trimWhitespaceLeading = function(input) { |
| 535 return input.replace(/^\s+/gm, ''); |
| 536 }; |
| 537 |
| 538 /** |
| 539 * Trims whitespace from the end of the input string. |
| 540 * Simplified version of string_util::TrimWhitespace. |
| 541 * @param {string} input String to trim. |
| 542 * @return {string} The |input| string without trailing whitespace. |
| 543 */ |
| 544 __gCrWeb.autofill.trimWhitespaceTrailing = function(input) { |
| 545 return input.replace(/\s+$/gm, ''); |
| 546 }; |
| 547 |
| 548 /** |
| 549 * Appends |suffix| to |prefix| so that any intermediary whitespace is collapsed |
| 550 * to a single space. If |force_whitespace| is true, then the resulting string |
| 551 * is guaranteed to have a space between |prefix| and |suffix|. Otherwise, the |
| 552 * result includes a space only if |prefix| has trailing whitespace or |suffix| |
| 553 * has leading whitespace. |
| 554 * |
| 555 * A few examples: |
| 556 * CombineAndCollapseWhitespace('foo', 'bar', false) -> 'foobar' |
| 557 * CombineAndCollapseWhitespace('foo', 'bar', true) -> 'foo bar' |
| 558 * CombineAndCollapseWhitespace('foo ', 'bar', false) -> 'foo bar' |
| 559 * CombineAndCollapseWhitespace('foo', ' bar', false) -> 'foo bar' |
| 560 * CombineAndCollapseWhitespace('foo', ' bar', true) -> 'foo bar' |
| 561 * CombineAndCollapseWhitespace('foo ', ' bar', false) -> 'foo bar' |
| 562 * CombineAndCollapseWhitespace(' foo', 'bar ', false) -> ' foobar ' |
| 563 * CombineAndCollapseWhitespace(' foo', 'bar ', true) -> ' foo bar ' |
| 564 * |
| 565 * It is based on the logic in |
| 566 * const string16 CombineAndCollapseWhitespace(const string16& prefix, |
| 567 * const string16& suffix, |
| 568 * bool force_whitespace) |
| 569 * @param {string} prefix The prefix string in the string combination. |
| 570 * @param {string} suffix The suffix string in the string combination. |
| 571 * @param {boolean} forceWhitespace A boolean indicating if whitespace should |
| 572 * be added as separator in the combination. |
| 573 * @return {string} The combined string. |
| 574 */ |
| 575 __gCrWeb.autofill.combineAndCollapseWhitespace = function( |
| 576 prefix, suffix, forceWhitespace) { |
| 577 var prefixTrimmed = __gCrWeb.autofill.trimWhitespaceTrailing(prefix); |
| 578 var prefixTrailingWhitespace = prefixTrimmed != prefix; |
| 579 var suffixTrimmed = __gCrWeb.autofill.trimWhitespaceLeading(suffix); |
| 580 var suffixLeadingWhitespace = suffixTrimmed != suffix; |
| 581 if (prefixTrailingWhitespace || suffixLeadingWhitespace || forceWhitespace) { |
| 582 return prefixTrimmed + ' ' + suffixTrimmed; |
| 583 } else { |
| 584 return prefixTrimmed + suffixTrimmed; |
| 585 } |
| 586 }; |
| 587 |
| 588 /** |
| 589 * This is a helper function for the findChildText() function (see below). |
| 590 * Search depth is limited with the |depth| parameter. |
| 591 * Based on form_autofill_util::findChildTextInner(). |
| 592 * @param {Element} node The node to fetch the text content from. |
| 593 * @param {int} depth The maximum depth to descend on the DOM. |
| 594 * @return {string} The discovered and adapted string. |
| 595 */ |
| 596 __gCrWeb.autofill.findChildTextInner = function(node, depth) { |
| 597 if (depth <= 0 || !node) { |
| 598 return ''; |
| 599 } |
| 600 |
| 601 // Skip over comments. |
| 602 if (node.nodeType === document.COMMENT_NODE) { |
| 603 return __gCrWeb.autofill.findChildTextInner(node.nextSibling, depth - 1); |
| 604 } |
| 605 |
| 606 if (node.nodeType !== document.ELEMENT_NODE && |
| 607 node.nodeType !== document.TEXT_NODE) { |
| 608 return ''; |
| 609 } |
| 610 |
| 611 // Ignore elements known not to contain inferable labels. |
| 612 if (node.nodeType === document.ELEMENT_NODE) { |
| 613 if (node.tagName === 'OPTION' || |
| 614 node.tagName === 'SCRIPT' || |
| 615 node.tagName === 'NOSCRIPT' || |
| 616 (__gCrWeb.common.isFormControlElement(node) && |
| 617 __gCrWeb.autofill.isAutofillableElement(node))) { |
| 618 return ''; |
| 619 } |
| 620 } |
| 621 |
| 622 // Extract the text exactly at this node. |
| 623 var nodeText = __gCrWeb.autofill.nodeValue(node); |
| 624 if (node.nodeType === document.TEXT_NODE && !nodeText) { |
| 625 // In the C++ version, this text node would have been stripped completely. |
| 626 // Just pass the buck. |
| 627 return __gCrWeb.autofill.findChildTextInner(node.nextSibling, depth); |
| 628 } |
| 629 |
| 630 // Recursively compute the children's text. |
| 631 // Preserve inter-element whitespace separation. |
| 632 var childText = |
| 633 __gCrWeb.autofill.findChildTextInner(node.firstChild, depth - 1); |
| 634 var addSpace = node.nodeType === document.TEXT_NODE && !nodeText; |
| 635 // Emulate apparently incorrect Chromium behavior tracked in crbug 239819. |
| 636 addSpace = false; |
| 637 nodeText = __gCrWeb.autofill.combineAndCollapseWhitespace(nodeText, |
| 638 childText, addSpace); |
| 639 |
| 640 // Recursively compute the siblings' text. |
| 641 // Again, preserve inter-element whitespace separation. |
| 642 var siblingText = |
| 643 __gCrWeb.autofill.findChildTextInner(node.nextSibling, depth - 1); |
| 644 addSpace = node.nodeType === document.TEXT_NODE && !nodeText; |
| 645 // Emulate apparently incorrect Chromium behavior tracked in crbug 239819. |
| 646 addSpace = false; |
| 647 nodeText = __gCrWeb.autofill.combineAndCollapseWhitespace(nodeText, |
| 648 siblingText, addSpace); |
| 649 |
| 650 return nodeText; |
| 651 }; |
| 652 |
| 653 /** |
| 654 * Returns the aggregated values of the descendants of |element| that are |
| 655 * non-empty text nodes. |
| 656 * |
| 657 * It is based on the logic in |
| 658 * string16 FindChildText(const WebNode& node) |
| 659 * chromium/src/components/autofill/renderer/form_autofill_util.cc, which is a |
| 660 * faster alternative to |innerText()| for performance critical operations. |
| 661 * |
| 662 * @param {Element} node A node of which the child text will be return. |
| 663 * @return {string} The child text. |
| 664 */ |
| 665 __gCrWeb.autofill.findChildText = function(node) { |
| 666 if (node.nodeType === document.TEXT_NODE) |
| 667 return __gCrWeb.autofill.nodeValue(node); |
| 668 var child = node.firstChild; |
| 669 |
| 670 var kChildSearchDepth = 10; |
| 671 var nodeText = __gCrWeb.autofill.findChildTextInner(child, kChildSearchDepth); |
| 672 nodeText = nodeText.trim(); |
| 673 return nodeText; |
| 674 }; |
| 675 |
| 676 /** |
| 677 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 678 * a previous sibling of |element|, |
| 679 * e.g. Some Text <input ...> |
| 680 * or Some <span>Text</span> <input ...> |
| 681 * or <p>Some Text</p><input ...> |
| 682 * or <label>Some Text</label> <input ...> |
| 683 * or Some Text <img><input ...> |
| 684 * or <b>Some Text</b><br/> <input ...>. |
| 685 * |
| 686 * It is based on the logic in |
| 687 * string16 InferLabelFromPrevious(const WebFormControlElement& element) |
| 688 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 689 * |
| 690 * @param {Element} element An element to examine. |
| 691 * @return {string} The label of element. |
| 692 */ |
| 693 __gCrWeb.autofill.inferLabelFromPrevious = function(element) { |
| 694 var inferredLabel = ''; |
| 695 var previous = element; |
| 696 if (!previous) { |
| 697 return ''; |
| 698 } |
| 699 |
| 700 while (true) { |
| 701 previous = previous.previousSibling; |
| 702 if (!previous) { |
| 703 break; |
| 704 } |
| 705 |
| 706 // Skip over comments. |
| 707 var nodeType = previous.nodeType; |
| 708 if (nodeType === document.COMMENT_NODE_NODE) { |
| 709 continue; |
| 710 } |
| 711 |
| 712 // Otherwise, only consider normal HTML elements and their contents. |
| 713 if (nodeType != document.TEXT_NODE && |
| 714 nodeType != document.ELEMENT_NODE) { |
| 715 break; |
| 716 } |
| 717 |
| 718 // A label might be split across multiple "lightweight" nodes. |
| 719 // Coalesce any text contained in multiple consecutive |
| 720 // (a) plain text nodes or |
| 721 // (b) inline HTML elements that are essentially equivalent to text nodes. |
| 722 if (nodeType === document.TEXT_NODE || |
| 723 __gCrWeb.autofill.hasTagName(previous, 'b') || |
| 724 __gCrWeb.autofill.hasTagName(previous, 'strong') || |
| 725 __gCrWeb.autofill.hasTagName(previous, 'span') || |
| 726 __gCrWeb.autofill.hasTagName(previous, 'font')) { |
| 727 var value = __gCrWeb.autofill.findChildText(previous); |
| 728 // A text node's value will be empty if it is for a line break. |
| 729 var addSpace = nodeType === document.TEXT_NODE && |
| 730 value.length === 0; |
| 731 inferredLabel = |
| 732 __gCrWeb.autofill.combineAndCollapseWhitespace( |
| 733 value, inferredLabel, addSpace); |
| 734 continue; |
| 735 } |
| 736 |
| 737 // If we have identified a partial label and have reached a non-lightweight |
| 738 // element, consider the label to be complete. |
| 739 var trimmedLabel = inferredLabel.trim(); |
| 740 if (trimmedLabel.length > 0) { |
| 741 break; |
| 742 } |
| 743 |
| 744 // <img> and <br> tags often appear between the input element and its |
| 745 // label text, so skip over them. |
| 746 if (__gCrWeb.autofill.hasTagName(previous, 'img') || |
| 747 __gCrWeb.autofill.hasTagName(previous, 'br')) { |
| 748 continue; |
| 749 } |
| 750 |
| 751 // We only expect <p> and <label> tags to contain the full label text. |
| 752 if (__gCrWeb.autofill.hasTagName(previous, 'p') || |
| 753 __gCrWeb.autofill.hasTagName(previous, 'label')) { |
| 754 inferredLabel = __gCrWeb.autofill.findChildText(previous); |
| 755 } |
| 756 break; |
| 757 } |
| 758 return inferredLabel.trim(); |
| 759 }; |
| 760 |
| 761 /** |
| 762 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 763 * the placeholder attribute. |
| 764 * |
| 765 * It is based on the logic in |
| 766 * string16 InferLabelFromPlaceholder(const WebFormControlElement& element) |
| 767 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 768 * |
| 769 * @param {Element} element An element to examine. |
| 770 * @return {string} The label of element. |
| 771 */ |
| 772 __gCrWeb.autofill.inferLabelFromPlaceholder = function(element) { |
| 773 if (!element || !element.placeholder) { |
| 774 return ''; |
| 775 } |
| 776 |
| 777 return element.placeholder; |
| 778 }; |
| 779 |
| 780 /** |
| 781 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 782 * enclosing list item, e.g. |
| 783 * <li>Some Text<input ...><input ...><input ...></li> |
| 784 * |
| 785 * It is based on the logic in |
| 786 * string16 InferLabelFromListItem(const WebFormControlElement& element) |
| 787 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 788 * |
| 789 * @param {Element} element An element to examine. |
| 790 * @return {string} The label of element. |
| 791 */ |
| 792 __gCrWeb.autofill.inferLabelFromListItem = function(element) { |
| 793 if (!element) { |
| 794 return ''; |
| 795 } |
| 796 |
| 797 var parent = element.parentNode; |
| 798 while (parent && |
| 799 parent.nodeType === document.ELEMENT_NODE && |
| 800 !__gCrWeb.autofill.hasTagName(parent, 'li')) { |
| 801 parent = parent.parentNode; |
| 802 } |
| 803 |
| 804 if (parent && __gCrWeb.autofill.hasTagName(parent, 'li')) |
| 805 return __gCrWeb.autofill.findChildText(parent); |
| 806 |
| 807 return ''; |
| 808 }; |
| 809 |
| 810 /** |
| 811 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 812 * surrounding table structure, |
| 813 * e.g. <tr><td>Some Text</td><td><input ...></td></tr> |
| 814 * or <tr><th>Some Text</th><td><input ...></td></tr> |
| 815 * or <tr><td><b>Some Text</b></td><td><b><input ...></b></td></tr> |
| 816 * or <tr><th><b>Some Text</b></th><td><b><input ...></b></td></tr> |
| 817 * |
| 818 * It is based on the logic in |
| 819 * string16 InferLabelFromTableColumn(const WebFormControlElement& element) |
| 820 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 821 * |
| 822 * @param {Element} element An element to examine. |
| 823 * @return {string} The label of element. |
| 824 */ |
| 825 __gCrWeb.autofill.inferLabelFromTableColumn = function(element) { |
| 826 if (!element) { |
| 827 return ''; |
| 828 } |
| 829 |
| 830 var parent = element.parentNode; |
| 831 while (parent && |
| 832 parent.nodeType === document.ELEMENT_NODE && |
| 833 !__gCrWeb.autofill.hasTagName(parent, 'td')) { |
| 834 parent = parent.parentNode; |
| 835 } |
| 836 |
| 837 if (!parent) { |
| 838 return ''; |
| 839 } |
| 840 |
| 841 // Check all previous siblings, skipping non-element nodes, until we find a |
| 842 // non-empty text block. |
| 843 var inferredLabel = ''; |
| 844 var previous = parent.previousSibling; |
| 845 while (inferredLabel.length === 0 && previous) { |
| 846 if (__gCrWeb.autofill.hasTagName(previous, 'td') || |
| 847 __gCrWeb.autofill.hasTagName(previous, 'th')) { |
| 848 inferredLabel = __gCrWeb.autofill.findChildText(previous); |
| 849 } |
| 850 previous = previous.previousSibling; |
| 851 } |
| 852 |
| 853 return inferredLabel; |
| 854 }; |
| 855 |
| 856 /** |
| 857 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 858 * surrounding table structure, |
| 859 * e.g. <tr><td>Some Text</td></tr><tr><td><input ...></td></tr> |
| 860 * |
| 861 * It is based on the logic in |
| 862 * string16 InferLabelFromTableRow(const WebFormControlElement& element) |
| 863 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 864 * |
| 865 * @param {Element} element An element to examine. |
| 866 * @return {string} The label of element. |
| 867 */ |
| 868 __gCrWeb.autofill.inferLabelFromTableRow = function(element) { |
| 869 if (!element) { |
| 870 return ''; |
| 871 } |
| 872 |
| 873 var parent = element.parentNode; |
| 874 while (parent && |
| 875 parent.nodeType === document.ELEMENT_NODE && |
| 876 !__gCrWeb.autofill.hasTagName(parent, 'tr')) { |
| 877 parent = parent.parentNode; |
| 878 } |
| 879 |
| 880 if (!parent) { |
| 881 return ''; |
| 882 } |
| 883 |
| 884 var inferredLabel = ''; |
| 885 // Check all previous siblings, skipping non-element nodes, until we find a |
| 886 // non-empty text block. |
| 887 var previous = parent.previousSibling; |
| 888 while (inferredLabel.length === 0 && previous) { |
| 889 if (__gCrWeb.autofill.hasTagName(previous, 'tr')) { |
| 890 inferredLabel = __gCrWeb.autofill.findChildText(previous); |
| 891 } |
| 892 previous = previous.previousSibling; |
| 893 } |
| 894 return inferredLabel; |
| 895 }; |
| 896 |
| 897 /** |
| 898 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 899 * a surrounding div table, |
| 900 * e.g. <div>Some Text<span><input ...></span></div> |
| 901 * e.g. <div>Some Text</div><div><input ...></div> |
| 902 * |
| 903 * It is based on the logic in |
| 904 * string16 InferLabelFromDivTable(const WebFormControlElement& element) |
| 905 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 906 * |
| 907 * @param {Element} element An element to examine. |
| 908 * @return {string} The label of element. |
| 909 */ |
| 910 __gCrWeb.autofill.inferLabelFromDivTable = function(element) { |
| 911 if (!element) { |
| 912 return ''; |
| 913 } |
| 914 |
| 915 var node = element.parentNode; |
| 916 var lookingForParent = true; |
| 917 |
| 918 // Search the sibling and parent <div>s until we find a candidate label. |
| 919 var inferredLabel = ''; |
| 920 while (inferredLabel.length === 0 && node) { |
| 921 if (__gCrWeb.autofill.hasTagName(node, 'div')) { |
| 922 lookingForParent = false; |
| 923 inferredLabel = __gCrWeb.autofill.findChildText(node); |
| 924 } else if (lookingForParent && |
| 925 (__gCrWeb.autofill.hasTagName(node, 'table') || |
| 926 __gCrWeb.autofill.hasTagName(node, 'fieldset'))) { |
| 927 // If the element is in a table or fieldset, its label most likely is too. |
| 928 break; |
| 929 } |
| 930 |
| 931 if (!node.previousSibling) { |
| 932 // If there are no more siblings, continue walking up the tree. |
| 933 lookingForParent = true; |
| 934 } |
| 935 |
| 936 if (lookingForParent) { |
| 937 node = node.parentNode; |
| 938 } else { |
| 939 node = node.previousSibling; |
| 940 } |
| 941 } |
| 942 |
| 943 return inferredLabel; |
| 944 }; |
| 945 |
| 946 /** |
| 947 * Helper for |InferLabelForElement()| that infers a label, if possible, from |
| 948 * a surrounding definition list, |
| 949 * e.g. <dl><dt>Some Text</dt><dd><input ...></dd></dl> |
| 950 * e.g. <dl><dt><b>Some Text</b></dt><dd><b><input ...></b></dd></dl> |
| 951 * |
| 952 * It is based on the logic in |
| 953 * string16 InferLabelFromDefinitionList( |
| 954 * const WebFormControlElement& element) |
| 955 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 956 * |
| 957 * @param {Element} element An element to examine. |
| 958 * @return {string} The label of element. |
| 959 */ |
| 960 __gCrWeb.autofill.inferLabelFromDefinitionList = function(element) { |
| 961 if (!element) { |
| 962 return ''; |
| 963 } |
| 964 |
| 965 var parent = element.parentNode; |
| 966 while (parent && |
| 967 parent.nodeType === document.ELEMENT_NODE && |
| 968 !__gCrWeb.autofill.hasTagName(parent, 'dd')) { |
| 969 parent = parent.parentNode; |
| 970 } |
| 971 |
| 972 if (!parent || !__gCrWeb.autofill.hasTagName(parent, 'dd')) { |
| 973 return ''; |
| 974 } |
| 975 |
| 976 // Skip by any intervening text nodes. |
| 977 var previous = parent.previousSibling; |
| 978 while (previous && |
| 979 previous.nodeType === document.TEXT_NODE) { |
| 980 previous = previous.previousSibling; |
| 981 } |
| 982 |
| 983 if (!previous || !__gCrWeb.autofill.hasTagName(previous, 'dt')) |
| 984 return ''; |
| 985 |
| 986 return __gCrWeb.autofill.findChildText(previous); |
| 987 }; |
| 988 |
| 989 /** |
| 990 * Infers corresponding label for |element| from surrounding context in the DOM, |
| 991 * e.g. the contents of the preceding <p> tag or text element. |
| 992 * |
| 993 * It is based on the logic in |
| 994 * string16 InferLabelForElement(const WebFormControlElement& element) |
| 995 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 996 * |
| 997 * @param {Element} element An element to examine. |
| 998 * @return {string} The label of element. |
| 999 */ |
| 1000 __gCrWeb.autofill.inferLabelForElement = function(element) { |
| 1001 var inferredLabel = __gCrWeb.autofill.inferLabelFromPrevious(element); |
| 1002 if (inferredLabel.length > 0) { |
| 1003 return inferredLabel; |
| 1004 } |
| 1005 |
| 1006 // If we didn't find a label, check for the placeholder case. |
| 1007 inferredLabel = __gCrWeb.autofill.inferLabelFromPlaceholder(element); |
| 1008 if (inferredLabel.length > 0) { |
| 1009 return inferredLabel; |
| 1010 } |
| 1011 |
| 1012 // If we didn't find a label, check for list item case. |
| 1013 inferredLabel = __gCrWeb.autofill.inferLabelFromListItem(element); |
| 1014 if (inferredLabel.length > 0) { |
| 1015 return inferredLabel; |
| 1016 } |
| 1017 |
| 1018 // If we didn't find a label, check for table cell case. |
| 1019 inferredLabel = __gCrWeb.autofill.inferLabelFromTableColumn(element); |
| 1020 if (inferredLabel.length > 0) { |
| 1021 return inferredLabel; |
| 1022 } |
| 1023 |
| 1024 // If we didn't find a label, check for table row case. |
| 1025 inferredLabel = __gCrWeb.autofill.inferLabelFromTableRow(element); |
| 1026 if (inferredLabel.length > 0) { |
| 1027 return inferredLabel; |
| 1028 } |
| 1029 |
| 1030 // If we didn't find a label, check for definition list case. |
| 1031 inferredLabel = __gCrWeb.autofill.inferLabelFromDefinitionList(element); |
| 1032 if (inferredLabel.length > 0) { |
| 1033 return inferredLabel; |
| 1034 } |
| 1035 |
| 1036 // If we didn't find a label, check for div table case. |
| 1037 return __gCrWeb.autofill.inferLabelFromDivTable(element); |
| 1038 }; |
| 1039 |
| 1040 /** |
| 1041 * Fills |field| data with the values of the <option> elements present in |
| 1042 * |selectElement|. |
| 1043 * |
| 1044 * It is based on the logic in |
| 1045 * void GetOptionStringsFromElement(const WebSelectElement& select_element, |
| 1046 * std::vector<string16>* option_values, |
| 1047 * std::vector<string16>* option_contents) |
| 1048 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 1049 * |
| 1050 * @param {Element} selectElement A select element from which option data are |
| 1051 * extracted. |
| 1052 * @param {Object} field A field that will contain the extracted option |
| 1053 * information. |
| 1054 */ |
| 1055 __gCrWeb.autofill.getOptionStringsFromElement = function( |
| 1056 selectElement, field) { |
| 1057 field['option_values'] = []; |
| 1058 // Protect against custom implementation of Array.toJSON in host pages. |
| 1059 field['option_values'].toJSON = null; |
| 1060 field['option_contents'] = []; |
| 1061 field['option_contents'].toJSON = null; |
| 1062 var options = selectElement.options; |
| 1063 for (var i = 0; i < options.length; ++i) { |
| 1064 var option = options[i]; |
| 1065 field['option_values'].push(option['value']); |
| 1066 field['option_contents'].push(option['text']); |
| 1067 } |
| 1068 }; |
| 1069 |
| 1070 /** |
| 1071 * Sets the |field|'s value to the value in |data|. |
| 1072 * Also sets the "autofilled" attribute. |
| 1073 * |
| 1074 * It is based on the logic in |
| 1075 * void FillFormField(const FormFieldData& data, |
| 1076 * bool is_initiating_node, |
| 1077 * blink::WebFormControlElement* field) |
| 1078 * in chromium/src/components/autofill/renderer/form_autofill_util.cc. |
| 1079 * |
| 1080 * Different from FillFormField(), is_initiating_node is not considered in |
| 1081 * this implementation. |
| 1082 * |
| 1083 * @param {Object} data Data that will be filled into field. |
| 1084 * @param {Element} field The element to which data will be filled. |
| 1085 */ |
| 1086 __gCrWeb.autofill.fillFormField = function(data, field) { |
| 1087 // Nothing to fill. |
| 1088 if (!data['value'] || data['value'].length === 0) { |
| 1089 return; |
| 1090 } |
| 1091 |
| 1092 if (__gCrWeb.autofill.isTextInput(field) || |
| 1093 __gCrWeb.autofill.isTextAreaElement(field)) { |
| 1094 var sanitizedValue = data['value']; |
| 1095 |
| 1096 if (__gCrWeb.autofill.isTextInput(field)) { |
| 1097 // If the 'max_length' attribute contains a negative value, the default |
| 1098 // maxlength value is used. |
| 1099 var maxLength = data['max_length']; |
| 1100 if (maxLength < 0) { |
| 1101 maxLength = __gCrWeb.autofill.MAX_DATA_LENGTH; |
| 1102 } |
| 1103 sanitizedValue = data['value'].substr(0, maxLength); |
| 1104 } |
| 1105 |
| 1106 __gCrWeb.common.setInputElementValue(sanitizedValue, field, true); |
| 1107 field.isAutofilled = true; |
| 1108 } else if (__gCrWeb.autofill.isSelectElement(field)) { |
| 1109 if (field.value !== data['value']) { |
| 1110 field.value = data['value']; |
| 1111 __gCrWeb.common.createAndDispatchHTMLEvent(field, 'change', true, false); |
| 1112 } |
| 1113 } else { |
| 1114 if (__gCrWeb.autofill.isCheckableElement(field)) { |
| 1115 __gCrWeb.common.setInputElementChecked(data['is_checked'], field, true); |
| 1116 } |
| 1117 } |
| 1118 }; |
| 1119 |
| 1120 /** |
| 1121 * Returns true if |element| is a text input element. |
| 1122 * |
| 1123 * It is based on the logic in |
| 1124 * bool IsTextInput(const blink::WebInputElement* element) |
| 1125 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1126 * |
| 1127 * @param {Element} element An element to examine. |
| 1128 * @return {boolean} Whether element is a text input field. |
| 1129 */ |
| 1130 __gCrWeb.autofill.isTextInput = function(element) { |
| 1131 if (!element) { |
| 1132 return false; |
| 1133 } |
| 1134 return __gCrWeb.common.isTextField(element); |
| 1135 }; |
| 1136 |
| 1137 /** |
| 1138 * Returns true if |element| is a 'select' element. |
| 1139 * |
| 1140 * It is based on the logic in |
| 1141 * bool IsSelectElement(const blink::WebFormControlElement& element) |
| 1142 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1143 * |
| 1144 * @param {Element} element An element to examine. |
| 1145 * @return {boolean} Whether element is a 'select' element. |
| 1146 */ |
| 1147 __gCrWeb.autofill.isSelectElement = function(element) { |
| 1148 if (!element) { |
| 1149 return false; |
| 1150 } |
| 1151 return element.type === 'select-one'; |
| 1152 }; |
| 1153 |
| 1154 /** |
| 1155 * Returns true if |element| is a 'textarea' element. |
| 1156 * |
| 1157 * It is based on the logic in |
| 1158 * bool IsTextAreaElement(const blink::WebFormControlElement& element) |
| 1159 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1160 * |
| 1161 * @param {Element} element An element to examine. |
| 1162 * @return {boolean} Whether element is a 'textarea' element. |
| 1163 */ |
| 1164 __gCrWeb.autofill.isTextAreaElement = function(element) { |
| 1165 if (!element) { |
| 1166 return false; |
| 1167 } |
| 1168 return element.type === 'textarea'; |
| 1169 }; |
| 1170 |
| 1171 /** |
| 1172 * Returns true if |element| is a checkbox or a radio button element. |
| 1173 * |
| 1174 * It is based on the logic in |
| 1175 * bool IsCheckableElement(const blink::WebInputElement* element) |
| 1176 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1177 * |
| 1178 * @param {Element} element An element to examine. |
| 1179 * @return {boolean} Whether element is a checkbox or a radio button. |
| 1180 */ |
| 1181 __gCrWeb.autofill.isCheckableElement = function(element) { |
| 1182 if (!element) { |
| 1183 return false; |
| 1184 } |
| 1185 return element.type === 'checkbox' || element.type === 'radio'; |
| 1186 }; |
| 1187 |
| 1188 /** |
| 1189 * Returns true if |element| is one of the input element types that can be |
| 1190 * autofilled. {Text, Radiobutton, Checkbox}. |
| 1191 * |
| 1192 * It is based on the logic in |
| 1193 * bool IsAutofillableInputElement(const blink::WebInputElement* element) |
| 1194 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1195 * |
| 1196 * @param {Element} element An element to examine. |
| 1197 * @return {boolean} Whether element is one of the input element types that |
| 1198 * can be autofilled. |
| 1199 */ |
| 1200 __gCrWeb.autofill.isAutofillableInputElement = function(element) { |
| 1201 return __gCrWeb.autofill.isTextInput(element) || |
| 1202 __gCrWeb.autofill.isCheckableElement(element); |
| 1203 }; |
| 1204 |
| 1205 /** |
| 1206 * Returns the nodeValue in a way similar to the C++ version of node.nodeValue, |
| 1207 * used in src/components/autofill/renderer/form_autofill_util.h. Newlines and |
| 1208 * tabs are stripped. |
| 1209 * |
| 1210 * @param {Element} element An element to examine. |
| 1211 * @return {string} The text contained in |element|. |
| 1212 */ |
| 1213 __gCrWeb.autofill.nodeValue = function(element) { |
| 1214 return (element.nodeValue || '').replace(/[\n\t]/gm, ''); |
| 1215 }; |
| 1216 |
| 1217 /** |
| 1218 * Returns the value in a way similar to the C++ version of node.value, |
| 1219 * used in src/components/autofill/renderer/form_autofill_util.h. Newlines and |
| 1220 * tabs are stripped. |
| 1221 * |
| 1222 * @param {Element} element An element to examine. |
| 1223 * @return {string} The value for |element|. |
| 1224 */ |
| 1225 __gCrWeb.autofill.value = function(element) { |
| 1226 return (element.value || '').replace(/[\n\t]/gm, ''); |
| 1227 }; |
| 1228 |
| 1229 /** |
| 1230 * Returns the auto-fillable form control elements in |formElement|. |
| 1231 * |
| 1232 * It is based on the logic in |
| 1233 * void ExtractAutofillableElements( |
| 1234 * const blink::WebFormElement& form_element, |
| 1235 * RequirementsMask requirements, |
| 1236 * std::vector<blink::WebFormControlElement>* autofillable_elements); |
| 1237 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1238 * |
| 1239 * @param {Element} formElement A form element to be processed. |
| 1240 * @param {int} requirementsMask A mask on the requirement. |
| 1241 * @param {Array.<Element>} autofillableElements The array of autofillable |
| 1242 * elements. |
| 1243 */ |
| 1244 __gCrWeb.autofill.extractAutofillableElements = function( |
| 1245 formElement, requirementsMask, autofillableElements) { |
| 1246 var controlElements = __gCrWeb.common.getFormControlElements(formElement); |
| 1247 |
| 1248 for (var i = 0; i < controlElements.length; ++i) { |
| 1249 var element = controlElements[i]; |
| 1250 if (!__gCrWeb.autofill.isAutofillableElement(element)) { |
| 1251 continue; |
| 1252 } |
| 1253 if (requirementsMask & |
| 1254 __gCrWeb.autofill.REQUIREMENTS_MASK_REQUIRE_AUTOCOMPLETE) { |
| 1255 // Different from method void ExtractAutofillableElements() in |
| 1256 // chromium/src/components/autofill/renderer/form_autofill_util.h, where |
| 1257 // satisfiesRequireAutocomplete() check is only applied on input controls, |
| 1258 // here satisfiesRequireAutocomplete() check is also applied on select |
| 1259 // control element. This is based on the TODO in that file saying "WebKit |
| 1260 // currently doesn't handle the autocomplete attribute for select control |
| 1261 // elements, but it probably should." |
| 1262 if (!__gCrWeb.autofill.satisfiesRequireAutocomplete(element, false)) { |
| 1263 continue; |
| 1264 } |
| 1265 } |
| 1266 autofillableElements.push(element); |
| 1267 } |
| 1268 }; |
| 1269 |
| 1270 /** |
| 1271 * Fills out a FormField object from a given form control element. |
| 1272 * |
| 1273 * It is based on the logic in |
| 1274 * void WebFormControlElementToFormField( |
| 1275 * const blink::WebFormControlElement& element, |
| 1276 * ExtractMask extract_mask, |
| 1277 * FormFieldData* field); |
| 1278 * in chromium/src/components/autofill/renderer/form_autofill_util.h. |
| 1279 * |
| 1280 * @param {Element} element The element to be processed. |
| 1281 * @param {int} extractMask A bit field mask to extract data from |element|. |
| 1282 * See the document on variable __gCrWeb.autofill.EXTRACT_MASK_NONE, |
| 1283 * __gCrWeb.autofill.EXTRACT_MASK_VALUE, |
| 1284 * __gCrWeb.autofill.EXTRACT_MASK_OPTION_TEXT and |
| 1285 * __gCrWeb.autofill.EXTRACT_MASK_OPTIONS. |
| 1286 * @param {Object} field Field to fill in the element information. |
| 1287 */ |
| 1288 __gCrWeb.autofill.webFormControlElementToFormField = function( |
| 1289 element, extractMask, field) { |
| 1290 if (!field || !element) { |
| 1291 return; |
| 1292 } |
| 1293 // The label is not officially part of a form control element; however, the |
| 1294 // labels for all form control elements are scraped from the DOM and set in |
| 1295 // form data. |
| 1296 field['name'] = __gCrWeb['common'].nameForAutofill(element); |
| 1297 field['form_control_type'] = element.type; |
| 1298 var attribute = element.getAttribute('autocomplete'); |
| 1299 if (attribute) { |
| 1300 field['autocomplete_attribute'] = attribute; |
| 1301 } |
| 1302 if (field['autocomplete_attribute'] != null && |
| 1303 field['autocomplete_attribute'].length > |
| 1304 __gCrWeb.autofill.MAX_DATA_LENGTH) { |
| 1305 // Discard overly long attribute values to avoid DOS-ing the browser |
| 1306 // process. However, send over a default string to indicate that the |
| 1307 // attribute was present. |
| 1308 field['autocomplete_attribute'] = 'x-max-data-length-exceeded'; |
| 1309 } |
| 1310 |
| 1311 if (!__gCrWeb.autofill.isAutofillableElement(element)) { |
| 1312 return; |
| 1313 } |
| 1314 |
| 1315 if (__gCrWeb.autofill.isAutofillableInputElement(element) || |
| 1316 __gCrWeb.autofill.isTextAreaElement(element)) { |
| 1317 field['is_autofilled'] = element.isAutofilled; |
| 1318 field['should_autocomplete'] = __gCrWeb.common.autoComplete(element); |
| 1319 // TODO(chenyu): compute this item properly. |
| 1320 // field['is_focusable'] = element.isFocusable; |
| 1321 } |
| 1322 |
| 1323 if (__gCrWeb.autofill.isAutofillableInputElement(element)) { |
| 1324 if (__gCrWeb.autofill.isTextInput(element)) { |
| 1325 field['max_length'] = element.maxLength; |
| 1326 } |
| 1327 field['is_checkable'] = __gCrWeb.autofill.isCheckableElement(element); |
| 1328 } else if (__gCrWeb.autofill.isTextAreaElement(element)) { |
| 1329 // Nothing more to do in this case. |
| 1330 } else if (extractMask & __gCrWeb.autofill.EXTRACT_MASK_OPTIONS) { |
| 1331 __gCrWeb.autofill.getOptionStringsFromElement(element, field); |
| 1332 } |
| 1333 |
| 1334 if (!extractMask & __gCrWeb.autofill.EXTRACT_MASK_VALUE) { |
| 1335 return; |
| 1336 } |
| 1337 |
| 1338 var value = __gCrWeb.autofill.value(element); |
| 1339 |
| 1340 if (!__gCrWeb.autofill.isAutofillableInputElement(element)) { |
| 1341 // Convert the |select_element| value to text if requested. |
| 1342 if (extractMask & __gCrWeb.autofill.EXTRACT_MASK_OPTION_TEXT) { |
| 1343 var options = element.options; |
| 1344 for (var index = 0; index < options.length; ++index) { |
| 1345 var optionElement = options[index]; |
| 1346 if (__gCrWeb.autofill.value(optionElement) === value) { |
| 1347 value = optionElement.text; |
| 1348 break; |
| 1349 } |
| 1350 } |
| 1351 } |
| 1352 } |
| 1353 |
| 1354 // There is a constraint on the maximum data length in method |
| 1355 // WebFormControlElementToFormField() in form_autofill_util.h in order to |
| 1356 // prevent a malicious site from DOS'ing the browser: http://crbug.com/49332, |
| 1357 // which isn't really meaningful here, but we need to follow the same logic to |
| 1358 // get the same form signature wherever possible (to get the benefits of the |
| 1359 // existing crowdsourced field detection corpus). |
| 1360 if (value.length > __gCrWeb.autofill.MAX_DATA_LENGTH) { |
| 1361 value = value.substr(0, __gCrWeb.autofill.MAX_DATA_LENGTH); |
| 1362 } |
| 1363 field['value'] = value; |
| 1364 }; |
| 1365 |
| 1366 /** |
| 1367 * For debugging purposes, annotate forms on the page with prediction data using |
| 1368 * the placeholder attribute. |
| 1369 * |
| 1370 * @param {Object} data The form and field identifiers with their prection data. |
| 1371 */ |
| 1372 __gCrWeb.autofill['fillPredictionData'] = function(data) { |
| 1373 |
| 1374 for (formName in data) { |
| 1375 var form = __gCrWeb.common.getFormElementFromIdentifier(formName); |
| 1376 var formData = data[formName]; |
| 1377 var controlElements = __gCrWeb.common.getFormControlElements(form); |
| 1378 for (var i = 0; i < controlElements.length; ++i) { |
| 1379 var element = controlElements[i]; |
| 1380 if (!__gCrWeb.autofill.isAutofillableElement(element)) { |
| 1381 continue; |
| 1382 } |
| 1383 var elementName = __gCrWeb['common'].nameForAutofill(element); |
| 1384 var value = formData[elementName]; |
| 1385 if (value) { |
| 1386 element.placeholder = value; |
| 1387 } |
| 1388 } |
| 1389 } |
| 1390 }; |
OLD | NEW |