| // 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/core/common/form_field_data.h" |
| |
| #include "base/pickle.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_util.h" |
| |
| // TODO(crbug/897756): Clean up the (de)serialization code. |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // Increment this anytime pickle format is modified as well as provide |
| // deserialization routine from previous kFormFieldDataPickleVersion format. |
| const int kFormFieldDataPickleVersion = 8; |
| |
| void AddVectorToPickle(std::vector<base::string16> strings, |
| base::Pickle* pickle) { |
| pickle->WriteInt(static_cast<int>(strings.size())); |
| for (size_t i = 0; i < strings.size(); ++i) { |
| pickle->WriteString16(strings[i]); |
| } |
| } |
| |
| bool ReadStringVector(base::PickleIterator* iter, |
| std::vector<base::string16>* strings) { |
| int size; |
| if (!iter->ReadInt(&size)) |
| return false; |
| |
| base::string16 pickle_data; |
| for (int i = 0; i < size; i++) { |
| if (!iter->ReadString16(&pickle_data)) |
| return false; |
| |
| strings->push_back(pickle_data); |
| } |
| return true; |
| } |
| |
| template <typename T> |
| bool ReadAsInt(base::PickleIterator* iter, T* target_value) { |
| int pickle_data; |
| if (!iter->ReadInt(&pickle_data)) |
| return false; |
| |
| *target_value = static_cast<T>(pickle_data); |
| return true; |
| } |
| |
| bool DeserializeSection1(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadString16(&field_data->label) && |
| iter->ReadString16(&field_data->name) && |
| iter->ReadString16(&field_data->value) && |
| iter->ReadString(&field_data->form_control_type) && |
| iter->ReadString(&field_data->autocomplete_attribute) && |
| iter->ReadUInt64(&field_data->max_length) && |
| iter->ReadBool(&field_data->is_autofilled); |
| } |
| |
| bool DeserializeSection5(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| bool is_checked = false; |
| bool is_checkable = false; |
| const bool success = |
| iter->ReadBool(&is_checked) && iter->ReadBool(&is_checkable); |
| |
| if (success) |
| SetCheckStatus(field_data, is_checkable, is_checked); |
| |
| return success; |
| } |
| |
| bool DeserializeSection6(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return ReadAsInt(iter, &field_data->check_status); |
| } |
| |
| bool DeserializeSection7(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadBool(&field_data->is_focusable) && |
| iter->ReadBool(&field_data->should_autocomplete); |
| } |
| |
| bool DeserializeSection3(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return ReadAsInt(iter, &field_data->text_direction) && |
| ReadStringVector(iter, &field_data->option_values) && |
| ReadStringVector(iter, &field_data->option_contents); |
| } |
| |
| bool DeserializeSection2(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return ReadAsInt(iter, &field_data->role); |
| } |
| |
| bool DeserializeSection4(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadString16(&field_data->placeholder); |
| } |
| |
| bool DeserializeSection8(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadString16(&field_data->css_classes); |
| } |
| |
| bool DeserializeSection9(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadUInt32(&field_data->properties_mask); |
| } |
| |
| bool DeserializeSection10(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadString16(&field_data->id_attribute); |
| } |
| |
| bool DeserializeSection11(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| return iter->ReadString16(&field_data->name_attribute); |
| } |
| |
| bool HaveSameLabel(const FormFieldData& field1, const FormFieldData& field2) { |
| if (field1.label == field2.label) |
| return true; |
| |
| // Assume the labels same if they come from same source but not LABEL tag |
| // when kAutofillSkipComparingInferredLabels is enabled. |
| if (base::FeatureList::IsEnabled( |
| features::kAutofillSkipComparingInferredLabels)) { |
| return field1.label_source == field2.label_source && |
| field1.label_source != FormFieldData::LABEL_TAG; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| FormFieldData::FormFieldData() |
| : max_length(0), |
| is_autofilled(false), |
| check_status(NOT_CHECKABLE), |
| is_focusable(true), |
| should_autocomplete(true), |
| role(ROLE_ATTRIBUTE_OTHER), |
| text_direction(base::i18n::UNKNOWN_DIRECTION), |
| properties_mask(0), |
| is_enabled(false), |
| is_readonly(false), |
| label_source(LabelSource::UNKNOWN) {} |
| |
| FormFieldData::FormFieldData(const FormFieldData& other) = default; |
| |
| FormFieldData::~FormFieldData() {} |
| |
| bool FormFieldData::SameFieldAs(const FormFieldData& field) const { |
| // TODO(crbug.com/896689): On iOS the unique_id member uniquely addresses |
| // this field in the DOM. |
| #if defined(OS_IOS) |
| if (unique_id != field.unique_id) |
| return false; |
| #endif |
| |
| // A FormFieldData stores a value, but the value is not part of the identity |
| // of the field, so we don't want to compare the values. |
| // Similarly, flags like is_enabled, which are only used for parsing but are |
| // not stored persistently, are not used for comparison. |
| // is_autofilled and section are also secondary properties of a field. Two |
| // fields could be the same, and have different sections, because the section |
| // is updated for one, but not for the other. |
| return name == field.name && id_attribute == field.id_attribute && |
| name_attribute == field.name_attribute && |
| form_control_type == field.form_control_type && |
| autocomplete_attribute == field.autocomplete_attribute && |
| placeholder == field.placeholder && max_length == field.max_length && |
| css_classes == field.css_classes && |
| // is_checked and is_autofilled counts as "value" since these change |
| // when we fill things in. |
| IsCheckable(check_status) == IsCheckable(field.check_status) && |
| is_focusable == field.is_focusable && |
| should_autocomplete == field.should_autocomplete && |
| role == field.role && text_direction == field.text_direction && |
| HaveSameLabel(*this, field); |
| // The option values/contents which are the list of items in the list |
| // of a drop-down are currently not considered part of the identity of |
| // a form element. This is debatable, since one might base heuristics |
| // on the types of elements that are available. Alternatively, one |
| // could imagine some forms that dynamically change the element |
| // contents (say, insert years starting from the current year) that |
| // should not be considered changes in the structure of the form. |
| } |
| |
| bool FormFieldData::SimilarFieldAs(const FormFieldData& field) const { |
| return HaveSameLabel(*this, field) && name == field.name && |
| id_attribute == field.id_attribute && |
| name_attribute == field.name_attribute && |
| form_control_type == field.form_control_type && |
| IsCheckable(check_status) == IsCheckable(field.check_status); |
| } |
| |
| bool FormFieldData::DynamicallySameFieldAs(const FormFieldData& field) const { |
| return name == field.name && id_attribute == field.id_attribute && |
| name_attribute == field.name_attribute && |
| HaveSameLabel(*this, field) && IsVisible() == field.IsVisible() && |
| form_control_type == field.form_control_type; |
| } |
| |
| bool FormFieldData::IsTextInputElement() const { |
| return form_control_type == "text" || form_control_type == "password" || |
| form_control_type == "search" || form_control_type == "tel" || |
| form_control_type == "url" || form_control_type == "email"; |
| } |
| |
| bool FormFieldData::operator==(const FormFieldData& field) const { |
| return SameFieldAs(field) && unique_renderer_id == field.unique_renderer_id && |
| is_autofilled == field.is_autofilled && |
| check_status == field.check_status && |
| option_values == field.option_values && |
| option_contents == field.option_contents && |
| properties_mask == field.properties_mask; |
| } |
| |
| bool FormFieldData::operator!=(const FormFieldData& field) const { |
| return !(*this == field); |
| } |
| |
| bool FormFieldData::operator<(const FormFieldData& field) const { |
| // This does not use std::tie() as that generates more implicit variables |
| // than the max-vartrack-size for var-tracking-assignments when compiling |
| // for Android, producing build warnings. (See https://crbug.com/555171 for |
| // context.) |
| |
| // Like SameFieldAs this ignores the value. |
| if (label < field.label) |
| return true; |
| if (label > field.label) |
| return false; |
| if (name < field.name) |
| return true; |
| if (name > field.name) |
| return false; |
| |
| // TODO(crbug.com/896689): On iOS the unique_id member uniquely addresses |
| // this field in the DOM. |
| #if defined(OS_IOS) |
| if (unique_id < field.unique_id) |
| return true; |
| if (unique_id < field.unique_id) |
| return false; |
| #endif |
| |
| if (id_attribute < field.id_attribute) |
| return true; |
| if (id_attribute > field.id_attribute) |
| return false; |
| if (name_attribute < field.name_attribute) |
| return true; |
| if (name_attribute > field.name_attribute) |
| return false; |
| if (form_control_type < field.form_control_type) |
| return true; |
| if (form_control_type > field.form_control_type) |
| return false; |
| if (autocomplete_attribute < field.autocomplete_attribute) |
| return true; |
| if (autocomplete_attribute > field.autocomplete_attribute) |
| return false; |
| if (placeholder < field.placeholder) |
| return true; |
| if (placeholder > field.placeholder) |
| return false; |
| if (max_length < field.max_length) |
| return true; |
| if (max_length > field.max_length) |
| return false; |
| if (css_classes < field.css_classes) |
| return true; |
| if (css_classes > field.css_classes) |
| return false; |
| // Skip |is_checked| and |is_autofilled| as in SameFieldAs. |
| if (IsCheckable(check_status) < IsCheckable(field.check_status)) |
| return true; |
| if (IsCheckable(check_status) > IsCheckable(field.check_status)) |
| return false; |
| if (is_focusable < field.is_focusable) |
| return true; |
| if (is_focusable > field.is_focusable) |
| return false; |
| if (should_autocomplete < field.should_autocomplete) |
| return true; |
| if (should_autocomplete > field.should_autocomplete) |
| return false; |
| if (role < field.role) |
| return true; |
| if (role > field.role) |
| return false; |
| if (text_direction < field.text_direction) |
| return true; |
| if (text_direction > field.text_direction) |
| return false; |
| // See SameFieldAs above for why we don't check option_values/contents and |
| // flags like is_enabled. |
| return false; |
| } |
| |
| void SerializeFormFieldData(const FormFieldData& field_data, |
| base::Pickle* pickle) { |
| pickle->WriteInt(kFormFieldDataPickleVersion); |
| pickle->WriteString16(field_data.label); |
| pickle->WriteString16(field_data.name); |
| pickle->WriteString16(field_data.value); |
| pickle->WriteString(field_data.form_control_type); |
| pickle->WriteString(field_data.autocomplete_attribute); |
| pickle->WriteUInt64(field_data.max_length); |
| pickle->WriteBool(field_data.is_autofilled); |
| pickle->WriteInt(field_data.check_status); |
| pickle->WriteBool(field_data.is_focusable); |
| pickle->WriteBool(field_data.should_autocomplete); |
| pickle->WriteInt(field_data.role); |
| pickle->WriteInt(field_data.text_direction); |
| AddVectorToPickle(field_data.option_values, pickle); |
| AddVectorToPickle(field_data.option_contents, pickle); |
| pickle->WriteString16(field_data.placeholder); |
| pickle->WriteString16(field_data.css_classes); |
| pickle->WriteUInt32(field_data.properties_mask); |
| pickle->WriteString16(field_data.id_attribute); |
| pickle->WriteString16(field_data.name_attribute); |
| } |
| |
| bool DeserializeFormFieldData(base::PickleIterator* iter, |
| FormFieldData* field_data) { |
| int version; |
| FormFieldData temp_form_field_data; |
| if (!iter->ReadInt(&version)) { |
| LOG(ERROR) << "Bad pickle of FormFieldData, no version present"; |
| return false; |
| } |
| |
| switch (version) { |
| case 1: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection5(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 2: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection5(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 3: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection5(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data) || |
| !DeserializeSection4(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 4: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection6(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data) || |
| !DeserializeSection4(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 5: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection6(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data) || |
| !DeserializeSection4(iter, &temp_form_field_data) || |
| !DeserializeSection8(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 6: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection6(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data) || |
| !DeserializeSection4(iter, &temp_form_field_data) || |
| !DeserializeSection8(iter, &temp_form_field_data) || |
| !DeserializeSection9(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 7: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection6(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data) || |
| !DeserializeSection4(iter, &temp_form_field_data) || |
| !DeserializeSection8(iter, &temp_form_field_data) || |
| !DeserializeSection9(iter, &temp_form_field_data) || |
| !DeserializeSection10(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| case 8: { |
| if (!DeserializeSection1(iter, &temp_form_field_data) || |
| !DeserializeSection6(iter, &temp_form_field_data) || |
| !DeserializeSection7(iter, &temp_form_field_data) || |
| !DeserializeSection2(iter, &temp_form_field_data) || |
| !DeserializeSection3(iter, &temp_form_field_data) || |
| !DeserializeSection4(iter, &temp_form_field_data) || |
| !DeserializeSection8(iter, &temp_form_field_data) || |
| !DeserializeSection9(iter, &temp_form_field_data) || |
| !DeserializeSection10(iter, &temp_form_field_data) || |
| !DeserializeSection11(iter, &temp_form_field_data)) { |
| LOG(ERROR) << "Could not deserialize FormFieldData from pickle"; |
| return false; |
| } |
| break; |
| } |
| default: { |
| LOG(ERROR) << "Unknown FormFieldData pickle version " << version; |
| return false; |
| } |
| } |
| *field_data = temp_form_field_data; |
| return true; |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const FormFieldData& field) { |
| const char* check_status_str = nullptr; |
| switch (field.check_status) { |
| case FormFieldData::CheckStatus::NOT_CHECKABLE: |
| check_status_str = "NOT_CHECKABLE"; |
| break; |
| case FormFieldData::CheckStatus::CHECKABLE_BUT_UNCHECKED: |
| check_status_str = "CHECKABLE_BUT_UNCHECKED"; |
| break; |
| case FormFieldData::CheckStatus::CHECKED: |
| check_status_str = "CHECKED"; |
| break; |
| default: |
| NOTREACHED(); |
| check_status_str = "<invalid>"; |
| } |
| |
| const char* role_str = nullptr; |
| switch (field.role) { |
| case FormFieldData::RoleAttribute::ROLE_ATTRIBUTE_PRESENTATION: |
| role_str = "ROLE_ATTRIBUTE_PRESENTATION"; |
| break; |
| case FormFieldData::RoleAttribute::ROLE_ATTRIBUTE_OTHER: |
| role_str = "ROLE_ATTRIBUTE_OTHER"; |
| break; |
| default: |
| NOTREACHED(); |
| role_str = "<invalid>"; |
| } |
| |
| return os << "label='" << base::UTF16ToUTF8(field.label) << "' " |
| << "name='" << base::UTF16ToUTF8(field.name) << "' " |
| << "id_attribute='" << base::UTF16ToUTF8(field.id_attribute) << "' " |
| << "name_attribute='" << base::UTF16ToUTF8(field.name_attribute) |
| << "' " |
| << "value='" << base::UTF16ToUTF8(field.value) << "' " |
| << "control='" << field.form_control_type << "' " |
| << "autocomplete='" << field.autocomplete_attribute << "' " |
| << "placeholder='" << field.placeholder << "' " |
| << "max_length=" << field.max_length << " " |
| << "css_classes='" << field.css_classes << "' " |
| << "autofilled=" << field.is_autofilled << " " |
| << "check_status=" << check_status_str << " " |
| << "is_focusable=" << field.is_focusable << " " |
| << "should_autocomplete=" << field.should_autocomplete << " " |
| << "role=" << role_str << " " |
| << "text_direction=" << field.text_direction << " " |
| << "is_enabled=" << field.is_enabled << " " |
| << "is_readonly=" << field.is_readonly << " " |
| << "typed_value=" << field.typed_value << " " |
| << "properties_mask=" << field.properties_mask << " " |
| << "label_source=" << field.label_source; |
| } |
| |
| } // namespace autofill |