| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/autofill/content/renderer/form_cache.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/autofill/content/renderer/form_autofill_util.h" |
| #include "components/autofill/content/renderer/page_form_analyser_logger.h" |
| #include "components/autofill/core/common/autofill_constants.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/form_data_predictions.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "third_party/blink/public/platform/web_string.h" |
| #include "third_party/blink/public/platform/web_vector.h" |
| #include "third_party/blink/public/web/web_console_message.h" |
| #include "third_party/blink/public/web/web_document.h" |
| #include "third_party/blink/public/web/web_form_control_element.h" |
| #include "third_party/blink/public/web/web_form_element.h" |
| #include "third_party/blink/public/web/web_input_element.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_select_element.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using blink::WebAutofillState; |
| using blink::WebConsoleMessage; |
| using blink::WebDocument; |
| using blink::WebElement; |
| using blink::WebFormControlElement; |
| using blink::WebFormElement; |
| using blink::WebLocalFrame; |
| using blink::WebInputElement; |
| using blink::WebNode; |
| using blink::WebSelectElement; |
| using blink::WebString; |
| using blink::WebVector; |
| |
| namespace autofill { |
| |
| namespace { |
| |
| static const char* kSupportedAutocompleteTypes[] = {"given-name", |
| "additional-name", |
| "family-name", |
| "name", |
| "honorific-suffix", |
| "email", |
| "tel-local", |
| "tel-area-code", |
| "tel-country-code", |
| "tel-national", |
| "tel", |
| "tel-extension", |
| "street-address", |
| "address-line1", |
| "address-line2", |
| "address-line3", |
| "address-level1", |
| "address-level2", |
| "address-level3", |
| "postal-code", |
| "country-name", |
| "cc-name", |
| "cc-given-name", |
| "cc-family-name", |
| "cc-number", |
| "cc-exp-month", |
| "cc-exp-year", |
| "cc-exp", |
| "cc-type", |
| "cc-csc", |
| "organization"}; |
| |
| // For a given |type| (a string representation of enum values), return the |
| // appropriate autocomplete value that should be suggested to the website |
| // developer. |
| const char* MapTypePredictionToAutocomplete(base::StringPiece type) { |
| if (type == "NAME_FIRST") |
| return kSupportedAutocompleteTypes[0]; |
| if (type == "NAME_MIDDLE") |
| return kSupportedAutocompleteTypes[1]; |
| if (type == "NAME_LAST") |
| return kSupportedAutocompleteTypes[2]; |
| if (type == "NAME_FULL") |
| return kSupportedAutocompleteTypes[3]; |
| if (type == "NAME_SUFFIX") |
| return kSupportedAutocompleteTypes[4]; |
| if (type == "EMAIL_ADDRESS") |
| return kSupportedAutocompleteTypes[5]; |
| if (type == "PHONE_HOME_NUMBER") |
| return kSupportedAutocompleteTypes[6]; |
| if (type == "PHONE_HOME_CITY_CODE") |
| return kSupportedAutocompleteTypes[7]; |
| if (type == "PHONE_HOME_COUNTRY_CODE") |
| return kSupportedAutocompleteTypes[8]; |
| if (type == "PHONE_HOME_CITY_AND_NUMBER") |
| return kSupportedAutocompleteTypes[9]; |
| if (type == "PHONE_HOME_WHOLE_NUMBER") |
| return kSupportedAutocompleteTypes[10]; |
| if (type == "PHONE_HOME_EXTENSION") |
| return kSupportedAutocompleteTypes[11]; |
| if (type == "ADDRESS_HOME_STREET_ADDRESS") |
| return kSupportedAutocompleteTypes[12]; |
| if (type == "ADDRESS_HOME_LINE1") |
| return kSupportedAutocompleteTypes[13]; |
| if (type == "ADDRESS_HOME_LINE2") |
| return kSupportedAutocompleteTypes[14]; |
| if (type == "ADDRESS_HOME_LINE3") |
| return kSupportedAutocompleteTypes[15]; |
| if (type == "ADDRESS_HOME_CITY") |
| return kSupportedAutocompleteTypes[16]; |
| if (type == "ADDRESS_HOME_STATE") |
| return kSupportedAutocompleteTypes[17]; |
| if (type == "ADDRESS_HOME_DEPENDENT_LOCALITY") |
| return kSupportedAutocompleteTypes[18]; |
| if (type == "ADDRESS_HOME_ZIP") |
| return kSupportedAutocompleteTypes[19]; |
| if (type == "ADDRESS_HOME_COUNTRY") |
| return kSupportedAutocompleteTypes[20]; |
| if (type == "CREDIT_CARD_NAME_FULL") |
| return kSupportedAutocompleteTypes[21]; |
| if (type == "CREDIT_CARD_NAME_FIRST") |
| return kSupportedAutocompleteTypes[22]; |
| if (type == "CREDIT_CARD_NAME_LAST") |
| return kSupportedAutocompleteTypes[23]; |
| if (type == "CREDIT_CARD_NUMBER") |
| return kSupportedAutocompleteTypes[24]; |
| if (type == "CREDIT_CARD_EXP_MONTH") |
| return kSupportedAutocompleteTypes[25]; |
| if (type == "CREDIT_CARD_EXP_2_DIGIT_YEAR" || |
| type == "CREDIT_CARD_EXP_4_DIGIT_YEAR") |
| return kSupportedAutocompleteTypes[26]; |
| if (type == "CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR" || |
| type == "CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR") |
| return kSupportedAutocompleteTypes[27]; |
| if (type == "CREDIT_CARD_TYPE") |
| return kSupportedAutocompleteTypes[28]; |
| if (type == "CREDIT_CARD_VERIFICATION_CODE") |
| return kSupportedAutocompleteTypes[29]; |
| if (type == "COMPANY_NAME") |
| return kSupportedAutocompleteTypes[30]; |
| return ""; |
| } |
| |
| void LogDeprecationMessages(const WebFormControlElement& element) { |
| std::string autocomplete_attribute = |
| element.GetAttribute("autocomplete").Utf8(); |
| |
| static const char* const deprecated[] = { "region", "locality" }; |
| for (const char* str : deprecated) { |
| if (autocomplete_attribute.find(str) == std::string::npos) |
| continue; |
| std::string msg = std::string("autocomplete='") + str + |
| "' is deprecated and will soon be ignored. See http://goo.gl/YjeSsW"; |
| WebConsoleMessage console_message = WebConsoleMessage( |
| WebConsoleMessage::kLevelWarning, WebString::FromASCII(msg)); |
| element.GetDocument().GetFrame()->AddMessageToConsole(console_message); |
| } |
| } |
| |
| // Determines whether the form is interesting enough to send to the browser |
| // for further operations. |
| bool IsFormInteresting(const FormData& form, size_t num_editable_elements) { |
| if (form.fields.empty()) |
| return false; |
| |
| // If the form has at least one field with an autocomplete attribute, it is a |
| // candidate for autofill. |
| bool all_fields_are_passwords = true; |
| for (const FormFieldData& field : form.fields) { |
| if (!field.autocomplete_attribute.empty()) |
| return true; |
| if (field.form_control_type != "password") |
| all_fields_are_passwords = false; |
| } |
| |
| // If there are no autocomplete attributes, the form needs to have at least |
| // the required number of editable fields for the prediction routines to be a |
| // candidate for autofill. |
| return num_editable_elements >= MinRequiredFieldsForHeuristics() || |
| num_editable_elements >= MinRequiredFieldsForQuery() || |
| num_editable_elements >= MinRequiredFieldsForUpload() || |
| (all_fields_are_passwords && |
| num_editable_elements >= |
| kRequiredFieldsForFormsWithOnlyPasswordFields); |
| } |
| |
| } // namespace |
| |
| FormCache::FormCache(WebLocalFrame* frame) : frame_(frame) {} |
| |
| FormCache::~FormCache() { |
| } |
| |
| std::vector<FormData> FormCache::ExtractNewForms() { |
| std::vector<FormData> forms; |
| WebDocument document = frame_->GetDocument(); |
| if (document.IsNull()) |
| return forms; |
| |
| initial_checked_state_.clear(); |
| initial_select_values_.clear(); |
| WebVector<WebFormElement> web_forms; |
| document.Forms(web_forms); |
| |
| // Log an error message for deprecated attributes, but only the first time |
| // the form is parsed. |
| bool log_deprecation_messages = parsed_forms_.empty(); |
| |
| const form_util::ExtractMask extract_mask = |
| static_cast<form_util::ExtractMask>(form_util::EXTRACT_VALUE | |
| form_util::EXTRACT_OPTIONS); |
| |
| size_t num_fields_seen = 0; |
| for (size_t i = 0; i < web_forms.size(); ++i) { |
| const WebFormElement& form_element = web_forms[i]; |
| |
| std::vector<WebFormControlElement> control_elements = |
| form_util::ExtractAutofillableElementsInForm(form_element); |
| size_t num_editable_elements = |
| ScanFormControlElements(control_elements, log_deprecation_messages); |
| |
| if (num_editable_elements == 0) |
| continue; |
| |
| FormData form; |
| if (!WebFormElementToFormData(form_element, WebFormControlElement(), |
| nullptr, extract_mask, &form, nullptr)) { |
| continue; |
| } |
| |
| num_fields_seen += form.fields.size(); |
| if (num_fields_seen > form_util::kMaxParseableFields) |
| return forms; |
| |
| if (!base::ContainsKey(parsed_forms_, form) && |
| IsFormInteresting(form, num_editable_elements)) { |
| for (auto it = parsed_forms_.begin(); it != parsed_forms_.end(); ++it) { |
| if (it->SameFormAs(form)) { |
| parsed_forms_.erase(it); |
| break; |
| } |
| } |
| |
| SaveInitialValues(control_elements); |
| forms.push_back(form); |
| parsed_forms_.insert(form); |
| } |
| } |
| |
| // Look for more parseable fields outside of forms. |
| std::vector<WebElement> fieldsets; |
| std::vector<WebFormControlElement> control_elements = |
| form_util::GetUnownedAutofillableFormFieldElements(document.All(), |
| &fieldsets); |
| |
| size_t num_editable_elements = |
| ScanFormControlElements(control_elements, log_deprecation_messages); |
| |
| if (num_editable_elements == 0) |
| return forms; |
| |
| FormData synthetic_form; |
| if (!UnownedCheckoutFormElementsAndFieldSetsToFormData( |
| fieldsets, control_elements, nullptr, document, extract_mask, |
| &synthetic_form, nullptr)) { |
| return forms; |
| } |
| |
| num_fields_seen += synthetic_form.fields.size(); |
| if (num_fields_seen > form_util::kMaxParseableFields) |
| return forms; |
| |
| if (!parsed_forms_.count(synthetic_form) && |
| IsFormInteresting(synthetic_form, num_editable_elements)) { |
| SaveInitialValues(control_elements); |
| forms.push_back(synthetic_form); |
| parsed_forms_.insert(synthetic_form); |
| parsed_forms_.erase(synthetic_form_); |
| synthetic_form_ = synthetic_form; |
| } |
| return forms; |
| } |
| |
| void FormCache::Reset() { |
| synthetic_form_ = FormData(); |
| parsed_forms_.clear(); |
| initial_select_values_.clear(); |
| initial_checked_state_.clear(); |
| } |
| |
| bool FormCache::ClearSectionWithElement(const WebFormControlElement& element) { |
| WebFormElement form_element = element.Form(); |
| std::vector<WebFormControlElement> control_elements; |
| if (form_element.IsNull()) { |
| control_elements = form_util::GetUnownedAutofillableFormFieldElements( |
| element.GetDocument().All(), nullptr); |
| } else { |
| control_elements = |
| form_util::ExtractAutofillableElementsInForm(form_element); |
| } |
| for (size_t i = 0; i < control_elements.size(); ++i) { |
| WebFormControlElement control_element = control_elements[i]; |
| // Don't modify the value of disabled fields. |
| if (!control_element.IsEnabled()) |
| continue; |
| |
| // Don't clear field that was not autofilled |
| if (!control_element.IsAutofilled()) |
| continue; |
| |
| if (control_element.AutofillSection() != element.AutofillSection()) |
| continue; |
| |
| control_element.SetAutofillState(WebAutofillState::kNotFilled); |
| |
| WebInputElement* input_element = ToWebInputElement(&control_element); |
| if (form_util::IsTextInput(input_element) || |
| form_util::IsMonthInput(input_element)) { |
| input_element->SetAutofillValue(blink::WebString()); |
| |
| // Clearing the value in the focused node (above) can cause selection |
| // to be lost. We force selection range to restore the text cursor. |
| if (element == *input_element) { |
| int length = input_element->Value().length(); |
| input_element->SetSelectionRange(length, length); |
| } |
| } else if (form_util::IsTextAreaElement(control_element)) { |
| control_element.SetAutofillValue(blink::WebString()); |
| } else if (form_util::IsSelectElement(control_element)) { |
| WebSelectElement select_element = control_element.To<WebSelectElement>(); |
| |
| std::map<const WebSelectElement, base::string16>::const_iterator |
| initial_value_iter = initial_select_values_.find(select_element); |
| if (initial_value_iter != initial_select_values_.end() && |
| select_element.Value().Utf16() != initial_value_iter->second) { |
| select_element.SetAutofillValue( |
| blink::WebString::FromUTF16(initial_value_iter->second)); |
| } |
| } else { |
| WebInputElement input_element = control_element.To<WebInputElement>(); |
| DCHECK(form_util::IsCheckableElement(&input_element)); |
| std::map<const WebInputElement, bool>::const_iterator it = |
| initial_checked_state_.find(input_element); |
| if (it != initial_checked_state_.end() && |
| input_element.IsChecked() != it->second) { |
| input_element.SetChecked(it->second, true); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| bool FormCache::ShowPredictions(const FormDataPredictions& form, |
| bool attach_predictions_to_dom) { |
| DCHECK_EQ(form.data.fields.size(), form.fields.size()); |
| |
| std::vector<WebFormControlElement> control_elements; |
| |
| // First check the synthetic form. |
| bool found_synthetic_form = false; |
| if (form.data.SameFormAs(synthetic_form_)) { |
| found_synthetic_form = true; |
| WebDocument document = frame_->GetDocument(); |
| control_elements = form_util::GetUnownedAutofillableFormFieldElements( |
| document.All(), nullptr); |
| } |
| |
| if (!found_synthetic_form) { |
| // Find the real form by searching through the WebDocuments. |
| bool found_form = false; |
| WebFormElement form_element; |
| WebVector<WebFormElement> web_forms; |
| frame_->GetDocument().Forms(web_forms); |
| |
| for (size_t i = 0; i < web_forms.size(); ++i) { |
| form_element = web_forms[i]; |
| // To match two forms, we look for the form's name and the number of |
| // fields on that form. (Form names may not be unique.) |
| // Note: WebString() == WebString(string16()) does not evaluate to |true| |
| // -- WebKit distinguishes between a "null" string (lhs) and an "empty" |
| // string (rhs). We don't want that distinction, so forcing to string16. |
| base::string16 element_name = form_util::GetFormIdentifier(form_element); |
| if (element_name == form.data.name) { |
| found_form = true; |
| control_elements = |
| form_util::ExtractAutofillableElementsInForm(form_element); |
| if (control_elements.size() == form.fields.size()) |
| break; |
| } |
| } |
| |
| if (!found_form) |
| return false; |
| } |
| |
| if (control_elements.size() != form.fields.size()) { |
| // Keep things simple. Don't show predictions for forms that were modified |
| // between page load and the server's response to our query. |
| return false; |
| } |
| |
| PageFormAnalyserLogger logger(frame_); |
| for (size_t i = 0; i < control_elements.size(); ++i) { |
| WebFormControlElement& element = control_elements[i]; |
| |
| const FormFieldData& field_data = form.data.fields[i]; |
| if (element.NameForAutofill().Utf16() != field_data.name) { |
| // Keep things simple. Don't show predictions for elements whose names |
| // were modified between page load and the server's response to our query. |
| continue; |
| } |
| const FormFieldDataPredictions& field = form.fields[i]; |
| |
| // Possibly add a console warning for this field regarding the usage of |
| // autocomplete attributes. |
| const std::string predicted_autocomplete_attribute = |
| MapTypePredictionToAutocomplete(field.overall_type); |
| if (ShouldShowAutocompleteConsoleWarnings( |
| predicted_autocomplete_attribute, |
| element.GetAttribute("autocomplete").Utf8())) { |
| logger.Send( |
| base::StringPrintf("Input elements should have autocomplete " |
| "attributes (suggested: autocomplete='%s', " |
| "confirm at https://goo.gl/6KgkJg)", |
| predicted_autocomplete_attribute.c_str()), |
| PageFormAnalyserLogger::kVerbose, element); |
| } |
| |
| // If the flag is enabled, attach the prediction to the field. |
| if (attach_predictions_to_dom) { |
| constexpr size_t kMaxLabelSize = 100; |
| const base::string16 truncated_label = field_data.label.substr( |
| 0, std::min(field_data.label.length(), kMaxLabelSize)); |
| |
| // A rough estimate of the maximum title size is: |
| // 8 field titles at <17 chars each |
| // + 7 values at <40 chars each |
| // + 1 truncated label at <kMaxLabelSize; |
| // = 516 chars, rounded up to the next multiple of 64 = 576 |
| // A particularly large parseable name could blow through this and cause |
| // another allocation, but that's OK. |
| constexpr size_t kMaxTitleSize = 576; |
| std::string title; |
| title.reserve(kMaxTitleSize); |
| title += "overall type: "; |
| title += field.overall_type; |
| title += "\nserver type: "; |
| title += field.server_type; |
| title += "\nheuristic type: "; |
| title += field.heuristic_type; |
| title += "\nlabel: "; |
| title += base::UTF16ToUTF8(truncated_label); |
| title += "\nparseable name: "; |
| title += field.parseable_name; |
| title += "\nsection: "; |
| title += field.section; |
| title += "\nfield signature: "; |
| title += field.signature; |
| title += "\nform signature: "; |
| title += form.signature; |
| |
| element.SetAttribute("title", WebString::FromUTF8(title)); |
| |
| element.SetAttribute("autofill-prediction", |
| WebString::FromUTF8(field.overall_type)); |
| } |
| } |
| logger.Flush(); |
| |
| return true; |
| } |
| |
| size_t FormCache::ScanFormControlElements( |
| const std::vector<WebFormControlElement>& control_elements, |
| bool log_deprecation_messages) { |
| size_t num_editable_elements = 0; |
| for (size_t i = 0; i < control_elements.size(); ++i) { |
| const WebFormControlElement& element = control_elements[i]; |
| |
| if (log_deprecation_messages) |
| LogDeprecationMessages(element); |
| |
| // Save original values of <select> elements so we can restore them |
| // when |ClearFormWithNode()| is invoked. |
| if (form_util::IsSelectElement(element) || |
| form_util::IsTextAreaElement(element)) { |
| ++num_editable_elements; |
| } else { |
| const WebInputElement input_element = element.ToConst<WebInputElement>(); |
| if (!form_util::IsCheckableElement(&input_element)) |
| ++num_editable_elements; |
| } |
| } |
| return num_editable_elements; |
| } |
| |
| void FormCache::SaveInitialValues( |
| const std::vector<WebFormControlElement>& control_elements) { |
| for (const WebFormControlElement& element : control_elements) { |
| if (form_util::IsSelectElement(element)) { |
| const WebSelectElement select_element = |
| element.ToConst<WebSelectElement>(); |
| initial_select_values_.insert( |
| std::make_pair(select_element, select_element.Value().Utf16())); |
| } else { |
| const WebInputElement* input_element = ToWebInputElement(&element); |
| if (form_util::IsCheckableElement(input_element)) { |
| initial_checked_state_.insert( |
| std::make_pair(*input_element, input_element->IsChecked())); |
| } |
| } |
| } |
| } |
| |
| bool FormCache::ShouldShowAutocompleteConsoleWarnings( |
| const std::string& predicted_autocomplete, |
| const std::string& actual_autocomplete) { |
| if (!base::FeatureList::IsEnabled( |
| features::kAutofillShowAutocompleteConsoleWarnings)) { |
| return false; |
| } |
| |
| // If we have no better prediction, do not show. |
| if (predicted_autocomplete.empty()) |
| return false; |
| |
| // We should show a warning if the actual autocomplete attribute is empty, |
| // or we recognize the autocomplete attribute, but we think it's the wrong |
| // one. |
| if (actual_autocomplete.empty()) |
| return true; |
| |
| // An autocomplete attribute can be multiple strings (e.g. "shipping name"). |
| // Look at all the tokens. |
| for (base::StringPiece actual : base::SplitStringPiece( |
| actual_autocomplete, " ", base::WhitespaceHandling::TRIM_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_NONEMPTY)) { |
| // If we recognize the value but it's not correct, show a warning. |
| if (base::ContainsValue(kSupportedAutocompleteTypes, actual) && |
| actual != predicted_autocomplete) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace autofill |