Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/renderer/autofill/form_autofill_util.h" | 5 #include "chrome/renderer/autofill/form_autofill_util.h" |
| 6 | 6 |
| 7 #include <map> | 7 #include <map> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/memory/scoped_vector.h" | 10 #include "base/memory/scoped_vector.h" |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 35 using WebKit::WebNode; | 35 using WebKit::WebNode; |
| 36 using WebKit::WebNodeList; | 36 using WebKit::WebNodeList; |
| 37 using WebKit::WebOptionElement; | 37 using WebKit::WebOptionElement; |
| 38 using WebKit::WebSelectElement; | 38 using WebKit::WebSelectElement; |
| 39 using WebKit::WebString; | 39 using WebKit::WebString; |
| 40 using WebKit::WebVector; | 40 using WebKit::WebVector; |
| 41 | 41 |
| 42 namespace { | 42 namespace { |
| 43 | 43 |
| 44 using autofill::ExtractAutofillableElements; | 44 using autofill::ExtractAutofillableElements; |
| 45 using autofill::IsAutofillableInputElement; | |
| 46 using autofill::IsCheckboxElement; | |
| 47 using autofill::IsRadioButtonElement; | |
| 48 using autofill::IsSelectElement; | |
| 45 using autofill::IsTextInput; | 49 using autofill::IsTextInput; |
| 46 using autofill::IsSelectElement; | |
| 47 | 50 |
| 48 // The maximum length allowed for form data. | 51 // The maximum length allowed for form data. |
| 49 const size_t kMaxDataLength = 1024; | 52 const size_t kMaxDataLength = 1024; |
| 50 | 53 |
| 51 bool IsOptionElement(const WebElement& element) { | 54 bool IsOptionElement(const WebElement& element) { |
| 52 return element.hasTagName("option"); | 55 return element.hasTagName("option"); |
| 53 } | 56 } |
| 54 | 57 |
| 55 bool IsScriptElement(const WebElement& element) { | 58 bool IsScriptElement(const WebElement& element) { |
| 56 return element.hasTagName("script"); | 59 return element.hasTagName("script"); |
| 57 } | 60 } |
| 58 | 61 |
| 59 bool IsNoScriptElement(const WebElement& element) { | 62 bool IsNoScriptElement(const WebElement& element) { |
| 60 return element.hasTagName("noscript"); | 63 return element.hasTagName("noscript"); |
| 61 } | 64 } |
| 62 | 65 |
| 63 bool HasTagName(const WebNode& node, const WebKit::WebString& tag) { | 66 bool HasTagName(const WebNode& node, const WebKit::WebString& tag) { |
| 64 return node.isElementNode() && node.toConst<WebElement>().hasTagName(tag); | 67 return node.isElementNode() && node.toConst<WebElement>().hasTagName(tag); |
| 65 } | 68 } |
| 66 | 69 |
| 67 bool IsAutofillableElement(const WebFormControlElement& element) { | 70 bool IsAutofillableElement(const WebFormControlElement& element) { |
| 68 const WebInputElement* input_element = toWebInputElement(&element); | 71 const WebInputElement* input_element = toWebInputElement(&element); |
| 69 return IsTextInput(input_element) || IsSelectElement(element); | 72 return IsAutofillableInputElement(input_element) || IsSelectElement(element); |
| 70 } | 73 } |
| 71 | 74 |
| 72 // Appends |suffix| to |prefix| so that any intermediary whitespace is collapsed | 75 // Appends |suffix| to |prefix| so that any intermediary whitespace is collapsed |
| 73 // to a single space. If |force_whitespace| is true, then the resulting string | 76 // to a single space. If |force_whitespace| is true, then the resulting string |
| 74 // is guaranteed to have a space between |prefix| and |suffix|. Otherwise, the | 77 // is guaranteed to have a space between |prefix| and |suffix|. Otherwise, the |
| 75 // result includes a space only if |prefix| has trailing whitespace or |suffix| | 78 // result includes a space only if |prefix| has trailing whitespace or |suffix| |
| 76 // has leading whitespace. | 79 // has leading whitespace. |
| 77 // A few examples: | 80 // A few examples: |
| 78 // * CombineAndCollapseWhitespace("foo", "bar", false) -> "foobar" | 81 // * CombineAndCollapseWhitespace("foo", "bar", false) -> "foobar" |
| 79 // * CombineAndCollapseWhitespace("foo", "bar", true) -> "foo bar" | 82 // * CombineAndCollapseWhitespace("foo", "bar", true) -> "foo bar" |
| (...skipping 371 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 451 // rename form fields while the user is interacting with the Autofill | 454 // rename form fields while the user is interacting with the Autofill |
| 452 // popup. I (isherman) am not aware of any such websites, and so am | 455 // popup. I (isherman) am not aware of any such websites, and so am |
| 453 // optimistically including a NOTREACHED(). If you ever trip this check, | 456 // optimistically including a NOTREACHED(). If you ever trip this check, |
| 454 // please file a bug against me. | 457 // please file a bug against me. |
| 455 NOTREACHED(); | 458 NOTREACHED(); |
| 456 continue; | 459 continue; |
| 457 } | 460 } |
| 458 | 461 |
| 459 bool is_initiating_element = (*element == initiating_element); | 462 bool is_initiating_element = (*element == initiating_element); |
| 460 | 463 |
| 461 const WebInputElement* input_element = toWebInputElement(element); | 464 const WebInputElement* input_element = toWebInputElement(element); |
|
Ilya Sherman
2012/12/12 00:26:44
nit: Please move this to be after the comment, i.e
Raman Kakilate
2012/12/12 01:22:04
Done.
| |
| 462 if (IsTextInput(input_element)) { | 465 // Only autofill empty fields and the field that initiated the filling, |
| 463 // Only autofill empty fields and the field that initiated the filling, | 466 // i.e. the field the user is currently editing and interacting with. |
| 464 // i.e. the field the user is currently editing and interacting with. | 467 if (!is_initiating_element && IsTextInput(input_element) && |
| 465 if (!is_initiating_element && !input_element->value().isEmpty()) | 468 !input_element->value().isEmpty()) |
| 466 continue; | 469 continue; |
| 467 } | |
| 468 | 470 |
| 469 if (!element->isEnabled() || element->isReadOnly() || | 471 if (!element->isEnabled() || element->isReadOnly() || |
| 470 (only_focusable_elements && !element->isFocusable())) | 472 (only_focusable_elements && !element->isFocusable())) |
| 471 continue; | 473 continue; |
| 472 | 474 |
| 473 callback(data.fields[i], is_initiating_element, element); | 475 callback(data.fields[i], is_initiating_element, element); |
| 474 } | 476 } |
| 475 } | 477 } |
| 476 | 478 |
| 477 // Sets the |field|'s value to the value in |data|. | 479 // Sets the |field|'s value to the value in |data|. |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 489 // returns the default maxlength value. | 491 // returns the default maxlength value. |
| 490 input_element->setValue( | 492 input_element->setValue( |
| 491 data.value.substr(0, input_element->maxLength()), true); | 493 data.value.substr(0, input_element->maxLength()), true); |
| 492 input_element->setAutofilled(true); | 494 input_element->setAutofilled(true); |
| 493 if (is_initiating_node) { | 495 if (is_initiating_node) { |
| 494 int length = input_element->value().length(); | 496 int length = input_element->value().length(); |
| 495 input_element->setSelectionRange(length, length); | 497 input_element->setSelectionRange(length, length); |
| 496 // Clear the current IME composition (the underline), if there is one. | 498 // Clear the current IME composition (the underline), if there is one. |
| 497 input_element->document().frame()->unmarkText(); | 499 input_element->document().frame()->unmarkText(); |
| 498 } | 500 } |
| 499 } else { | 501 } else if (IsSelectElement(*field)) { |
| 500 DCHECK(IsSelectElement(*field)); | |
| 501 WebSelectElement select_element = field->to<WebSelectElement>(); | 502 WebSelectElement select_element = field->to<WebSelectElement>(); |
| 502 if (select_element.value() != data.value) { | 503 if (select_element.value() != data.value) { |
| 503 select_element.setValue(data.value); | 504 select_element.setValue(data.value); |
| 504 select_element.dispatchFormControlChangeEvent(); | 505 select_element.dispatchFormControlChangeEvent(); |
| 505 } | 506 } |
| 507 } else { | |
| 508 DCHECK(IsRadioButtonElement(input_element) || | |
| 509 IsCheckboxElement(input_element)); | |
| 510 input_element->setChecked(data.is_checked, true); | |
| 506 } | 511 } |
| 507 } | 512 } |
| 508 | 513 |
| 509 // Sets the |field|'s "suggested" (non JS visible) value to the value in |data|. | 514 // Sets the |field|'s "suggested" (non JS visible) value to the value in |data|. |
| 510 // Also sets the "autofilled" attribute, causing the background to be yellow. | 515 // Also sets the "autofilled" attribute, causing the background to be yellow. |
| 511 void PreviewFormField(const FormFieldData& data, | 516 void PreviewFormField(const FormFieldData& data, |
| 512 bool is_initiating_node, | 517 bool is_initiating_node, |
| 513 WebKit::WebFormControlElement* field) { | 518 WebKit::WebFormControlElement* field) { |
| 514 // Nothing to preview. | 519 // Nothing to preview. |
| 515 if (data.value.empty()) | 520 if (data.value.empty()) |
| 516 return; | 521 return; |
| 517 | 522 |
| 518 // Only preview input fields. | 523 // Only preview input fields. Excludes checkboxes and radio buttons, as there |
| 524 // is no provision for setSuggestedCheckedValue in WebInputElement. | |
| 519 WebInputElement* input_element = toWebInputElement(field); | 525 WebInputElement* input_element = toWebInputElement(field); |
| 520 if (!IsTextInput(input_element)) | 526 if (!IsTextInput(input_element)) |
| 521 return; | 527 return; |
| 522 | 528 |
| 523 // If the maxlength attribute contains a negative value, maxLength() | 529 // If the maxlength attribute contains a negative value, maxLength() |
| 524 // returns the default maxlength value. | 530 // returns the default maxlength value. |
| 525 input_element->setSuggestedValue( | 531 input_element->setSuggestedValue( |
| 526 data.value.substr(0, input_element->maxLength())); | 532 data.value.substr(0, input_element->maxLength())); |
| 527 input_element->setAutofilled(true); | 533 input_element->setAutofilled(true); |
| 528 if (is_initiating_node) { | 534 if (is_initiating_node) { |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 544 if (!element) | 550 if (!element) |
| 545 return false; | 551 return false; |
| 546 | 552 |
| 547 return element->isTextField() && !element->isPasswordField(); | 553 return element->isTextField() && !element->isPasswordField(); |
| 548 } | 554 } |
| 549 | 555 |
| 550 bool IsSelectElement(const WebFormControlElement& element) { | 556 bool IsSelectElement(const WebFormControlElement& element) { |
| 551 return element.formControlType() == ASCIIToUTF16("select-one"); | 557 return element.formControlType() == ASCIIToUTF16("select-one"); |
| 552 } | 558 } |
| 553 | 559 |
| 560 bool IsCheckboxElement(const WebInputElement* element) { | |
| 561 if (!element) | |
| 562 return false; | |
| 563 | |
| 564 return element->formControlType() == ASCIIToUTF16("checkbox"); | |
| 565 } | |
| 566 | |
| 567 bool IsRadioButtonElement(const WebInputElement* element) { | |
| 568 if (!element) | |
| 569 return false; | |
| 570 | |
| 571 return element->formControlType() == ASCIIToUTF16("radio"); | |
| 572 } | |
| 573 | |
| 574 bool IsAutofillableInputElement(const WebInputElement* element) { | |
| 575 return IsTextInput(element) || IsCheckboxElement(element) || | |
| 576 IsRadioButtonElement(element); | |
| 577 } | |
| 578 | |
| 554 const string16 GetFormIdentifier(const WebFormElement& form) { | 579 const string16 GetFormIdentifier(const WebFormElement& form) { |
| 555 string16 identifier = form.name(); | 580 string16 identifier = form.name(); |
| 556 if (identifier.empty()) | 581 if (identifier.empty()) |
| 557 identifier = form.getAttribute(WebString("id")); | 582 identifier = form.getAttribute(WebString("id")); |
| 558 | 583 |
| 559 return identifier; | 584 return identifier; |
| 560 } | 585 } |
| 561 | 586 |
| 562 // Fills |autofillable_elements| with all the auto-fillable form control | 587 // Fills |autofillable_elements| with all the auto-fillable form control |
| 563 // elements in |form_element|. | 588 // elements in |form_element|. |
| 564 void ExtractAutofillableElements( | 589 void ExtractAutofillableElements( |
| 565 const WebFormElement& form_element, | 590 const WebFormElement& form_element, |
| 566 RequirementsMask requirements, | 591 RequirementsMask requirements, |
| 567 std::vector<WebFormControlElement>* autofillable_elements) { | 592 std::vector<WebFormControlElement>* autofillable_elements) { |
| 568 WebVector<WebFormControlElement> control_elements; | 593 WebVector<WebFormControlElement> control_elements; |
| 569 form_element.getFormControlElements(control_elements); | 594 form_element.getFormControlElements(control_elements); |
| 570 | 595 |
| 571 autofillable_elements->clear(); | 596 autofillable_elements->clear(); |
| 572 for (size_t i = 0; i < control_elements.size(); ++i) { | 597 for (size_t i = 0; i < control_elements.size(); ++i) { |
| 573 WebFormControlElement element = control_elements[i]; | 598 WebFormControlElement element = control_elements[i]; |
| 574 if (!IsAutofillableElement(element)) | 599 if (!IsAutofillableElement(element)) |
| 575 continue; | 600 continue; |
| 576 | 601 |
| 577 if (requirements & REQUIRE_AUTOCOMPLETE) { | 602 if (requirements & REQUIRE_AUTOCOMPLETE) { |
| 578 // TODO(jhawkins): WebKit currently doesn't handle the autocomplete | 603 // TODO(jhawkins): WebKit currently doesn't handle the autocomplete |
| 579 // attribute for select control elements, but it probably should. | 604 // attribute for select control elements, but it probably should. |
| 580 WebInputElement* input_element = toWebInputElement(&control_elements[i]); | 605 WebInputElement* input_element = toWebInputElement(&control_elements[i]); |
| 581 if (IsTextInput(input_element) && !input_element->autoComplete()) | 606 if (IsAutofillableInputElement(input_element) && |
| 607 !input_element->autoComplete()) | |
| 582 continue; | 608 continue; |
| 583 } | 609 } |
| 584 | 610 |
| 585 autofillable_elements->push_back(element); | 611 autofillable_elements->push_back(element); |
| 586 } | 612 } |
| 587 } | 613 } |
| 588 | 614 |
| 589 void WebFormControlElementToFormField(const WebFormControlElement& element, | 615 void WebFormControlElementToFormField(const WebFormControlElement& element, |
| 590 ExtractMask extract_mask, | 616 ExtractMask extract_mask, |
| 591 FormFieldData* field) { | 617 FormFieldData* field) { |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 603 // Discard overly long attribute values to avoid DOS-ing the browser | 629 // Discard overly long attribute values to avoid DOS-ing the browser |
| 604 // process. However, send over a default string to indicate that the | 630 // process. However, send over a default string to indicate that the |
| 605 // attribute was present. | 631 // attribute was present. |
| 606 field->autocomplete_attribute = "x-max-data-length-exceeded"; | 632 field->autocomplete_attribute = "x-max-data-length-exceeded"; |
| 607 } | 633 } |
| 608 | 634 |
| 609 if (!IsAutofillableElement(element)) | 635 if (!IsAutofillableElement(element)) |
| 610 return; | 636 return; |
| 611 | 637 |
| 612 const WebInputElement* input_element = toWebInputElement(&element); | 638 const WebInputElement* input_element = toWebInputElement(&element); |
| 613 if (IsTextInput(input_element)) { | 639 if (IsAutofillableInputElement(input_element)) { |
| 614 field->max_length = input_element->maxLength(); | 640 field->max_length = input_element->maxLength(); |
| 615 field->is_autofilled = input_element->isAutofilled(); | 641 field->is_autofilled = input_element->isAutofilled(); |
| 616 field->is_focusable = input_element->isFocusable(); | 642 field->is_focusable = input_element->isFocusable(); |
| 617 field->should_autocomplete = input_element->autoComplete(); | 643 field->should_autocomplete = input_element->autoComplete(); |
| 644 if (IsRadioButtonElement(input_element) || | |
| 645 IsCheckboxElement(input_element)) { | |
| 646 field->is_checkable = true; | |
| 647 } | |
|
Ilya Sherman
2012/12/12 00:26:44
nit: It's better to always explicitly set the valu
Raman Kakilate
2012/12/12 01:22:04
Done.
| |
| 618 } else if (extract_mask & EXTRACT_OPTIONS) { | 648 } else if (extract_mask & EXTRACT_OPTIONS) { |
| 619 // Set option strings on the field if available. | 649 // Set option strings on the field if available. |
| 620 DCHECK(IsSelectElement(element)); | 650 DCHECK(IsSelectElement(element)); |
| 621 const WebSelectElement select_element = element.toConst<WebSelectElement>(); | 651 const WebSelectElement select_element = element.toConst<WebSelectElement>(); |
| 622 GetOptionStringsFromElement(select_element, | 652 GetOptionStringsFromElement(select_element, |
| 623 &field->option_values, | 653 &field->option_values, |
| 624 &field->option_contents); | 654 &field->option_contents); |
| 625 } | 655 } |
| 626 | 656 |
| 627 if (!(extract_mask & EXTRACT_VALUE)) | 657 if (!(extract_mask & EXTRACT_VALUE)) |
| 628 return; | 658 return; |
| 629 | 659 |
| 630 string16 value; | 660 string16 value; |
| 631 if (IsTextInput(input_element)) { | 661 if (IsAutofillableInputElement(input_element)) { |
| 632 value = input_element->value(); | 662 value = input_element->value(); |
| 633 } else { | 663 } else { |
| 634 DCHECK(IsSelectElement(element)); | 664 DCHECK(IsSelectElement(element)); |
| 635 const WebSelectElement select_element = element.toConst<WebSelectElement>(); | 665 const WebSelectElement select_element = element.toConst<WebSelectElement>(); |
| 636 value = select_element.value(); | 666 value = select_element.value(); |
| 637 | 667 |
| 638 // Convert the |select_element| value to text if requested. | 668 // Convert the |select_element| value to text if requested. |
| 639 if (extract_mask & EXTRACT_OPTION_TEXT) { | 669 if (extract_mask & EXTRACT_OPTION_TEXT) { |
| 640 WebVector<WebElement> list_items = select_element.listItems(); | 670 WebVector<WebElement> list_items = select_element.listItems(); |
| 641 for (size_t i = 0; i < list_items.size(); ++i) { | 671 for (size_t i = 0; i < list_items.size(); ++i) { |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 698 // requirements and thus will be in the resulting |form|. | 728 // requirements and thus will be in the resulting |form|. |
| 699 std::vector<bool> fields_extracted(control_elements.size(), false); | 729 std::vector<bool> fields_extracted(control_elements.size(), false); |
| 700 | 730 |
| 701 for (size_t i = 0; i < control_elements.size(); ++i) { | 731 for (size_t i = 0; i < control_elements.size(); ++i) { |
| 702 const WebFormControlElement& control_element = control_elements[i]; | 732 const WebFormControlElement& control_element = control_elements[i]; |
| 703 | 733 |
| 704 if (!IsAutofillableElement(control_element)) | 734 if (!IsAutofillableElement(control_element)) |
| 705 continue; | 735 continue; |
| 706 | 736 |
| 707 const WebInputElement* input_element = toWebInputElement(&control_element); | 737 const WebInputElement* input_element = toWebInputElement(&control_element); |
| 708 if (requirements & REQUIRE_AUTOCOMPLETE && IsTextInput(input_element) && | 738 if (requirements & REQUIRE_AUTOCOMPLETE && |
| 739 IsAutofillableInputElement(input_element) && | |
| 709 !input_element->autoComplete()) | 740 !input_element->autoComplete()) |
| 710 continue; | 741 continue; |
| 711 | 742 |
| 712 // Create a new FormFieldData, fill it out and map it to the field's name. | 743 // Create a new FormFieldData, fill it out and map it to the field's name. |
| 713 FormFieldData* form_field = new FormFieldData; | 744 FormFieldData* form_field = new FormFieldData; |
| 714 WebFormControlElementToFormField(control_element, extract_mask, form_field); | 745 WebFormControlElementToFormField(control_element, extract_mask, form_field); |
| 715 form_fields.push_back(form_field); | 746 form_fields.push_back(form_field); |
| 716 // TODO(jhawkins): A label element is mapped to a form control element's id. | 747 // TODO(jhawkins): A label element is mapped to a form control element's id. |
| 717 // field->name() will contain the id only if the name does not exist. Add | 748 // field->name() will contain the id only if the name does not exist. Add |
| 718 // an id() method to WebFormControlElement and use that here. | 749 // an id() method to WebFormControlElement and use that here. |
| (...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 900 bool FormWithElementIsAutofilled(const WebInputElement& element) { | 931 bool FormWithElementIsAutofilled(const WebInputElement& element) { |
| 901 WebFormElement form_element = element.form(); | 932 WebFormElement form_element = element.form(); |
| 902 if (form_element.isNull()) | 933 if (form_element.isNull()) |
| 903 return false; | 934 return false; |
| 904 | 935 |
| 905 std::vector<WebFormControlElement> control_elements; | 936 std::vector<WebFormControlElement> control_elements; |
| 906 ExtractAutofillableElements(form_element, REQUIRE_AUTOCOMPLETE, | 937 ExtractAutofillableElements(form_element, REQUIRE_AUTOCOMPLETE, |
| 907 &control_elements); | 938 &control_elements); |
| 908 for (size_t i = 0; i < control_elements.size(); ++i) { | 939 for (size_t i = 0; i < control_elements.size(); ++i) { |
| 909 WebInputElement* input_element = toWebInputElement(&control_elements[i]); | 940 WebInputElement* input_element = toWebInputElement(&control_elements[i]); |
| 910 if (!IsTextInput(input_element)) | 941 if (!IsAutofillableInputElement(input_element)) |
| 911 continue; | 942 continue; |
| 912 | 943 |
| 913 if (input_element->isAutofilled()) | 944 if (input_element->isAutofilled()) |
| 914 return true; | 945 return true; |
| 915 } | 946 } |
| 916 | 947 |
| 917 return false; | 948 return false; |
| 918 } | 949 } |
| 919 | 950 |
| 920 } // namespace autofill | 951 } // namespace autofill |
| OLD | NEW |