blob: 62c74a71ec5b32a7662916a3b1d07e63f4dec8a9 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include <stddef.h>
#include <algorithm>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/format_macros.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "components/autofill/content/renderer/autofill_agent_test_api.h"
#include "components/autofill/content/renderer/autofill_renderer_test.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/form_cache.h"
#include "components/autofill/content/renderer/test_utils.h"
#include "components/autofill/core/common/autocomplete_parsing_util.h"
#include "components/autofill/core/common/autofill_data_validation.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/field_data_manager.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_test_api.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#include "components/autofill/core/common/unique_ids.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/web_autofill_state.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/web_element_collection.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_script_source.h"
#include "third_party/blink/public/web/web_select_element.h"
#if BUILDFLAG(IS_WIN)
#include "third_party/blink/public/web/win/web_font_rendering.h"
#endif
using base::ASCIIToUTF16;
using blink::WebAutofillState;
using blink::WebDocument;
using blink::WebElement;
using blink::WebFormControlElement;
using blink::WebFormElement;
using blink::WebInputElement;
using blink::WebLocalFrame;
using blink::WebSelectElement;
using blink::WebString;
using testing::_;
using testing::ElementsAre;
using testing::Field;
using testing::Optional;
using testing::Pair;
using testing::Property;
namespace autofill::form_util {
namespace {
struct AutofillFieldCase {
FormControlType form_control_type;
const char* const id_attribute;
const char* const initial_value;
bool should_be_autofilled; // Whether the filed should be autofilled.
const char* const autofill_value; // The value being used to fill the field.
const char* const expected_value; // The expected value after Autofill
// or Preview.
};
struct WebElementDescriptor {
enum RetrievalMethod {
CSS_SELECTOR,
ID,
NONE,
};
// Information to retrieve element with.
std::string descriptor;
// Which retrieval method to use.
RetrievalMethod retrieval_method = NONE;
};
const char kFormHtml[] =
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname>
<input id=lastname>
<input type=hidden id=imhidden>
<input id=notempty value=Hi>
<input autocomplete=off id=noautocomplete>
<input disabled=disabled id=notenabled>
<input readonly id=readonly>
<input style='visibility: hidden' id=invisible>
<input style='display: none' id=displaynone>
<input type=month id=month>
<input type=month id='month-nonempty' value='2011-12'>
<select id=select>
<option></option>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-nonempty'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-unchanged'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-displaynone' style='display:none'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<textarea id=textarea></textarea>
<textarea id='textarea-nonempty'>Go&#10;away!</textarea>
<input type=submit name='reply-send' value=Send>
</form>)";
// This constant uses a mixed-case title tag to be sure that the title match is
// not case-sensitive. Other tests in this file use an all-lower title tag.
const char kUnownedFormHtml[] =
R"(<head><title>Enter Shipping Info</title></head>
<input id=firstname>
<input id=lastname>
<input type=hidden id=imhidden>
<input id=notempty value=Hi>
<input autocomplete=off id=noautocomplete>
<input disabled=disabled id=notenabled>
<input readonly id=readonly>
<input style='visibility: hidden' id=invisible>
<input style='display: none' id=displaynone>
<input type=month id=month>
<input type=month id='month-nonempty' value='2011-12'>
<select id=select>
<option></option>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-nonempty'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-unchanged'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-displaynone' style='display:none'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<textarea id=textarea></textarea>
<textarea id='textarea-nonempty'>Go&#10;away!</textarea>
<input type=submit name='reply-send' value=Send>)";
// This constant has no title tag, and should be passed to
// LoadHTMLWithURLOverride to test the detection of unowned forms by URL.
const char kUnownedUntitledFormHtml[] =
R"(<input id=firstname>
<input id=lastname>
<input type=hidden id=imhidden>
<input id=notempty value=Hi>
<input autocomplete=off id=noautocomplete>
<input disabled=disabled id=notenabled>
<input readonly id=readonly>
<input style='visibility: hidden' id=invisible>
<input style='display: none' id=displaynone>
<input type=month id=month>
<input type=month id='month-nonempty' value='2011-12'>
<select id=select>
<option></option>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-nonempty'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-unchanged'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-displaynone' style='display:none'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<textarea id=textarea></textarea>
<textarea id='textarea-nonempty'>Go&#10;away!</textarea>
<input type=submit name='reply-send' value=Send>)";
// This constant does not have a title tag, but should match an unowned form
// anyway because it is not English.
const char kUnownedNonEnglishFormHtml[] =
R"(<html lang=fr>
<input id=firstname>
<input id=lastname>
<input type=hidden id=imhidden>
<input id=notempty value=Hi>
<input autocomplete=off id=noautocomplete>
<input disabled=disabled id=notenabled>
<input readonly id=readonly>
<input style='visibility: hidden' id=invisible>
<input style='display: none' id=displaynone>
<input type=month id=month>
<input type=month id='month-nonempty' value='2011-12'>
<select id=select>
<option></option>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-nonempty'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-unchanged'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<select id='select-displaynone' style='display:none'>
<option value=CA selected>California</option>
<option value=TX>Texas</option>
</select>
<textarea id=textarea></textarea>
<textarea id='textarea-nonempty'>Go&#10;away!</textarea>
<input type=submit name='reply-send' value=Send>
</html>)";
std::string RetrievalMethodToString(
const WebElementDescriptor::RetrievalMethod& method) {
switch (method) {
case WebElementDescriptor::CSS_SELECTOR:
return "CSS_SELECTOR";
case WebElementDescriptor::ID:
return "ID";
case WebElementDescriptor::NONE:
return "NONE";
}
NOTREACHED();
}
bool ClickElement(const WebDocument& document,
const WebElementDescriptor& element_descriptor) {
WebString web_descriptor = WebString::FromUTF8(element_descriptor.descriptor);
blink::WebElement element;
switch (element_descriptor.retrieval_method) {
case WebElementDescriptor::CSS_SELECTOR: {
element = document.QuerySelector(web_descriptor);
break;
}
case WebElementDescriptor::ID:
element = document.GetElementById(web_descriptor);
break;
case WebElementDescriptor::NONE:
return true;
}
if (!element) {
DVLOG(1) << "Could not find "
<< element_descriptor.descriptor
<< " by "
<< RetrievalMethodToString(element_descriptor.retrieval_method)
<< ".";
return false;
}
element.SimulateClick();
return true;
}
void ApplyFieldsAction(
const blink::WebDocument& document,
base::span<const FormFieldData> fields,
mojom::ActionPersistence action_persistence,
mojom::FormActionType action_type = mojom::FormActionType::kFill) {
std::vector<FormFieldData::FillData> filling_fields;
filling_fields.reserve(fields.size());
for (const FormFieldData& field : fields) {
filling_fields.emplace_back(field);
}
form_util::ApplyFieldsAction(document, filling_fields, action_type,
action_persistence,
*base::MakeRefCounted<FieldDataManager>());
}
static constexpr CallTimerState kExtractFormDataCallTimerStateDummy = {
.call_site = CallTimerState::CallSite::kUpdateFormCache,
.last_autofill_agent_reset = {},
.last_dom_content_loaded = {},
};
static constexpr CallTimerState kUpdateFormCacheCallTimerStateDummy = {
.call_site = CallTimerState::CallSite::kExtractForms,
.last_autofill_agent_reset = {},
.last_dom_content_loaded = {},
};
FormData FindForm(const blink::WebFormControlElement& element) {
if (auto p = FindFormAndFieldForFormControlElement(
element, *base::MakeRefCounted<FieldDataManager>(),
kExtractFormDataCallTimerStateDummy, /*extract_options=*/{},
/*form_cache=*/{})) {
return p->first;
}
return FormData();
}
// TODO(crbug.com/40765988): Replace this with FormData::DeepEqual().
#define EXPECT_FORM_FIELD_DATA_EQUALS(expected, actual) \
do { \
EXPECT_EQ(expected.label(), actual.label()); \
EXPECT_EQ(expected.name(), actual.name()); \
EXPECT_EQ(expected.value(), actual.value()); \
EXPECT_EQ(expected.form_control_type(), actual.form_control_type()); \
EXPECT_EQ(expected.autocomplete_attribute(), \
actual.autocomplete_attribute()); \
EXPECT_EQ(expected.parsed_autocomplete(), actual.parsed_autocomplete()); \
EXPECT_EQ(expected.placeholder(), actual.placeholder()); \
EXPECT_EQ(expected.max_length(), actual.max_length()); \
EXPECT_EQ(expected.css_classes(), actual.css_classes()); \
EXPECT_EQ(expected.is_autofilled(), actual.is_autofilled()); \
EXPECT_EQ(expected.is_user_edited(), actual.is_user_edited()); \
EXPECT_EQ(expected.section(), actual.section()); \
EXPECT_EQ(expected.check_status(), actual.check_status()); \
EXPECT_EQ(expected.properties_mask(), actual.properties_mask()); \
EXPECT_EQ(expected.id_attribute(), actual.id_attribute()); \
EXPECT_EQ(expected.name_attribute(), actual.name_attribute()); \
} while (0)
class FormAutofillTest : public test::AutofillRendererTest {
public:
FormAutofillTest() = default;
FormAutofillTest(const FormAutofillTest&) = delete;
FormAutofillTest& operator=(const FormAutofillTest&) = delete;
~FormAutofillTest() override = default;
void SetUp() override {
test::AutofillRendererTest::SetUp();
form_cache_.emplace(&autofill_agent());
#if BUILDFLAG(IS_WIN)
// Autofill uses the system font to render suggestion previews. On Windows
// an extra step is required to ensure that the system font is configured.
blink::WebFontRendering::SetMenuFontMetrics(
blink::WebString::FromASCII("Arial"), 12);
#endif
}
void TearDown() override {
form_cache_.reset();
test::AutofillRendererTest::TearDown();
}
std::optional<FormData> ExtractFormData(
WebFormElement form,
DenseSet<ExtractOption> extract_options = {}) {
return form_util::ExtractFormData(
GetDocument(), form, *base::MakeRefCounted<FieldDataManager>(),
kExtractFormDataCallTimerStateDummy, extract_options);
}
std::optional<std::pair<FormData, raw_ref<const FormFieldData>>>
FindFormAndFieldForFormControlElement(
WebFormControlElement control,
DenseSet<ExtractOption> extract_options = {}) {
return form_util::FindFormAndFieldForFormControlElement(
control, *base::MakeRefCounted<FieldDataManager>(),
kExtractFormDataCallTimerStateDummy, extract_options,
/*form_cache=*/{});
}
FormCache::UpdateFormCacheResult UpdateFormCache() {
return form_cache_->UpdateFormCache(
*base::MakeRefCounted<FieldDataManager>(),
kUpdateFormCacheCallTimerStateDummy);
}
void ExpectLabels(const char* html,
const std::vector<std::u16string>& id_attributes,
const std::vector<std::u16string>& name_attributes,
const std::vector<std::u16string>& labels,
const std::vector<std::u16string>& names,
const std::vector<std::u16string>& values) {
ASSERT_EQ(labels.size(), id_attributes.size());
ASSERT_EQ(labels.size(), name_attributes.size());
ASSERT_EQ(labels.size(), names.size());
ASSERT_EQ(labels.size(), values.size());
std::vector<FormFieldData> fields;
for (size_t i = 0; i < labels.size(); ++i) {
FormFieldData expected;
expected.set_id_attribute(id_attributes[i]);
expected.set_name_attribute(name_attributes[i]);
expected.set_label(labels[i]);
expected.set_name(names[i]);
expected.set_value(values[i]);
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
fields.push_back(expected);
}
ExpectLabelsAndTypes(html, fields);
}
void ExpectLabelsAndTypes(const char* html,
const std::vector<FormFieldData>& fields) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
const FormData& form = forms[0];
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://cnn.com"), form.action());
ASSERT_EQ(fields.size(), form.fields().size());
for (size_t i = 0; i < fields.size(); ++i) {
SCOPED_TRACE(base::StringPrintf("i: %" PRIuS, i));
EXPECT_FORM_FIELD_DATA_EQUALS(fields[i], form.fields()[i]);
}
}
// Use this validator when the test HTML uses the id attribute instead of
// the name attribute to identify the input fields. Otherwise, this is the
// same text structure as ExpectJohnSmithLabelsAndNameAttributes().
void ExpectJohnSmithLabelsAndIdAttributes(const char* html) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.emplace_back();
labels.push_back(u"First name:");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.emplace_back();
labels.push_back(u"Last name:");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.emplace_back();
labels.push_back(u"Email:");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(html, id_attributes, name_attributes, labels, names, values);
}
// Use this validator when the test HTML uses the name attribute instead of
// the id attribute to identify the input fields. Otherwise, this is the same
// text structure as ExpectJohnSmithLabelsAndIdAttributes().
void ExpectJohnSmithLabelsAndNameAttributes(const char* html) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.emplace_back();
name_attributes.push_back(u"firstname");
labels.push_back(u"First name:");
names.push_back(name_attributes.back());
values.push_back(u"John");
id_attributes.emplace_back();
name_attributes.push_back(u"lastname");
labels.push_back(u"Last name:");
names.push_back(name_attributes.back());
values.push_back(u"Smith");
id_attributes.emplace_back();
name_attributes.push_back(u"email");
labels.push_back(u"Email:");
names.push_back(name_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(html, id_attributes, name_attributes, labels, names, values);
}
typedef WebString (*GetValueFunction)(WebFormControlElement element);
// Test FormFill* functions.
void TestFormFillFunctions(const char* html,
bool unowned,
const char* url_override,
const AutofillFieldCase* field_cases,
size_t number_of_field_cases,
mojom::ActionPersistence action_persistence,
GetValueFunction get_value_function) {
if (url_override) {
LoadHTMLWithUrlOverride(html, url_override);
} else {
LoadHTML(html);
}
// Find the form to fill.
WebInputElement input_element = GetInputElementById("firstname");
FormData form = FindForm(input_element);
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(number_of_field_cases, fields.size());
// Verify the initial state of the form and setup filling data.
for (size_t i = 0; i < number_of_field_cases; ++i) {
SCOPED_TRACE(base::StringPrintf("Verify initial value for field %s",
field_cases[i].id_attribute));
EXPECT_EQ(field_cases[i].form_control_type,
fields[i].form_control_type());
EXPECT_EQ(base::UTF8ToUTF16(field_cases[i].id_attribute),
fields[i].id_attribute());
EXPECT_EQ(base::UTF8ToUTF16(field_cases[i].initial_value),
fields[i].value());
test_api(form).field(i).set_value(
ASCIIToUTF16(field_cases[i].autofill_value));
test_api(form).field(i).set_is_autofilled(true);
}
// Fill and validate.
ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
action_persistence);
for (size_t i = 0; i < number_of_field_cases; ++i) {
ValidateFilledField(field_cases[i], get_value_function,
action_persistence);
}
}
// Validate an Autofilled field.
void ValidateFilledField(const AutofillFieldCase& field_case,
GetValueFunction get_value_function,
mojom::ActionPersistence action_persistence) {
SCOPED_TRACE(base::StringPrintf("Verify autofilled value for field %s",
field_case.id_attribute));
WebFormControlElement element =
GetFormControlElementById(field_case.id_attribute);
EXPECT_EQ(field_case.expected_value, get_value_function(element).Utf8());
EXPECT_EQ(field_case.should_be_autofilled,
action_persistence == mojom::ActionPersistence::kFill
? element.IsAutofilled()
: element.IsPreviewed());
}
void TestFillForm(const char* html, bool unowned, const char* url_override) {
static const AutofillFieldCase field_cases[] = {
// Fields: form_control_type, name, initial_value,
// autocomplete_attribute, should_be_autofilled, autofill_value,
// expected_value.
// Regular empty fields (firstname & lastname) should be autofilled.
{FormControlType::kInputText, "firstname", "", true, "filled firstname",
"filled firstname"},
{FormControlType::kInputText, "lastname", "", true, "filled lastname",
"filled lastname"},
{FormControlType::kInputText, "notempty", "Hi", true, "filled notempty",
"filled notempty"},
{FormControlType::kInputText, "noautocomplete", "", true,
"filled noautocomplete", "filled noautocomplete"},
// Disabled fields should not be autofilled.
{FormControlType::kInputText, "notenabled", "", false,
"filled notenabled", ""},
// Readonly fields should not be autofilled.
{FormControlType::kInputText, "readonly", "", false, "filled readonly",
""},
// Fields with "visibility: hidden" should not be autofilled.
{FormControlType::kInputText, "invisible", "", false,
"filled invisible", ""},
// Fields with "display:none" should not be autofilled.
{FormControlType::kInputText, "displaynone", "", false,
"filled displaynone", ""},
// Regular <input type=month> should be autofilled.
{FormControlType::kInputMonth, "month", "", true, "2017-11", "2017-11"},
{FormControlType::kInputMonth, "month-nonempty", "2011-12", true,
"2017-11", "2017-11"},
// Regular select fields should be autofilled.
{FormControlType::kSelectOne, "select", "", true, "TX", "TX"},
// Select fields should be autofilled even if they already have a
// non-empty value.
{FormControlType::kSelectOne, "select-nonempty", "CA", true, "TX",
"TX"},
{FormControlType::kSelectOne, "select-unchanged", "CA", true, "CA",
"CA"},
// Select fields that are not focusable should be filled.
{FormControlType::kSelectOne, "select-displaynone", "CA", true, "TX",
"TX"},
// Regular textarea elements should be autofilled.
{FormControlType::kTextArea, "textarea", "", true,
"some multi-\nline value", "some multi-\nline value"},
{FormControlType::kTextArea, "textarea-nonempty", "Go\naway!", true,
"some multi-\nline value", "some multi-\nline value"},
};
TestFormFillFunctions(html, unowned, url_override, field_cases,
std::size(field_cases),
mojom::ActionPersistence::kFill, &GetValueWrapper);
WebInputElement firstname = GetInputElementById("firstname");
EXPECT_EQ(16u, firstname.SelectionStart());
EXPECT_EQ(16u, firstname.SelectionEnd());
}
void TestPreviewForm(const char* html, bool unowned,
const char* url_override) {
static const AutofillFieldCase field_cases[] = {
// Normal empty fields should be previewed.
{FormControlType::kInputText, "firstname", "", true,
"suggested firstname", "suggested firstname"},
{FormControlType::kInputText, "lastname", "", true,
"suggested lastname", "suggested lastname"},
{FormControlType::kInputText, "notempty", "Hi", true,
"suggested notempty", "suggested notempty"},
{FormControlType::kInputText, "noautocomplete", "", true,
"filled noautocomplete", "filled noautocomplete"},
// Disabled fields should not be previewed.
{FormControlType::kInputText, "notenabled", "", false,
"suggested notenabled", ""},
// Readonly fields should not be previewed.
{FormControlType::kInputText, "readonly", "", false,
"suggested readonly", ""},
// Fields with "visibility: hidden" should not be previewed.
{FormControlType::kInputText, "invisible", "", false,
"suggested invisible", ""},
// Fields with "display:none" should not previewed.
{FormControlType::kInputText, "displaynone", "", false,
"suggested displaynone", ""},
// Regular <input type=month> should be previewed.
{FormControlType::kInputMonth, "month", "", true, "2017-11", "2017-11"},
{FormControlType::kInputMonth, "month-nonempty", "2011-12", true,
"2017-11", "2017-11"},
// Regular select fields should be previewed.
{FormControlType::kSelectOne, "select", "", true, "TX", "TX"},
// Select fields should be previewed even if they already have a
// non-empty value.
{FormControlType::kSelectOne, "select-nonempty", "CA", true, "TX",
"TX"},
// Select fields should be previewed even if no suggestion is passed.
{FormControlType::kSelectOne, "select-unchanged", "CA", true, "", ""},
// Select fields that are not focusable should always be filled.
{FormControlType::kSelectOne, "select-displaynone", "CA", true, "CA",
"CA"},
// Normal textarea elements should be previewed.
{FormControlType::kTextArea, "textarea", "", true,
"suggested multi-\nline value", "suggested multi-\nline value"},
// Nonempty textarea elements should not be previewed.
{FormControlType::kTextArea, "textarea-nonempty", "Go\naway!", true,
"suggested multi-\nline value", "suggested multi-\nline value"},
};
TestFormFillFunctions(
html, unowned, url_override, field_cases, std::size(field_cases),
mojom::ActionPersistence::kPreview, &GetSuggestedValueWrapper);
// Verify preview selection.
WebInputElement firstname = GetInputElementById("firstname");
// Since the suggestion is previewed as a placeholder, there should be no
// selected text.
EXPECT_EQ(0u, firstname.SelectionStart());
EXPECT_EQ(0u, firstname.SelectionEnd());
}
void TestFindFormForInputElement(const char* html, bool unowned) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("firstname");
// Find the form and verify it's the correct form.
FormData form = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(4U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"John");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Smith");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"john@example.com");
expected.set_label(u"john@example.com");
expected.set_autocomplete_attribute("off");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
expected.set_autocomplete_attribute({});
expected.set_id_attribute(u"phone");
expected.set_name(expected.id_attribute());
expected.set_value(u"1.800.555.1234");
expected.set_label(u"1.800.555.1234");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
}
void TestFindFormForTextAreaElement(const char* html, bool unowned) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the textarea element we want to find.
WebElement element = GetDocument().GetElementById("street-address");
WebFormControlElement textarea_element =
element.To<WebFormControlElement>();
// Find the form and verify it's the correct form.
FormData form = FindForm(textarea_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(4U, fields.size());
FormFieldData expected;
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"John");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Smith");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"john@example.com");
expected.set_label(u"john@example.com");
expected.set_autocomplete_attribute("off");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
expected.set_autocomplete_attribute({});
expected.set_id_attribute(u"street-address");
expected.set_name(expected.id_attribute());
expected.set_value(u"123 Fantasy Ln.\nApt. 42");
expected.set_label({});
expected.set_form_control_type(FormControlType::kTextArea);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
}
void TestFillFormMaxLength(const char* html, bool unowned) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("firstname");
// Find the form that contains the input element.
FormData form = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_max_length(5);
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_max_length(7);
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_max_length(9);
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
// Fill the form.
test_api(form).field(0).set_value(u"Brother");
test_api(form).field(1).set_value(u"Jonathan");
test_api(form).field(2).set_value(u"brotherj@example.com");
test_api(form).field(0).set_is_autofilled(true);
test_api(form).field(1).set_is_autofilled(true);
test_api(form).field(2).set_is_autofilled(true);
ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
}
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
expected.set_form_control_type(FormControlType::kInputText);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Broth");
expected.set_max_length(5);
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Jonatha");
expected.set_max_length(7);
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"brotherj@");
expected.set_max_length(9);
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
}
void TestFillFormNegativeMaxLength(const char* html, bool unowned) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("firstname");
// Find the form that contains the input element.
FormData form = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
// Fill the form.
test_api(form).field(0).set_value(u"Brother");
test_api(form).field(1).set_value(u"Jonathan");
test_api(form).field(2).set_value(u"brotherj@example.com");
ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
}
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Brother");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Jonathan");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"brotherj@example.com");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
}
void TestFillFormEmptyName(const char* html, bool unowned) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("firstname");
// Find the form that contains the input element.
FormData form = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
// Fill the form.
test_api(form).field(0).set_value(u"Wyatt");
test_api(form).field(1).set_value(u"Earp");
test_api(form).field(2).set_value(u"wyatt@example.com");
ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
}
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Wyatt");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Earp");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"wyatt@example.com");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
}
void TestFillFormEmptyFormNames(const char* html, bool unowned) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
const size_t expected_size = unowned ? 1 : 2;
ASSERT_EQ(expected_size, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("apple");
// Find the form that contains the input element.
FormData form = FindForm(input_element);
if (!unowned) {
EXPECT_TRUE(form.name().empty());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
const size_t unowned_offset = unowned ? 3 : 0;
ASSERT_EQ(unowned_offset + 3, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"apple");
expected.set_name(expected.id_attribute());
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[unowned_offset]);
expected.set_id_attribute(u"banana");
expected.set_name(expected.id_attribute());
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[unowned_offset + 1]);
expected.set_id_attribute(u"cantelope");
expected.set_name(expected.id_attribute());
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[unowned_offset + 2]);
// Fill the form.
test_api(form).field(unowned_offset + 0).set_value(u"Red");
test_api(form).field(unowned_offset + 1).set_value(u"Yellow");
test_api(form).field(unowned_offset + 2).set_value(u"Also Yellow");
test_api(form).field(unowned_offset + 0).set_is_autofilled(true);
test_api(form).field(unowned_offset + 1).set_is_autofilled(true);
test_api(form).field(unowned_offset + 2).set_is_autofilled(true);
ExecuteJavaScriptForTests("document.getElementById('apple').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
if (!unowned) {
EXPECT_TRUE(form2.name().empty());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
}
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(unowned_offset + 3, fields2.size());
expected.set_id_attribute(u"apple");
expected.set_name(expected.id_attribute());
expected.set_value(u"Red");
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[unowned_offset + 0]);
expected.set_id_attribute(u"banana");
expected.set_name(expected.id_attribute());
expected.set_value(u"Yellow");
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[unowned_offset + 1]);
expected.set_id_attribute(u"cantelope");
expected.set_name(expected.id_attribute());
expected.set_value(u"Also Yellow");
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[unowned_offset + 2]);
}
void TestFillFormNonEmptyField(const char* html,
bool unowned,
const char* initial_lastname,
const char* initial_email,
const char* placeholder_firstname,
const char* placeholder_lastname,
const char* placeholder_email) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("firstname");
// Simulate typing by modifying the field value.
input_element.SetValue(WebString::FromASCII("Wy"));
// Find the form that contains the input element.
FormData form = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
}
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Wy");
if (placeholder_firstname) {
expected.set_label(ASCIIToUTF16(placeholder_firstname));
expected.set_placeholder(ASCIIToUTF16(placeholder_firstname));
}
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
if (initial_lastname) {
expected.set_label(ASCIIToUTF16(initial_lastname));
expected.set_value(ASCIIToUTF16(initial_lastname));
} else if (placeholder_lastname) {
expected.set_label(ASCIIToUTF16(placeholder_lastname));
expected.set_placeholder(ASCIIToUTF16(placeholder_lastname));
expected.set_value(ASCIIToUTF16(placeholder_lastname));
} else {
expected.set_label({});
expected.set_value({});
}
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
if (initial_email) {
expected.set_label(ASCIIToUTF16(initial_email));
expected.set_value(ASCIIToUTF16(initial_email));
} else if (placeholder_email) {
expected.set_label(ASCIIToUTF16(placeholder_email));
expected.set_placeholder(ASCIIToUTF16(placeholder_email));
expected.set_value(ASCIIToUTF16(placeholder_email));
} else {
expected.set_label({});
expected.set_value({});
}
expected.set_is_autofilled(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
// Preview the form and verify that the cursor position has been updated.
test_api(form).field(0).set_value(u"Wyatt");
test_api(form).field(1).set_value(u"Earp");
test_api(form).field(2).set_value(u"wyatt@example.com");
test_api(form).field(0).set_is_autofilled(true);
test_api(form).field(1).set_is_autofilled(true);
test_api(form).field(2).set_is_autofilled(true);
ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kPreview);
// The selection should be set after the second character.
EXPECT_EQ(2u, input_element.SelectionStart());
EXPECT_EQ(2u, input_element.SelectionEnd());
// Fill the form.
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
if (!unowned) {
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
}
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Wyatt");
if (placeholder_firstname) {
expected.set_label(ASCIIToUTF16(placeholder_firstname));
expected.set_placeholder(ASCIIToUTF16(placeholder_firstname));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Earp");
if (placeholder_lastname) {
expected.set_label(ASCIIToUTF16(placeholder_lastname));
expected.set_placeholder(ASCIIToUTF16(placeholder_lastname));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"wyatt@example.com");
if (placeholder_email) {
expected.set_label(ASCIIToUTF16(placeholder_email));
expected.set_placeholder(ASCIIToUTF16(placeholder_email));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
// Verify that the cursor position has been updated.
EXPECT_EQ(5u, input_element.SelectionStart());
EXPECT_EQ(5u, input_element.SelectionEnd());
}
// Tests that loading, dynamically editing, and then autofilling the form in
// `html` yields a specific result.
//
// The form is expected to have a very specific structure. In particular, its
// fields are supposed to be first name, last name, phone, credit card number,
// city, and state, whose placeholder attributes are supposed to match the
// `placeholder_*` arguments.
//
// Each field's value is modified dynamically. The second one is explicitly
// marked as user-edited; the other ones are not. The third and fourth field's
// values are typical placeholder values are expected to be ignored.
//
// TODO(crbug.com/41483772): Remove implicit assumptions about `html` from
// this function.
void TestFillFormAndModifyValues(const char* html,
const char* placeholder_firstname,
const char* placeholder_lastname,
const char* placeholder_phone,
const char* placeholder_creditcard,
const char* placeholder_city,
const char* placeholder_state) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("firstname");
WebFormElement form_element = input_element.Form();
std::vector<WebFormControlElement> control_elements =
GetOwnedAutofillableFormControls(input_element.GetDocument(),
form_element);
ASSERT_EQ(6U, control_elements.size());
// We now modify the values.
// This will be ignored, the string will be sanitized into an empty string.
control_elements[0].SetValue(WebString::FromUTF16(
std::u16string(1, base::i18n::kLeftToRightMark) + u" "));
// This will be considered as a value entered by the user.
control_elements[1].SetValue(WebString::FromUTF16(u"Earp"));
control_elements[1].SetUserHasEditedTheField(true);
// This will be ignored, the string will be sanitized into an empty string.
control_elements[2].SetValue(WebString::FromUTF16(u"(___)-___-____"));
// This will be ignored, the string will be sanitized into an empty string.
control_elements[3].SetValue(WebString::FromUTF16(u"____-____-____-____"));
// This will be ignored, because it's injected by the website and not the
// user.
control_elements[4].SetValue(WebString::FromUTF16(u"Enter your city.."));
control_elements[5].SetValue(WebString::FromUTF16(u"AK"));
// Find the form that contains the input element.
FormData form = FindForm(input_element);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(6U, fields.size());
// Preview the form and verify that the cursor position has been updated.
test_api(form).field(0).set_value(u"Wyatt");
test_api(form).field(1).set_value(u"Earpagus");
test_api(form).field(2).set_value(u"888-123-4567");
test_api(form).field(3).set_value(u"1111-2222-3333-4444");
test_api(form).field(4).set_value(u"Montreal");
test_api(form).field(5).set_value(u"AA");
test_api(form).field(0).set_is_autofilled(true);
test_api(form).field(1).set_is_autofilled(true);
test_api(form).field(2).set_is_autofilled(true);
test_api(form).field(3).set_is_autofilled(true);
test_api(form).field(4).set_is_autofilled(true);
test_api(form).field(5).set_is_autofilled(true);
ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kPreview);
// Fill the form.
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(6U, fields2.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Wyatt");
if (placeholder_firstname) {
expected.set_label(ASCIIToUTF16(placeholder_firstname));
expected.set_placeholder(ASCIIToUTF16(placeholder_firstname));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
expected.set_is_user_edited(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
// The last name field is not filled, because there is a value in it.
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Earp");
if (placeholder_lastname) {
expected.set_label(ASCIIToUTF16(placeholder_lastname));
expected.set_placeholder(ASCIIToUTF16(placeholder_lastname));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(false);
expected.set_is_user_edited(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"phone");
expected.set_name(expected.id_attribute());
expected.set_value(u"888-123-4567");
if (placeholder_phone) {
expected.set_label(ASCIIToUTF16(placeholder_phone));
expected.set_placeholder(ASCIIToUTF16(placeholder_phone));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
expected.set_is_user_edited(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
expected.set_id_attribute(u"cc");
expected.set_name(expected.id_attribute());
expected.set_value(u"1111-2222-3333-4444");
if (placeholder_creditcard) {
expected.set_label(ASCIIToUTF16(placeholder_creditcard));
expected.set_placeholder(ASCIIToUTF16(placeholder_creditcard));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
expected.set_is_user_edited(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[3]);
expected.set_id_attribute(u"city");
expected.set_name(expected.id_attribute());
expected.set_value(u"Montreal");
if (placeholder_city) {
expected.set_label(ASCIIToUTF16(placeholder_city));
expected.set_placeholder(ASCIIToUTF16(placeholder_city));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
expected.set_is_user_edited(false);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[4]);
expected.set_form_control_type(FormControlType::kSelectOne);
expected.set_id_attribute(u"state");
expected.set_name_attribute(u"state");
expected.set_name(expected.name_attribute());
expected.set_value(u"AA");
if (placeholder_state) {
expected.set_label(ASCIIToUTF16(placeholder_state));
expected.set_placeholder(ASCIIToUTF16(placeholder_state));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
expected.set_is_user_edited(false);
expected.set_max_length(0);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[5]);
}
// Similar to TestFillFormAndModifyValues().
// TODO(crbug.com/41483772): Remove implicit assumptions about `html` from
// this function.
void TestFillFormAndModifyInitiatingValue(const char* html,
const char* placeholder_creditcard,
const char* placeholder_expiration,
const char* placeholder_name) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("cc");
WebFormElement form_element = input_element.Form();
std::vector<WebFormControlElement> control_elements =
GetOwnedAutofillableFormControls(input_element.GetDocument(),
form_element);
ASSERT_EQ(3U, control_elements.size());
// We now modify the values.
// This will be ignored.
control_elements[0].SetValue(WebString::FromUTF16(u"____-____-____-____"));
// This will be ignored.
control_elements[1].SetValue(WebString::FromUTF16(u"____/__"));
control_elements[2].SetValue(WebString::FromUTF16(u"John Smith"));
control_elements[2].SetUserHasEditedTheField(true);
// Find the form that contains the input element.
FormData form = FindForm(input_element);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
// Preview the form and verify that the cursor position has been updated.
test_api(form).field(0).set_value(u"1111-2222-3333-4444");
test_api(form).field(1).set_value(u"03/2030");
test_api(form).field(2).set_value(u"Susan Smith");
test_api(form).field(0).set_is_autofilled(true);
test_api(form).field(1).set_is_autofilled(true);
test_api(form).field(2).set_is_autofilled(true);
ExecuteJavaScriptForTests("document.getElementById('cc').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kPreview);
// The selection should be set after the 19th character.
EXPECT_EQ(19u, input_element.SelectionStart());
EXPECT_EQ(19u, input_element.SelectionEnd());
// Fill the form.
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"cc");
expected.set_name(expected.id_attribute());
expected.set_value(u"1111-2222-3333-4444");
if (placeholder_creditcard) {
expected.set_label(ASCIIToUTF16(placeholder_creditcard));
expected.set_placeholder(ASCIIToUTF16(placeholder_creditcard));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
expected.set_id_attribute(u"expiration_date");
expected.set_name(expected.id_attribute());
expected.set_value(u"03/2030");
if (placeholder_expiration) {
expected.set_label(ASCIIToUTF16(placeholder_expiration));
expected.set_placeholder(ASCIIToUTF16(placeholder_expiration));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"name");
expected.set_name(expected.id_attribute());
expected.set_value(u"John Smith");
if (placeholder_name) {
expected.set_label(ASCIIToUTF16(placeholder_name));
expected.set_placeholder(ASCIIToUTF16(placeholder_name));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(false);
expected.set_is_user_edited(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
// Verify that the cursor position has been updated.
EXPECT_EQ(19u, input_element.SelectionStart());
EXPECT_EQ(19u, input_element.SelectionEnd());
}
// Similar to TestFillFormAndModifyValues().
// TODO(crbug.com/41483772): Remove implicit assumptions about `html` from
// this function.
void TestFillFormJSModifiesUserInputValue(const char* html,
const char* placeholder_creditcard,
const char* placeholder_expiration,
const char* placeholder_name) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Get the input element we want to find.
WebInputElement input_element = GetInputElementById("cc");
WebFormElement form_element = input_element.Form();
std::vector<WebFormControlElement> control_elements =
GetOwnedAutofillableFormControls(input_element.GetDocument(),
form_element);
ASSERT_EQ(3U, control_elements.size());
// We now modify the values.
// This will be ignored.
control_elements[0].SetValue(WebString::FromUTF16(u"____-____-____-____"));
// This will be ignored.
control_elements[1].SetValue(WebString::FromUTF16(u"____/__"));
control_elements[2].SetValue(WebString::FromUTF16(u"john smith"));
control_elements[2].SetUserHasEditedTheField(true);
// Sometimes the JS modifies the value entered by the user.
ExecuteJavaScriptForTests(
"document.getElementById('name').value = 'John Smith';");
// Find the form that contains the input element.
FormData form = FindForm(input_element);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://abc.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
// Preview the form and verify that the cursor position has been updated.
test_api(form).field(0).set_value(u"1111-2222-3333-4444");
test_api(form).field(1).set_value(u"03/2030");
test_api(form).field(2).set_value(u"Susan Smith");
test_api(form).field(0).set_is_autofilled(true);
test_api(form).field(1).set_is_autofilled(true);
test_api(form).field(2).set_is_autofilled(true);
ExecuteJavaScriptForTests("document.getElementById('cc').focus();");
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kPreview);
// The selection should be set after the 19th character.
EXPECT_EQ(19u, input_element.SelectionStart());
EXPECT_EQ(19u, input_element.SelectionEnd());
// Fill the form.
ApplyFieldsAction(input_element.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill);
// Find the newly-filled form that contains the input element.
FormData form2 = FindForm(input_element);
EXPECT_EQ(u"TestForm", form2.name());
EXPECT_EQ(GURL("http://abc.com"), form2.action());
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"cc");
expected.set_name(expected.id_attribute());
expected.set_value(u"1111-2222-3333-4444");
if (placeholder_creditcard) {
expected.set_label(ASCIIToUTF16(placeholder_creditcard));
expected.set_placeholder(ASCIIToUTF16(placeholder_creditcard));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
expected.set_id_attribute(u"expiration_date");
expected.set_name(expected.id_attribute());
expected.set_value(u"03/2030");
if (placeholder_expiration) {
expected.set_label(ASCIIToUTF16(placeholder_expiration));
expected.set_placeholder(ASCIIToUTF16(placeholder_expiration));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"name");
expected.set_name(expected.id_attribute());
expected.set_value(u"John Smith");
if (placeholder_name) {
expected.set_label(ASCIIToUTF16(placeholder_name));
expected.set_placeholder(ASCIIToUTF16(placeholder_name));
} else {
expected.set_label({});
expected.set_placeholder({});
}
expected.set_is_autofilled(false);
expected.set_is_user_edited(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
// Verify that the cursor position has been updated.
EXPECT_EQ(19u, input_element.SelectionStart());
EXPECT_EQ(19u, input_element.SelectionEnd());
}
void TestClearPreviewedElements(const char* html) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
std::vector<std::pair<WebFormControlElement, WebAutofillState>> elements;
elements.emplace_back(GetInputElementById("firstname"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("lastname"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("email"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("email2"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("phone"),
WebAutofillState::kNotFilled);
WebInputElement firstname = elements[0].first.To<WebInputElement>();
WebInputElement lastname = elements[1].first.To<WebInputElement>();
// Set the auto-filled attribute.
for (auto& [element, state] : elements) {
element.SetAutofillState(WebAutofillState::kPreviewed);
}
// Set the suggested values on two of the elements.
firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
elements[2].first.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
elements[3].first.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
elements[4].first.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
std::vector<bool> is_value_empty(elements.size());
for (size_t i = 0; i < elements.size(); ++i) {
is_value_empty[i] = elements[i].first.Value().IsEmpty();
}
// Clear the previewed fields.
ClearPreviewedElements(elements);
// Verify the previewed fields are cleared.
for (size_t i = 0; i < elements.size(); ++i) {
WebFormControlElement& element = elements[i].first;
SCOPED_TRACE(testing::Message() << "Element " << i);
EXPECT_EQ(element.Value().IsEmpty(), is_value_empty[i]);
EXPECT_TRUE(element.SuggestedValue().IsEmpty());
EXPECT_FALSE(element.IsAutofilled());
}
}
void TestClearPreviewedFormWithNonEmptyInitiatingNode(const char* html) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
std::vector<std::pair<WebFormControlElement, WebAutofillState>> elements;
elements.emplace_back(GetInputElementById("firstname"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("lastname"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("email"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("email2"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("phone"),
WebAutofillState::kNotFilled);
WebInputElement firstname = elements[0].first.To<WebInputElement>();
WebInputElement lastname = elements[1].first.To<WebInputElement>();
// Set the auto-filled attribute.
for (auto& [element, state] : elements) {
element.SetAutofillState(WebAutofillState::kPreviewed);
}
// Set the suggested values on all of the elements.
firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
elements[2].first.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
elements[3].first.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
elements[4].first.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
// Clear the previewed fields.
ClearPreviewedElements(elements);
// Fields with non-empty values are restored.
EXPECT_EQ(u"W", firstname.Value().Utf16());
EXPECT_TRUE(firstname.SuggestedValue().IsEmpty());
EXPECT_FALSE(firstname.IsAutofilled());
// Verify the previewed fields are cleared.
for (size_t i = 1; i < elements.size(); ++i) {
WebFormControlElement& element = elements[i].first;
SCOPED_TRACE(testing::Message() << "Element " << i);
EXPECT_TRUE(element.Value().IsEmpty());
EXPECT_TRUE(element.SuggestedValue().IsEmpty());
EXPECT_FALSE(element.IsAutofilled());
}
}
void TestClearPreviewedFormWithAutofilledInitiatingNode(const char* html) {
LoadHTML(html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
std::vector<std::pair<WebFormControlElement, WebAutofillState>> elements;
elements.emplace_back(GetInputElementById("firstname"),
WebAutofillState::kAutofilled);
elements.emplace_back(GetInputElementById("lastname"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("email"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("email2"),
WebAutofillState::kNotFilled);
elements.emplace_back(GetInputElementById("phone"),
WebAutofillState::kNotFilled);
WebInputElement firstname = elements[0].first.To<WebInputElement>();
WebInputElement lastname = elements[1].first.To<WebInputElement>();
// Set the auto-filled attribute.
for (auto& [element, state] : elements) {
element.SetAutofillState(WebAutofillState::kPreviewed);
}
// Set the suggested values on all of the elements.
firstname.SetSuggestedValue(WebString::FromASCII("Wyatt"));
lastname.SetSuggestedValue(WebString::FromASCII("Earp"));
elements[2].first.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
elements[3].first.SetSuggestedValue(WebString::FromASCII("wyatt@earp.com"));
elements[4].first.SetSuggestedValue(WebString::FromASCII("650-777-9999"));
// Clear the previewed fields.
ClearPreviewedElements(elements);
// Fields with non-empty values are restored.
EXPECT_EQ(u"W", firstname.Value().Utf16());
EXPECT_TRUE(firstname.SuggestedValue().IsEmpty());
EXPECT_TRUE(firstname.IsAutofilled());
// Verify the previewed fields are cleared.
for (size_t i = 1; i < elements.size(); ++i) {
WebFormControlElement& element = elements[i].first;
SCOPED_TRACE(testing::Message() << "Element " << i);
EXPECT_TRUE(element.Value().IsEmpty());
EXPECT_TRUE(element.SuggestedValue().IsEmpty());
EXPECT_FALSE(element.IsAutofilled());
}
}
static WebString GetValueWrapper(WebFormControlElement element) {
if (element.FormControlType() == blink::mojom::FormControlType::kTextArea) {
return element.To<WebFormControlElement>().Value();
}
if (element.FormControlType() ==
blink::mojom::FormControlType::kSelectOne) {
return element.To<WebSelectElement>().Value();
}
return element.To<WebInputElement>().Value();
}
static WebString GetSuggestedValueWrapper(WebFormControlElement element) {
if (element.FormControlType() == blink::mojom::FormControlType::kTextArea) {
return element.To<WebFormControlElement>().SuggestedValue();
}
if (element.FormControlType() ==
blink::mojom::FormControlType::kSelectOne) {
return element.To<WebSelectElement>().SuggestedValue();
}
return element.To<WebInputElement>().SuggestedValue();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
// We use a fresh `FormCache` in this fixture because the `AutofillAgent`'s
// cache is used and populated by `AutofillAgent`.
std::optional<FormCache> form_cache_;
};
// We should be able to extract a normal text field.
TEST_F(FormAutofillTest, WebFormControlElementToFormField) {
LoadHTML(R"(<input id=element value=value>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
/*extract_options=*/{}, &result);
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_value(u"value");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract a text field with autocomplete="off".
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldAutocompleteOff) {
LoadHTML(R"(<input id=element value=value autocomplete=off>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_value(u"value");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_autocomplete_attribute("off");
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract a text field with maxlength specified.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldMaxLength) {
LoadHTML(R"(<input id=element value=value maxlength=5>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_value(u"value");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(5);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract a text field that has been autofilled.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldAutofilled) {
LoadHTML(R"(<input id=element value=value>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebInputElement element = GetInputElementById("element");
element.SetAutofillState(WebAutofillState::kAutofilled);
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_value(u"value");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_is_autofilled(true);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract a radio or a checkbox field that has been
// autofilled.
TEST_F(FormAutofillTest, WebFormControlElementToClickableFormField) {
LoadHTML(R"(<input type=checkbox id=checkbox value=mail checked>
<input type=radio id=radio value=male>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebInputElement element = GetInputElementById("checkbox");
element.SetAutofillState(WebAutofillState::kAutofilled);
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"checkbox");
expected.set_name(expected.id_attribute());
expected.set_value(u"mail");
expected.set_form_control_type(FormControlType::kInputCheckbox);
expected.set_max_length(0);
expected.set_is_autofilled(true);
expected.set_check_status(FormFieldData::CheckStatus::kChecked);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
element = GetInputElementById("radio");
element.SetAutofillState(WebAutofillState::kAutofilled);
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
expected.set_id_attribute(u"radio");
expected.set_name(expected.id_attribute());
expected.set_value(u"male");
expected.set_form_control_type(FormControlType::kInputRadio);
expected.set_max_length(0);
expected.set_is_autofilled(true);
expected.set_check_status(FormFieldData::CheckStatus::kCheckableButUnchecked);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract a <select> field.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldSelect) {
LoadHTML(R"(<select id=element>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_max_length(0);
expected.set_form_control_type(FormControlType::kSelectOne);
expected.set_value(u"CA");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
EXPECT_THAT(result.selected_option().CopyAsOptional(),
Optional(Field(&SelectOption::text, u"California")));
ASSERT_EQ(2U, result.options().size());
EXPECT_EQ(u"CA", result.options()[0].value);
EXPECT_EQ(u"California", result.options()[0].text);
EXPECT_EQ(u"TX", result.options()[1].value);
EXPECT_EQ(u"Texas", result.options()[1].text);
}
// We copy extra attributes for the select field.
TEST_F(FormAutofillTest,
WebFormControlElementToFormFieldSelect_ExtraAttributes) {
LoadHTML(R"(<select id=element autocomplete=off>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
element.SetAutofillState(WebAutofillState::kAutofilled);
FormFieldData result1;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result1);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_max_length(0);
expected.set_form_control_type(FormControlType::kSelectOne);
// We check that the extra attributes have been copied to `result1`.
expected.set_is_autofilled(true);
expected.set_autocomplete_attribute("off");
expected.set_should_autocomplete(false);
expected.set_is_focusable(true);
expected.set_is_visible(true);
expected.set_text_direction(base::i18n::LEFT_TO_RIGHT);
expected.set_value(u"CA");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result1);
}
// When faced with <select> field with *many* options, we should trim them to a
// reasonable number.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldLongSelect) {
std::string html = R"(<select id=element>)";
for (size_t i = 0; i < 2 * kMaxListSize; ++i) {
html += base::StringPrintf("<option value='%" PRIuS
"'>"
"%" PRIuS "</option>",
i, i);
}
html += "</select>";
LoadHTML(html.c_str());
WebLocalFrame* frame = GetMainFrame();
ASSERT_TRUE(frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
EXPECT_TRUE(result.options().empty());
}
// Test that we use the aria-label as the content if the <option> has no text.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldSelectAriaLabel) {
LoadHTML(
R"(<select id=element>
<option aria-label='usa'><img></option>
<option aria-label='uk'><img></option>
</select>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
ASSERT_EQ(2u, result.options().size());
EXPECT_EQ(u"usa", result.options()[0].text);
EXPECT_EQ(u"uk", result.options()[1].text);
}
// Test that the content for the <option> can be computed when the <option>s
// have nested HTML nodes.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldSelectNestedNodes) {
LoadHTML(
R"(<select id=element>
<option><div><img><b>+1</b> (Canada)</div></option>
</select>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
ASSERT_EQ(1u, result.options().size());
EXPECT_EQ(u"+1 (Canada)", result.options()[0].text);
}
// We should be able to extract a <textarea> field.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldTextArea) {
LoadHTML(R"(<textarea id=element>This element's value
spans multiple lines.</textarea>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_form_control_type(FormControlType::kTextArea);
expected.set_value(
u"This element's value\n"
u"spans multiple lines.");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract an <input type=month> field.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldMonthInput) {
LoadHTML(R"(<input type=month id=element value='2011-12'>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result_sans_value;
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_id_attribute(u"element");
expected.set_name(expected.id_attribute());
expected.set_max_length(0);
expected.set_form_control_type(FormControlType::kInputMonth);
expected.set_value(u"2011-12");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract password fields.
TEST_F(FormAutofillTest, WebFormControlElementToPasswordFormField) {
LoadHTML(R"(<form name=TestForm action='http://cnn.com'>
<input type=password id=password value=secret>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("password");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
FormFieldData expected;
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"password");
expected.set_name(expected.id_attribute());
expected.set_form_control_type(FormControlType::kInputPassword);
expected.set_value(u"secret");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
// We should be able to extract the autocompletetype attribute.
TEST_F(FormAutofillTest, WebFormControlElementToFormFieldAutocompletetype) {
std::string html =
R"(<input id=absent>
<input id=empty autocomplete=''>
<input id=off autocomplete=off>
<input id=regular autocomplete=email>
<input id='multi-valued' autocomplete='billing email'>
<input id=experimental x-autocompletetype='email'>
<input type=month id=month autocomplete='cc-exp'>
<select id=select autocomplete=state>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>
<textarea id=textarea autocomplete='street-address'>
Some multi-
lined value
</textarea>)";
html += "<input id=malicious autocomplete='" + std::string(10000, 'x') + "'>";
LoadHTML(html.c_str());
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
struct TestCase {
const std::string element_id;
FormControlType form_control_type;
const std::string autocomplete_attribute;
const std::string value;
};
TestCase test_cases[] = {
// An absent attribute is equivalent to an empty one.
{"absent", FormControlType::kInputText, "", ""},
// Make sure there are no issues parsing an empty attribute.
{"empty", FormControlType::kInputText, "", ""},
// Make sure there are no issues parsing an attribute value that isn't a
// type hint.
{"off", FormControlType::kInputText, "off", ""},
// Common case: exactly one type specified.
{"regular", FormControlType::kInputText, "email", ""},
// Verify that we correctly extract multiple tokens as well.
{"multi-valued", FormControlType::kInputText, "billing email", ""},
// Verify that <input type=month> fields are supported.
{"month", FormControlType::kInputMonth, "cc-exp", ""},
// We previously extracted this data from the experimental
// 'x-autocompletetype' attribute. Now that the field type hints are part
// of the spec under the autocomplete attribute, we no longer support the
// experimental version.
{"experimental", FormControlType::kInputText, "", ""},
// <select> elements should behave no differently from text fields here.
{"select", FormControlType::kSelectOne, "state", "CA"},
// <textarea> elements should also behave no differently from text fields.
{"textarea", FormControlType::kTextArea, "street-address",
" Some multi-\n lined value\n "},
// Very long attribute values should be replaced by a default string, to
// prevent malicious websites from DOSing the browser process.
{"malicious", FormControlType::kInputText, "x-max-data-length-exceeded"},
};
WebDocument document = frame->GetDocument();
for (auto& test_case : test_cases) {
WebFormControlElement element =
GetFormControlElementById(test_case.element_id);
FormFieldData result;
WebFormControlElementToFormFieldForTesting(WebFormElement(), element,
nullptr,
/*extract_options=*/{}, &result);
FormFieldData expected;
expected.set_id_attribute(ASCIIToUTF16(test_case.element_id));
expected.set_name(expected.id_attribute());
expected.set_form_control_type(test_case.form_control_type);
expected.set_max_length(
(test_case.form_control_type == FormControlType::kInputText ||
test_case.form_control_type == FormControlType::kTextArea)
? FormFieldData::kDefaultMaxLength
: 0);
expected.set_autocomplete_attribute(test_case.autocomplete_attribute);
expected.set_parsed_autocomplete(
ParseAutocompleteAttribute(test_case.autocomplete_attribute));
expected.set_value(ASCIIToUTF16(test_case.value));
SCOPED_TRACE(test_case.element_id);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, result);
}
}
TEST_F(FormAutofillTest, DetectTextDirectionFromDirectStyle) {
LoadHTML(R"(<style>input{direction:rtl}</style>
<form>
<input id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction());
}
TEST_F(FormAutofillTest, DetectTextDirectionFromDirectDIRAttribute) {
LoadHTML(R"(<form>
<input dir=rtl type=text id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction());
}
TEST_F(FormAutofillTest, DetectTextDirectionFromParentStyle) {
LoadHTML(R"(<style>form {direction: rtl}</style>
<form>
<input id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction());
}
TEST_F(FormAutofillTest, DetectTextDirectionFromParentDIRAttribute) {
LoadHTML(R"(<form dir=rtl>
<input id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction());
}
TEST_F(FormAutofillTest, DetectTextDirectionWhenStyleAndDIRAttributeMixed) {
LoadHTML(R"(<style>input{direction:ltr}</style>
<form dir=rtl>
<input id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, result.text_direction());
}
TEST_F(FormAutofillTest, TextAlignOverridesDirection) {
// text-align: right
LoadHTML(R"(<style>input{direction:ltr;text-align:right}</style>
<form>
<input id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction());
// text-align: left
LoadHTML(R"(<style>input{direction:rtl;text-align:left}</style>
<form>
<input id=element>
</form>)");
frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
element = GetFormControlElementById("element");
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, result.text_direction());
}
TEST_F(FormAutofillTest,
DetectTextDirectionWhenParentHasBothDIRAttributeAndStyle) {
LoadHTML(R"(<style>form{direction:ltr}</style>
<form dir=rtl>
<input id=element>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, result.text_direction());
}
TEST_F(FormAutofillTest, DetectTextDirectionWhenAncestorHasInlineStyle) {
LoadHTML(R"(<form style='direction:ltr'>
<span dir=rtl>
<input id=element>
</span>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormControlElement element = GetFormControlElementById("element");
FormFieldData result;
WebFormControlElementToFormFieldForTesting(element.Form(), element, nullptr,
{}, &result);
EXPECT_EQ(base::i18n::RIGHT_TO_LEFT, result.text_direction());
}
TEST_F(FormAutofillTest, WebFormElementToFormData) {
LoadHTML(
R"(<form name=TestForm action='http://cnn.com/submit/?a=1'>
<label for=firstname>First name:</label>
<input id=firstname value=John>
<label for=lastname>Last name:</label>
<input id=lastname value=Smith>
<label for=street-address>Address:</label>
<textarea id=street-address>123 Fantasy Ln.&#10;Apt. 42</textarea>
<label for=state>State:</label>
<select id=state>
<option value=CA>California</option>
<option value=TX>Texas</option>
</select>
<label for=password>Password:</label>
<input type=password id=password value=secret>
<label for=month>Card expiration:</label>
<input type=month id=month value='2011-12'>
<input type=submit name='reply-send' value=Send>
<!-- The below inputs should be ignored -->
<label for=notvisible>Hidden:</label>
<input type=hidden id=notvisible value=apple>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
std::vector<WebFormElement> forms = frame->GetDocument().GetTopLevelForms();
ASSERT_EQ(1U, forms.size());
WebInputElement input_element = GetInputElementById("firstname");
FormData form = FindForm(input_element);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GetFormRendererId(forms[0]), form.renderer_id());
EXPECT_EQ(GURL("http://cnn.com/submit/"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(6U, fields.size());
FormFieldData expected;
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"First name:");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Last name:");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"street-address");
expected.set_name(expected.id_attribute());
expected.set_value(u"123 Fantasy Ln.\nApt. 42");
expected.set_label(u"Address:");
expected.set_form_control_type(FormControlType::kTextArea);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
expected.set_id_attribute(u"state");
expected.set_name(expected.id_attribute());
expected.set_value(u"CA");
expected.set_label(u"State:");
expected.set_form_control_type(FormControlType::kSelectOne);
expected.set_max_length(0);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
expected.set_id_attribute(u"password");
expected.set_name(expected.id_attribute());
expected.set_value(u"secret");
expected.set_label(u"Password:");
expected.set_form_control_type(FormControlType::kInputPassword);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[4]);
expected.set_id_attribute(u"month");
expected.set_name(expected.id_attribute());
expected.set_value(u"2011-12");
expected.set_label(u"Card expiration:");
expected.set_form_control_type(FormControlType::kInputMonth);
expected.set_max_length(0);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[5]);
// Check renderer_id.
std::vector<WebFormControlElement> form_control_elements =
forms[0].GetFormControlElements();
for (size_t i = 0; i < fields.size(); ++i)
EXPECT_EQ(GetFieldRendererId(form_control_elements[i]),
fields[i].renderer_id());
}
TEST_F(FormAutofillTest, WebFormElementConsiderNonControlLabelableElements) {
LoadHTML(R"(<form id=form>
<label for=progress>Progress:</label>
<progress id=progress></progress>
<label for=firstname>First name:</label>
<input id=firstname value=John>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
FormData form = *ExtractFormData(web_form);
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(1U, fields.size());
EXPECT_EQ(u"firstname", fields[0].name());
}
// We should not be able to serialize a form with too many fillable fields.
TEST_F(FormAutofillTest, WebFormElementToFormData_TooManyFields) {
std::string html = "<form name=TestForm action='http://cnn.com'>";
for (size_t i = 0; i < (kMaxExtractableFields + 1); ++i) {
html += "<input>";
}
html += "</form>";
LoadHTML(html.c_str());
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
std::vector<WebFormElement> forms = frame->GetDocument().GetTopLevelForms();
ASSERT_EQ(1U, forms.size());
ASSERT_FALSE(forms.front().GetFormControlElements().empty());
WebInputElement input_element = forms.front()
.GetFormControlElements()
.front()
.DynamicTo<WebInputElement>();
EXPECT_THAT(
FindFormAndFieldForFormControlElement(input_element),
Optional(Pair(
Property(&FormData::fields,
ElementsAre(Property(&FormFieldData::renderer_id,
GetFieldRendererId(input_element)))),
_)));
}
// Tests that the `should_autocomplete` is set to false for all the fields when
// an autocomplete='off' attribute is set for the form in HTML.
TEST_F(FormAutofillTest, WebFormElementToFormData_AutocompleteOff_OnForm) {
LoadHTML(
R"(<form name=TestForm id=form action='http://cnn.com' autocomplete=off>
<label for=firstname>First name:</label>
<input id=firstname value=John>
<label for=lastname>Last name:</label>
<input id=lastname value=Smith>
<label for='street-address'>Address:</label>
<input id=addressline1 value='123 Test st.'>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
FormData form = *ExtractFormData(web_form);
for (const FormFieldData& field : form.fields()) {
EXPECT_FALSE(field.should_autocomplete());
}
}
// Tests that the `should_autocomplete` is set to false only for the field
// which has an autocomplete='off' attribute set for it in HTML.
TEST_F(FormAutofillTest, WebFormElementToFormData_AutocompleteOff_OnField) {
LoadHTML(
R"(<form name=TestForm id=form action='http://cnn.com'>
<label for=firstname>First name:</label>
<input id=firstname value=John autocomplete=off>
<label for=lastname>Last name:</label>
<input id=lastname value=Smith>
<label for='street-address'>Address:</label>
<input id=addressline1 value='123 Test st.'>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
FormData form = *ExtractFormData(web_form);
ASSERT_EQ(3U, form.fields().size());
EXPECT_FALSE(form.fields()[0].should_autocomplete());
EXPECT_TRUE(form.fields()[1].should_autocomplete());
EXPECT_TRUE(form.fields()[2].should_autocomplete());
}
// `should_autocomplete` must be set to false for the field with
// autocomplete='one-time-code' attribute set in HTML.
TEST_F(FormAutofillTest, WebFormElementToFormData_AutocompleteOff_OneTimeCode) {
LoadHTML(
R"(<form name=TestForm id=form action='http://cnn.com'>
<input value=123 autocomplete='one-time-code'>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
FormData form = *ExtractFormData(web_form);
ASSERT_EQ(1U, form.fields().size());
EXPECT_FALSE(form.fields()[0].should_autocomplete());
}
// Tests CSS classes are set.
TEST_F(FormAutofillTest, WebFormElementToFormData_CssClasses) {
LoadHTML(
R"(<form name=TestForm id=form action='http://cnn.com' autocomplete=off>
<input id=firstname class='firstname_field'>
<input id=lastname class='lastname_field'>
<input id=addressline1>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
FormData form = *ExtractFormData(web_form);
ASSERT_EQ(3U, form.fields().size());
EXPECT_EQ(u"firstname_field", form.fields()[0].css_classes());
EXPECT_EQ(u"lastname_field", form.fields()[1].css_classes());
EXPECT_EQ(std::u16string(), form.fields()[2].css_classes());
}
// Tests id attributes are set.
TEST_F(FormAutofillTest, WebFormElementToFormData_IdAttributes) {
LoadHTML(
R"(<form name=TestForm id=form action='http://cnn.com' autocomplete=off>
<input name=name1 id=firstname>
<input name=name2 id=lastname>
<input name=same id=same>
<input id=addressline1>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
FormData form = *ExtractFormData(web_form);
EXPECT_EQ(4U, form.fields().size());
// id attributes.
EXPECT_EQ(u"firstname", form.fields()[0].id_attribute());
EXPECT_EQ(u"lastname", form.fields()[1].id_attribute());
EXPECT_EQ(u"same", form.fields()[2].id_attribute());
EXPECT_EQ(u"addressline1", form.fields()[3].id_attribute());
// name attributes.
EXPECT_EQ(u"name1", form.fields()[0].name_attribute());
EXPECT_EQ(u"name2", form.fields()[1].name_attribute());
EXPECT_EQ(u"same", form.fields()[2].name_attribute());
EXPECT_EQ(u"", form.fields()[3].name_attribute());
// name for autofill
EXPECT_EQ(u"name1", form.fields()[0].name());
EXPECT_EQ(u"name2", form.fields()[1].name());
EXPECT_EQ(u"same", form.fields()[2].name());
EXPECT_EQ(u"addressline1", form.fields()[3].name());
}
TEST_F(FormAutofillTest, ExtractForms) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
First name: <input id=firstname value=John>
Last name: <input id=lastname value=Smith>
Email: <input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, ExtractMultipleForms) {
LoadHTML(R"(<form name=TestForm action='http://cnn.com'>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>
<form name=TestForm2 action='http://zoo.com'>
<input id=firstname value=Jack>
<input id=lastname value=Adams>
<input id=email value='jack@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(2U, forms.size());
// First form.
const FormData& form = forms[0];
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://cnn.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"John");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Smith");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"john@example.com");
expected.set_label(u"john@example.com");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
// Second form.
const FormData& form2 = forms[1];
EXPECT_EQ(u"TestForm2", form2.name());
EXPECT_EQ(GURL("http://zoo.com"), form2.action());
const std::vector<FormFieldData>& fields2 = form2.fields();
ASSERT_EQ(3U, fields2.size());
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Jack");
expected.set_label(u"Jack");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Adams");
expected.set_label(u"Adams");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"jack@example.com");
expected.set_label(u"jack@example.com");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
}
TEST_F(FormAutofillTest, OnlyExtractNewForms) {
LoadHTML(
R"(<form id=testform action='http://cnn.com'>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
// Second call should give nothing as there are no new forms.
forms = UpdateFormCache().updated_forms;
ASSERT_TRUE(forms.empty());
// Append to the current form will re-extract.
ExecuteJavaScriptForTests(
R"(var newInput = document.createElement('input');
newInput.setAttribute('type', 'text');
newInput.setAttribute('id', 'telephone');
newInput.value = '12345';
document.getElementById('testform').appendChild(newInput);)");
base::RunLoop().RunUntilIdle();
forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
const std::vector<FormFieldData>& fields = forms[0].fields();
ASSERT_EQ(4U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"John");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Smith");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"john@example.com");
expected.set_label(u"john@example.com");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
expected.set_id_attribute(u"telephone");
expected.set_name(expected.id_attribute());
expected.set_value(u"12345");
expected.set_label({});
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
forms.clear();
// Completely new form will also be extracted.
ExecuteJavaScriptForTests(
R"(var newForm=document.createElement('form');
newForm.id='new_testform';
newForm.action='http://google.com';
newForm.method='post';
var newFirstname=document.createElement('input');
newFirstname.setAttribute('type', 'text');
newFirstname.setAttribute('id', 'second_firstname');
newFirstname.value = 'Bob';
var newLastname=document.createElement('input');
newLastname.setAttribute('type', 'text');
newLastname.setAttribute('id', 'second_lastname');
newLastname.value = 'Hope';
var newEmail=document.createElement('input');
newEmail.setAttribute('type', 'text');
newEmail.setAttribute('id', 'second_email');
newEmail.value = 'bobhope@example.com';
newForm.appendChild(newFirstname);
newForm.appendChild(newLastname);
newForm.appendChild(newEmail);
document.body.appendChild(newForm);)");
base::RunLoop().RunUntilIdle();
forms = UpdateFormCache().updated_forms;
ASSERT_EQ(1U, forms.size());
const std::vector<FormFieldData>& fields2 = forms[0].fields();
ASSERT_EQ(3U, fields2.size());
expected.set_id_attribute(u"second_firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Bob");
expected.set_label({});
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[0]);
expected.set_id_attribute(u"second_lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Hope");
expected.set_label({});
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[1]);
expected.set_id_attribute(u"second_email");
expected.set_name(expected.id_attribute());
expected.set_value(u"bobhope@example.com");
expected.set_label({});
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
}
// We should not report additional forms for empty forms.
TEST_F(FormAutofillTest, ExtractFormsNoFields) {
LoadHTML(R"(<form name=TestForm action='http://cnn.com'>
</form>)");
std::vector<FormData> forms = UpdateFormCache().updated_forms;
ASSERT_TRUE(forms.empty());
}
TEST_F(FormAutofillTest, WebFormElementToFormData_Autocomplete) {
{
// Form is still Autofill-able despite autocomplete=off.
LoadHTML(
R"(<form name=TestForm action='http://cnn.com' autocomplete=off>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
std::vector<WebFormElement> web_forms = GetDocument().GetTopLevelForms();
ASSERT_EQ(1U, web_forms.size());
WebFormElement web_form = web_forms[0];
EXPECT_TRUE(ExtractFormData(web_form));
}
}
TEST_F(FormAutofillTest, FindFormForInputElement) {
TestFindFormForInputElement(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com' autocomplete=off>
<input id=phone value='1.800.555.1234'>
<input type=submit name='reply-send' value=Send>
</form>)",
false);
}
TEST_F(FormAutofillTest, FindFormForInputElementForUnownedForm) {
TestFindFormForInputElement(
R"(<head><title>delivery recipient</title></head>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com' autocomplete=off>
<input id=phone value='1.800.555.1234'>
<input type=submit name='reply-send' value=Send>)",
true);
}
TEST_F(FormAutofillTest, FindFormForTextAreaElement) {
TestFindFormForTextAreaElement(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com' autocomplete=off>
<textarea id='street-address'>123 Fantasy Ln.&#10;Apt. 42</textarea>
<input type=submit name='reply-send' value=Send>
</form>)",
false);
}
TEST_F(FormAutofillTest, FindFormForTextAreaElementForUnownedForm) {
TestFindFormForTextAreaElement(
R"(<head><title>delivery address</title></head>
<input id=firstname value=John>
<input id=lastname value=Smith>
<input id=email value='john@example.com' autocomplete=off>
<textarea id='street-address'>123 Fantasy Ln.&#10;Apt. 42</textarea>
<input type=submit name='reply-send' value=Send>)",
true);
}
// Test regular FillForm function.
TEST_F(FormAutofillTest, FillForm) {
TestFillForm(kFormHtml, false, nullptr);
}
TEST_F(FormAutofillTest, FillFormForUnownedForm) {
TestFillForm(kUnownedFormHtml, true, nullptr);
}
TEST_F(FormAutofillTest, FillFormForUnownedUntitledForm) {
TestFillForm(kUnownedUntitledFormHtml, true,
"http://example.test/checkout_flow");
}
TEST_F(FormAutofillTest, FillFormForUnownedNonEnglishForm) {
TestFillForm(kUnownedNonEnglishFormHtml, true, nullptr);
}
TEST_F(FormAutofillTest, FillFormForUnownedNonASCIIForm) {
std::string html = R"(<head><title>accented latin: \xC3\xA0, )"
R"(thai: \xE0\xB8\x81, control: \x04, )"
R"(nbsp: \xEF\xBB\xBF, non-BMP: \xF0\x9F\x8C\x80; )"
R"(This should match a CHECKOUT flow )"
R"(despite the non-ASCII chars</title></head>)";
html.append(kUnownedUntitledFormHtml);
TestFillForm(html.c_str(), true, nullptr);
}
TEST_F(FormAutofillTest, PreviewFormX) {
TestPreviewForm(kFormHtml, false, nullptr);
}
TEST_F(FormAutofillTest, PreviewFormForUnownedForm) {
TestPreviewForm(kUnownedFormHtml, true, nullptr);
}
TEST_F(FormAutofillTest, PreviewFormForUnownedUntitledForm) {
TestPreviewForm(kUnownedUntitledFormHtml, true,
"http://example.test/Enter_Shipping_Address/");
}
TEST_F(FormAutofillTest, PreviewFormForUnownedNonEnglishForm) {
TestPreviewForm(kUnownedNonEnglishFormHtml, true, nullptr);
}
TEST_F(FormAutofillTest, Labels) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<label for=firstname> First name: </label>
<input id=firstname value=John>
<label for=lastname> Last name: </label>
<input id=lastname value=Smith>
<label for=email> Email: </label>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
// <label for=fieldId> elements are correctly assigned to their inputs. Multiple
// labels are separated with a space.
// TODO(crbug.com/40229922): Simplify the test using `ExpectLabels()`. This
// requires some refactoring of the fixture, as only owned forms are supported
// at the moment.
TEST_F(FormAutofillTest, LabelForAttribute) {
LoadHTML(R"(<label for=fieldId>foo</label>
<label for=fieldId>bar</label>
<input id=fieldId>)");
ASSERT_NE(GetMainFrame(), nullptr);
base::HistogramTester histogram_tester;
// Simulate seeing an unowned form containing just the input "fieldID".
FormData form = *ExtractFormData(WebFormElement());
ASSERT_EQ(form.fields().size(), 1u);
FormFieldData& form_field_data = test_api(form).field(0);
EXPECT_EQ(form_field_data.label(), u"foo bar");
EXPECT_EQ(form_field_data.label_source(), FormFieldData::LabelSource::kForId);
}
// Tests that when a label is assigned to an input, text behind it is considered
// as a fallback.
// The label is assigned to the input without the for-attribute, by declaring it
// it inside the label.
TEST_F(FormAutofillTest, LabelTextBehindInput) {
ExpectLabels(R"(
<form name=TestForm action=http://cnn.com>
<label>
<input>
label
</label>
</form>
)",
/*id_attributes=*/{u""}, /*name_attributes=*/{u""},
/*labels=*/{u"label"}, /*names=*/{u""}, /*values=*/{u""});
}
TEST_F(FormAutofillTest, LabelsWithSpans) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<label for=firstname><span>First name: </span></label>
<input id=firstname value=John>
<label for=lastname><span>Last name: </span></label>
<input id=lastname value=Smith>
<label for=email><span>Email: </span></label>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
// This test is different from FormAutofillTest.Labels in that the label
// elements for= attribute is set to the name of the form control element it is
// a label for instead of the id of the form control element. This is invalid
// because the for= attribute must be set to the id of the form control element;
// however, current label parsing code will extract the text from the previous
// label element and apply it to the following input field.
TEST_F(FormAutofillTest, InvalidLabels) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"");
name_attributes.push_back(u"firstname");
labels.push_back(u"First name:");
names.push_back(name_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"");
name_attributes.push_back(u"lastname");
labels.push_back(u"Last name:");
names.push_back(name_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"");
name_attributes.push_back(u"email");
labels.push_back(u"Email:");
names.push_back(name_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<label for=firstname> First name: </label>
<input name=firstname value=John>
<label for=lastname> Last name: </label>
<input name=lastname value=Smith>
<label for=email> Email: </label>
<input name=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
// This test has three form control elements, only one of which has a label
// element associated with it.
TEST_F(FormAutofillTest, OneLabelElement) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
First name:
<input id=firstname value=John>
<label for=lastname>Last name: </label>
<input id=lastname value=Smith>
Email:
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromText) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
First name:
<input id=firstname value=John>
Last name:
<input id=lastname value=Smith>
Email:
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromParagraph) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<p>First name:</p><input id=firstname value=John>
<p>Last name:</p>
<input id=lastname value=Smith>
<p>Email:</p>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromBold) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<b>First name:</b><input id=firstname value=John>
<b>Last name:</b>
<input id=lastname value=Smith>
<b>Email:</b>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredPriorToImgOrBr) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
First name:<img><input id=firstname value=John>
Last name:<img>
<input id=lastname value=Smith>
Email:<br>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromTableCell) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>First name:</td>
<td><input id=firstname value=John></td>
</tr>
<tr>
<td>Last name:</td>
<td><input id=lastname value=Smith></td>
</tr>
<tr>
<td>Email:</td>
<td><input id=email value='john@example.com'></td>
</tr>
<tr>
<td></td>
<td><input type=submit name='reply-send' value=Send></td>
</tr>
</table>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromTableCellTH) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<th>First name:</th>
<td><input id=firstname value=John></td>
</tr>
<tr>
<th>Last name:</th>
<td><input id=lastname value=Smith></td>
</tr>
<tr>
<th>Email:</th>
<td><input id=email value='john@example.com'></td>
</tr>
<tr>
<td></td>
<td><input type=submit name='reply-send' value=Send></td>
</tr>
</table>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromTableCellNested) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.push_back(u"");
labels.push_back(u"First name: Bogus");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.push_back(u"");
labels.push_back(u"Last name:");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.push_back(u"");
labels.push_back(u"Email:");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>
<font>
First name:
</font>
<font>
Bogus
</font>
</td>
<td>
<font>
<input id=firstname value=John>
</font>
</td>
</tr>
<tr>
<td>
<font>
Last name:
</font>
</td>
<td>
<font>
<input id=lastname value=Smith>
</font>
</td>
</tr>
<tr>
<td>
<font>
Email:
</font>
</td>
<td>
<font>
<input id=email value='john@example.com'>
</font>
</td>
</tr>
<tr>
<td></td>
<td>
<input type=submit name='reply-send' value=Send>
</td>
</tr>
</table>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, LabelsInferredFromTableEmptyTDs) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.push_back(u"");
labels.push_back(u"* First Name");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.push_back(u"");
labels.push_back(u"* Last Name");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.push_back(u"");
labels.push_back(u"* Email");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>
<span>*</span>
<b>First Name</b>
</td>
<td></td>
<td>
<input id=firstname value=John>
</td>
</tr>
<tr>
<td>
<span>*</span>
<b>Last Name</b>
</td>
<td></td>
<td>
<input id=lastname value=Smith>
</td>
</tr>
<tr>
<td>
<span>*</span>
<b>Email</b>
</td>
<td></td>
<td>
<input id=email value='john@example.com'>
</td>
</tr>
<tr>
<td></td>
<td>
<input type=submit name='reply-send' value=Send>
</td>
</tr>
</table>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, LabelsInferredFromPreviousTD) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.push_back(u"");
labels.push_back(u"* First Name");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.push_back(u"");
labels.push_back(u"* Last Name");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.push_back(u"");
labels.push_back(u"* Email");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>* First Name</td>
<td>
Bogus
<input type=hidden>
<input id=firstname value=John>
</td>
</tr>
<tr>
<td>* Last Name</td>
<td>
<input id=lastname value=Smith>
</td>
</tr>
<tr>
<td>* Email</td>
<td>
<input id=email value='john@example.com'>
</td>
</tr>
<tr>
<td></td>
<td>
<input type=submit name='reply-send' value=Send>
</td>
</tr>
</table>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
// <script>, <noscript> and <option> tags are excluded when the labels are
// inferred.
// Also <!-- comment --> is excluded.
TEST_F(FormAutofillTest, LabelsInferredFromTableWithSpecialElements) {
FormFieldData expected;
std::vector<FormFieldData> fields;
expected.set_id_attribute(u"firstname");
expected.set_name_attribute(u"");
expected.set_label(u"* First Name");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
fields.push_back(expected);
expected.set_id_attribute(u"middlename");
expected.set_name_attribute(u"");
expected.set_label(u"* Middle Name");
expected.set_name(expected.id_attribute());
expected.set_value(u"Joe");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
fields.push_back(expected);
expected.set_id_attribute(u"lastname");
expected.set_name_attribute(u"");
expected.set_label(u"* Last Name");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
fields.push_back(expected);
expected.set_id_attribute(u"country");
expected.set_name_attribute(u"");
expected.set_label(u"* Country");
expected.set_name(expected.id_attribute());
expected.set_value(u"US");
expected.set_form_control_type(FormControlType::kSelectOne);
expected.set_max_length(0);
fields.push_back(expected);
expected.set_id_attribute(u"email");
expected.set_name_attribute(u"");
expected.set_label(u"* Email");
expected.set_name(expected.id_attribute());
expected.set_value(u"john@example.com");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
fields.push_back(expected);
ExpectLabelsAndTypes(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>
<span>*</span>
<b>First Name</b>
</td>
<td>
<script> <!-- function test() { alert('ignored as label'); } -->
</script>
<input id=firstname value=John>
</td>
</tr>
<tr>
<td>
<span>*</span>
<b>Middle Name</b>
</td>
<td>
<noscript>
<p>Bad</p>
</noscript>
<input id=middlename value=Joe>
</td>
</tr>
<tr>
<td>
<span>*</span>
<b>Last Name</b>
</td>
<td>
<input id=lastname value=Smith>
</td>
</tr>
<tr>
<td>
<span>*</span>
<b>Country</b>
</td>
<td>
<select id=country>
<option value=US>The value should be ignored as label.
</option>
<option value=JP>JAPAN</option>
</select>
</td>
</tr>
<tr>
<td>
<span>*</span>
<b>Email</b>
</td>
<td>
<!-- This comment should be ignored as inferred label.-->
<input id=email value='john@example.com'>
</td>
</tr>
<tr>
<td></td>
<td>
<input type=submit name='reply-send' value=Send>
</td>
</tr>
</table>
</form>)",
fields);
}
TEST_F(FormAutofillTest, LabelsInferredFromTableLabels) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>
<label>First name:</label>
<input id=firstname value=John>
</td>
</tr>
<tr>
<td>
<label>Last name:</label>
<input id=lastname value=Smith>
</td>
</tr>
<tr>
<td>
<label>Email:</label>
<input id=email value='john@example.com'>
</td>
</tr>
</table>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromTableTDInterveningElements) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>
First name:
<br>
<input id=firstname value=John>
</td>
</tr>
<tr>
<td>
Last name:
<br>
<input id=lastname value=Smith>
</td>
</tr>
<tr>
<td>
Email:
<br>
<input id=email value='john@example.com'>
</td>
</tr>
</table>
<input type=submit name='reply-send' value=Send>
</form>)");
}
// Verify that we correctly infer labels when the label text spans multiple
// adjacent HTML elements, not separated by whitespace.
TEST_F(FormAutofillTest, LabelsInferredFromTableAdjacentElements) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.push_back(u"");
labels.push_back(u"*First Name");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.push_back(u"");
labels.push_back(u"*Last Name");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.push_back(u"");
labels.push_back(u"*Email");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>
<span>*</span><b>First Name</b>
</td>
<td>
<input id=firstname value=John>
</td>
</tr>
<tr>
<td>
<span>*</span><b>Last Name</b>
</td>
<td>
<input id=lastname value=Smith>
</td>
</tr>
<tr>
<td>
<span>*</span><b>Email</b>
</td>
<td>
<input id=email value='john@example.com'>
</td>
</tr>
<tr>
<td>
<input type=submit name='reply-send' value=Send>
</td>
</tr>
</table>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
// Verify that we correctly infer labels when the label text resides in the
// previous row.
TEST_F(FormAutofillTest, LabelsInferredFromTableRow) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.push_back(u"");
labels.push_back(u"*First Name");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.push_back(u"");
labels.push_back(u"*Last Name");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.push_back(u"");
labels.push_back(u"*Email");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
id_attributes.push_back(u"name2");
name_attributes.push_back(u"");
labels.push_back(u"NAME");
names.push_back(id_attributes.back());
values.push_back(u"John Smith");
id_attributes.push_back(u"email2");
name_attributes.push_back(u"");
labels.push_back(u"EMAIL");
names.push_back(id_attributes.back());
values.push_back(u"john@example2.com");
id_attributes.push_back(u"phone1");
name_attributes.push_back(u"");
labels.push_back(u"Phone");
names.push_back(id_attributes.back());
values.push_back(u"123");
id_attributes.push_back(u"phone2");
name_attributes.push_back(u"");
labels.push_back(u"Phone");
names.push_back(id_attributes.back());
values.push_back(u"456");
id_attributes.push_back(u"phone3");
name_attributes.push_back(u"");
labels.push_back(u"Phone");
names.push_back(id_attributes.back());
values.push_back(u"7890");
// Note that ccnumber uses the name attribute instead of the id attribute.
id_attributes.push_back(u"");
name_attributes.push_back(u"ccnumber");
labels.push_back(u"Credit Card Number");
names.push_back(name_attributes.back());
values.push_back(u"4444555544445555");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<table>
<tr>
<td>*First Name</td>
<td>*Last Name</td>
<td>*Email</td>
</tr>
<tr>
<td>
<input id=firstname value=John>
</td>
<td>
<input id=lastname value=Smith>
</td>
<td>
<input id=email value='john@example.com'>
</td>
</tr>
<tr>
<td colspan=2>NAME</td>
<td>EMAIL</td>
</tr>
<tr>
<td colspan=2>
<input id=name2 value='John Smith'>
</td>
<td>
<input id=email2 value='john@example2.com'>
</td>
</tr>
<tr>
<td>Phone</td>
</tr>
<tr>
<td>
<input id=phone1 value=123>
</td>
<td>
<input id=phone2 value=456>
</td>
<td>
<input id=phone3 value=7890>
</td>
</tr>
<tr>
<th>
Credit Card Number
</th>
</tr>
<tr>
<td>
<input name=ccnumber value=4444555544445555>
</td>
</tr>
<tr>
<td>
<input type=submit name='reply-send' value=Send>
</td>
</tr>
</table>)",
id_attributes, name_attributes, labels, names, values);
}
// Verify that we correctly infer labels when enclosed within a list item.
TEST_F(FormAutofillTest, LabelsInferredFromListItem) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"areacode");
name_attributes.push_back(u"");
labels.push_back(u"* Home Phone");
names.push_back(id_attributes.back());
values.push_back(u"415");
id_attributes.push_back(u"prefix");
name_attributes.push_back(u"");
labels.push_back(u"* Home Phone");
names.push_back(id_attributes.back());
values.push_back(u"555");
id_attributes.push_back(u"suffix");
name_attributes.push_back(u"");
labels.push_back(u"* Home Phone");
names.push_back(id_attributes.back());
values.push_back(u"1212");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<div>
<li>
<span>Bogus</span>
</li>
<li>
<label><em>*</em> Home Phone</label>
<input id=areacode value=415>
<input id=prefix value=555>
<input id=suffix value=1212>
</li>
<li>
<input type=submit name='reply-send' value=Send>
</li>
</div>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, LabelsInferredFromDefinitionList) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.push_back(u"");
labels.push_back(u"* First name: Bogus");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.push_back(u"");
labels.push_back(u"Last name:");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.push_back(u"");
labels.push_back(u"Email:");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<dl>
<dt>
<span>
*
</span>
<span>
First name:
</span>
<span>
Bogus
</span>
</dt>
<dd>
<font>
<input id=firstname value=John>
</font>
</dd>
<dt>
<span>
Last name:
</span>
</dt>
<dd>
<font>
<input id=lastname value=Smith>
</font>
</dd>
<dt>
<span>
Email:
</span>
</dt>
<dd>
<font>
<input id=email value='john@example.com'>
</font>
</dd>
<dt></dt>
<dd>
<input type=submit name='reply-send' value=Send>
</dd>
</dl>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, LabelsInferredWithSameName) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"");
name_attributes.push_back(u"Address");
labels.push_back(u"Address Line 1:");
names.push_back(name_attributes.back());
values.emplace_back();
id_attributes.push_back(u"");
name_attributes.push_back(u"Address");
labels.push_back(u"Address Line 2:");
names.push_back(name_attributes.back());
values.emplace_back();
id_attributes.push_back(u"");
name_attributes.push_back(u"Address");
labels.push_back(u"Address Line 3:");
names.push_back(name_attributes.back());
values.emplace_back();
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
Address Line 1:
<input name=Address>
Address Line 2:
<input name=Address>
Address Line 3:
<input name=Address>
<input type=submit name='reply-send' value=Send>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, LabelsInferredWithImageTags) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"");
name_attributes.push_back(u"dayphone1");
labels.push_back(u"Phone:");
names.push_back(name_attributes.back());
values.emplace_back();
id_attributes.push_back(u"");
name_attributes.push_back(u"dayphone2");
labels.push_back(u"");
names.push_back(name_attributes.back());
values.emplace_back();
id_attributes.push_back(u"");
name_attributes.push_back(u"dayphone3");
labels.push_back(u"");
names.push_back(name_attributes.back());
values.emplace_back();
id_attributes.push_back(u"");
name_attributes.push_back(u"dayphone4");
labels.push_back(u"ext.:");
names.push_back(name_attributes.back());
values.emplace_back();
id_attributes.push_back(u"");
name_attributes.push_back(u"dummy");
labels.emplace_back();
names.push_back(name_attributes.back());
values.emplace_back();
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
Phone:
<input name=dayphone1>
<img>
-
<img>
<input name=dayphone2>
<img>
-
<img>
<input name=dayphone3>
ext.:
<input name=dayphone4>
<input name=dummy>
<input type=submit name='reply-send' value=Send>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, LabelsInferredFromDivTable) {
ExpectJohnSmithLabelsAndNameAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<div>First name:<br>
<span>
<input name=firstname value=John>
</span>
</div>
<div>Last name:<br>
<span>
<input name=lastname value=Smith>
</span>
</div>
<div>Email:<br>
<span>
<input name=email value='john@example.com'>
</span>
</div>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromDivSiblingTable) {
ExpectJohnSmithLabelsAndNameAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<div>First name:</div>
<div>
<span>
<input name=firstname value=John>
</span>
</div>
<div>Last name:</div>
<div>
<span>
<input name=lastname value=Smith>
</span>
</div>
<div>Email:</div>
<div>
<span>
<input name=email value='john@example.com'>
</span>
</div>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromLabelInDivTable) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<label>First name:</label>
<label for=lastname>Last name:</label>
<div>
<input id=firstname value=John>
</div>
<div>
<input id=lastname value=Smith>
</div>
<label>Email:</label>
<div>
<span>
<input id=email value='john@example.com'>
</span>
</div>
<input type=submit name='reply-send' value=Send>
</form>)");
}
TEST_F(FormAutofillTest, LabelsInferredFromDefinitionListRatherThanDivTable) {
ExpectJohnSmithLabelsAndIdAttributes(
R"(<form name=TestForm action='http://cnn.com'>
<div>This is not a label.<br>
<dl>
<dt>
<span>
First name:
</span>
</dt>
<dd>
<font>
<input id=firstname value=John>
</font>
</dd>
<dt>
<span>
Last name:
</span>
</dt>
<dd>
<font>
<input id=lastname value=Smith>
</font>
</dd>
<dt>
<span>
Email:
</span>
</dt>
<dd>
<font>
<input id=email value='john@example.com'>
</font>
</dd>
<dt></dt>
<dd>
<input type=submit name='reply-send' value=Send>
</dd>
</dl>
</div>
</form>)");
}
TEST_F(FormAutofillTest, FillFormMaxLength) {
TestFillFormMaxLength(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname maxlength=5>
<input id=lastname maxlength=7>
<input id=email maxlength=9>
<input type=submit name='reply-send' value=Send>
</form>)",
false);
}
TEST_F(FormAutofillTest, FillFormMaxLengthForUnownedForm) {
TestFillFormMaxLength(
R"(<head><title>delivery recipient info</title></head>
<input id=firstname maxlength=5>
<input id=lastname maxlength=7>
<input id=email maxlength=9>
<input type=submit name='reply-send' value=Send>)",
true);
}
// This test uses negative values of the maxlength attribute for input elements.
// In this case, the maxlength of the input elements is set to the default
// maxlength (defined in WebKit.)
TEST_F(FormAutofillTest, FillFormNegativeMaxLength) {
TestFillFormNegativeMaxLength(
R"(<head><title>delivery recipient info</title></head>
<form name=TestForm action='http://abc.com'>
<input id=firstname maxlength='-1'>
<input id=lastname maxlength='-10'>
<input id=email maxlength='-13'>
<input type=submit name='reply-send' value=Send>
</form>)",
false);
}
TEST_F(FormAutofillTest, FillFormNegativeMaxLengthForUnownedForm) {
TestFillFormNegativeMaxLength(
R"(<head><title>delivery recipient info</title></head>
<input id=firstname maxlength='-1'>
<input id=lastname maxlength='-10'>
<input id=email maxlength='-13'>
<input type=submit name='reply-send' value=Send>)",
true);
}
TEST_F(FormAutofillTest, FillFormEmptyName) {
TestFillFormEmptyName(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname>
<input id=lastname>
<input id=email>
<input type=submit value=Send>
</form>)",
false);
}
TEST_F(FormAutofillTest, FillFormEmptyNameForUnownedForm) {
TestFillFormEmptyName(
R"(<head><title>delivery recipient info</title></head>
<input id=firstname>
<input id=lastname>
<input id=email>
<input type=submit value=Send>)",
true);
}
TEST_F(FormAutofillTest, FillFormEmptyFormNames) {
TestFillFormEmptyFormNames(
R"(<form action='http://abc.com'>
<input id=firstname>
<input id=middlename>
<input id=lastname>
<input type=submit value=Send>
</form>
<form action='http://abc.com'>
<input id=apple>
<input id=banana>
<input id=cantelope>
<input type=submit value=Send>
</form>)",
false);
}
TEST_F(FormAutofillTest, FillFormEmptyFormNamesForUnownedForm) {
TestFillFormEmptyFormNames(
R"(<head><title>enter delivery preferences</title></head>
<input id=firstname>
<input id=middlename>
<input id=lastname>
<input id=apple>
<input id=banana>
<input id=cantelope>
<input type=submit value=Send>)",
true);
}
TEST_F(FormAutofillTest, ThreePartPhone) {
LoadHTML(R"(<form name=TestForm action='http://cnn.com'>
Phone:
<input name=dayphone1>
-
<input name=dayphone2>
-
<input name=dayphone3>
ext.:
<input name=dayphone4>
<input type=submit name='reply-send' value=Send>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
std::vector<WebFormElement> forms = frame->GetDocument().GetTopLevelForms();
ASSERT_EQ(1U, forms.size());
FormData form = *ExtractFormData(forms[0]);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://cnn.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(4U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_label(u"Phone:");
expected.set_name_attribute(u"dayphone1");
expected.set_name(expected.name_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_label(u"");
expected.set_name_attribute(u"dayphone2");
expected.set_name(expected.name_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_label(u"");
expected.set_name_attribute(u"dayphone3");
expected.set_name(expected.name_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
expected.set_label(u"ext.:");
expected.set_name_attribute(u"dayphone4");
expected.set_name(expected.name_attribute());
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
}
TEST_F(FormAutofillTest, MaxLengthFields) {
LoadHTML(R"(<form name=TestForm action='http://cnn.com'>
Phone:
<input maxlength=3 name=dayphone1>
-
<input maxlength=3 name=dayphone2>
-
<input maxlength=4 size=5 name=dayphone3>
ext.:
<input maxlength=5 name=dayphone4>
<input name=default1>
<input maxlength='-1' name=invalid1>
<input type=submit name='reply-send' value=Send>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
std::vector<WebFormElement> forms = frame->GetDocument().GetTopLevelForms();
ASSERT_EQ(1U, forms.size());
FormData form = *ExtractFormData(forms[0]);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://cnn.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(6U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_name_attribute(u"dayphone1");
expected.set_label(u"Phone:");
expected.set_name(expected.name_attribute());
expected.set_max_length(3);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_name_attribute(u"dayphone2");
expected.set_label(u"");
expected.set_name(expected.name_attribute());
expected.set_max_length(3);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_name_attribute(u"dayphone3");
expected.set_label(u"");
expected.set_name(expected.name_attribute());
expected.set_max_length(4);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
expected.set_name_attribute(u"dayphone4");
expected.set_label(u"ext.:");
expected.set_name(expected.name_attribute());
expected.set_max_length(5);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[3]);
// When unspecified `size`, default is returned.
expected.set_name_attribute(u"default1");
expected.set_label({});
expected.set_name(expected.name_attribute());
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[4]);
// When invalid `size`, default is returned.
expected.set_name_attribute(u"invalid1");
expected.set_label({});
expected.set_name(expected.name_attribute());
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[5]);
}
// This test re-creates the experience of typing in a field then selecting a
// profile from the Autofill suggestions popup. The field that is being typed
// into should be filled even though it's not technically empty.
TEST_F(FormAutofillTest, FillFormNonEmptyField) {
TestFillFormNonEmptyField(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname>
<input id=lastname>
<input id=email>
<input type=submit value=Send>
</form>)",
false, nullptr, nullptr, nullptr, nullptr, nullptr);
}
TEST_F(FormAutofillTest, FillFormNonEmptyFieldsWithDefaultValues) {
TestFillFormNonEmptyField(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname value='Enter first name'>
<input id=lastname value='Enter last name'>
<input id=email value='Enter email'>
<input type=submit value=Send>
</form>)",
false, "Enter last name", "Enter email", nullptr, nullptr, nullptr);
}
TEST_F(FormAutofillTest, FillFormModifyValues) {
TestFillFormAndModifyValues(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname placeholder='First Name' value='First Name'>
<input id=lastname placeholder='Last Name' value='Last Name'>
<input id=phone placeholder=Phone value=Phone>
<input id=cc placeholder='Credit Card Number' value='Credit Card'>
<input id=city placeholder=City value=City>
<select id=state name=state placeholder=State>
<option selected>?</option>
<option>AA</option>
<option>AE</option>
<option>AK</option>
</select>
<input type=submit value=Send>
</form>)",
"First Name", "Last Name", "Phone", "Credit Card Number", "City",
"State");
}
TEST_F(FormAutofillTest, FillFormModifyInitiatingValue) {
TestFillFormAndModifyInitiatingValue(
R"(<form name=TestForm action='http://abc.com'>
<input id=cc placeholder='Credit Card Number' value='Credit Card'>
<input id=expiration_date placeholder='Expiration Date'
value='Expiration Date'>
<input id=name placeholder='Full Name' value='Full Name'>
<input type=submit value=Send>
</form>)",
"Credit Card Number", "Expiration Date", "Full Name");
}
TEST_F(FormAutofillTest, FillFormJSModifiesUserInputValue) {
TestFillFormJSModifiesUserInputValue(
R"(<form name=TestForm action='http://abc.com'>
<input id=cc placeholder='Credit Card Number' value='Credit Card'>
<input id=expiration_date placeholder='Expiration Date'
value='Expiration Date'>
<input id=name placeholder='Full Name' value='Full Name'>
<input type=submit value=Send>
</form>)",
"Credit Card Number", "Expiration Date", "Full Name");
}
TEST_F(FormAutofillTest, FillFormNonEmptyFieldsWithPlaceholderValues) {
TestFillFormNonEmptyField(
R"(<form name=TestForm action='http://abc.com' method=POST>
<input id=firstname placeholder='First Name' value='First Name'>
<input id=lastname placeholder='Last Name' value='Last Name'>
<input id=email placeholder=Email value=Email>
<input type=submit value=Send>
</form>)",
false, nullptr, nullptr, "First Name", "Last Name", "Email");
}
TEST_F(FormAutofillTest, FillFormNonEmptyFieldForUnownedForm) {
TestFillFormNonEmptyField(
R"(<head><title>delivery recipient info</title></head>
<input id=firstname>
<input id=lastname>
<input id=email>
<input type=submit value=Send>)",
true, nullptr, nullptr, nullptr, nullptr, nullptr);
}
TEST_F(FormAutofillTest, UndoAutofill) {
LoadHTML(R"(
<form id=form_id>
<input id=text_id_1>
<input id=text_id_2>
<select id=select_id_1>
<option value=undo_select_option_1>Foo</option>
<option value=autofill_select_option_1>Bar</option>
</select>
<select id=select_id_2>
<option value=undo_select_option_2>Foo</option>
<option value=autofill_select_option_2>Bar</option>
</select>
</form>
)");
WebFormControlElement text_element_1 = GetFormControlElementById("text_id_1");
WebFormControlElement text_element_2 = GetFormControlElementById("text_id_2");
text_element_1.SetAutofillValue("autofill_text_1",
WebAutofillState::kAutofilled);
text_element_2.SetAutofillValue("autofill_text_2",
WebAutofillState::kAutofilled);
WebFormControlElement select_element_1 =
GetFormControlElementById("select_id_1");
WebFormControlElement select_element_2 =
GetFormControlElementById("select_id_2");
select_element_1.SetAutofillValue("autofill_select_option_1",
WebAutofillState::kAutofilled);
select_element_2.SetAutofillValue("autofill_select_option_2",
WebAutofillState::kAutofilled);
auto HasAutofillValue = [](const WebString& value,
WebAutofillState autofill_state) {
return ::testing::AllOf(
::testing::Property(&WebFormControlElement::Value, value),
::testing::Property(&WebFormControlElement::GetAutofillState,
autofill_state));
};
ASSERT_THAT(text_element_1, HasAutofillValue("autofill_text_1",
WebAutofillState::kAutofilled));
ASSERT_THAT(text_element_2, HasAutofillValue("autofill_text_2",
WebAutofillState::kAutofilled));
ASSERT_THAT(select_element_1,
HasAutofillValue("autofill_select_option_1",
WebAutofillState::kAutofilled));
ASSERT_THAT(select_element_2,
HasAutofillValue("autofill_select_option_2",
WebAutofillState::kAutofilled));
std::vector<WebFormElement> forms =
GetMainFrame()->GetDocument().GetTopLevelForms();
EXPECT_EQ(1U, forms.size());
FormData form = *ExtractFormData(forms[0]);
EXPECT_EQ(form.fields().size(), 4u);
std::vector<FormFieldData> undo_fields;
for (size_t i = 0; i < 4; i += 2) {
std::u16string type = i == 0 ? u"text" : u"select_option";
test_api(form).field(i).set_value(u"undo_" + type + u"_1");
test_api(form).field(i).set_is_autofilled(false);
undo_fields.push_back(form.fields()[i]);
}
form.set_fields(undo_fields);
ExecuteJavaScriptForTests("document.getElementById('text_id_1').focus();");
ApplyFieldsAction(text_element_1.GetDocument(), form.fields(),
mojom::ActionPersistence::kFill,
mojom::FormActionType::kUndo);
EXPECT_THAT(text_element_1,
HasAutofillValue("undo_text_1", WebAutofillState::kNotFilled));
EXPECT_THAT(text_element_2, HasAutofillValue("autofill_text_2",
WebAutofillState::kAutofilled));
EXPECT_THAT(select_element_1, HasAutofillValue("undo_select_option_1",
WebAutofillState::kNotFilled));
EXPECT_THAT(select_element_2,
HasAutofillValue("autofill_select_option_2",
WebAutofillState::kAutofilled));
}
TEST_F(FormAutofillTest, ClearPreviewedElements) {
TestClearPreviewedElements(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname value=Wyatt>
<input id=lastname>
<input id=email>
<input type=email id=email2>
<input type=tel id=phone>
<input type=submit value=Send>
</form>)");
}
TEST_F(FormAutofillTest, ClearPreviewedFormWithElementForUnownedForm) {
TestClearPreviewedElements(
R"(<head><title>store checkout</title></head>
<input id=firstname value=Wyatt>
<input id=lastname>
<input id=email>
<input type=email id=email2>
<input type=tel id=phone>
<input type=submit value=Send>)");
}
TEST_F(FormAutofillTest, ClearPreviewedFormWithNonEmptyInitiatingNode) {
TestClearPreviewedFormWithNonEmptyInitiatingNode(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname value=W>
<input id=lastname>
<input id=email>
<input type=email id=email2>
<input type=tel id=phone>
<input type=submit value=Send>
</form>)");
}
TEST_F(FormAutofillTest,
ClearPreviewedFormWithNonEmptyInitiatingNodeForUnownedForm) {
TestClearPreviewedFormWithNonEmptyInitiatingNode(
R"(<head><title>shipping details</title></head>
<input id=firstname value=W>
<input id=lastname>
<input id=email>
<input type=email id=email2>
<input type=tel id=phone>
<input type=submit value=Send>)");
}
TEST_F(FormAutofillTest, ClearPreviewedFormWithAutofilledInitiatingNode) {
TestClearPreviewedFormWithAutofilledInitiatingNode(
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname value=W>
<input id=lastname>
<input id=email>
<input type=email id=email2>
<input type=tel id=phone>
<input type=submit value=Send>
</form>)");
}
TEST_F(FormAutofillTest,
ClearPreviewedFormWithAutofilledInitiatingNodeForUnownedForm) {
TestClearPreviewedFormWithAutofilledInitiatingNode(
R"(<head><title>shipping details</title></head>
<input id=firstname value=W>
<input id=lastname>
<input id=email>
<input type=email id=email2>
<input type=tel id=phone>
<input type=submit value=Send>)");
}
// If we have multiple labels per id, the labels concatenated into label string.
TEST_F(FormAutofillTest, MultipleLabelsPerElement) {
std::vector<std::u16string> id_attributes, name_attributes, labels, names,
values;
id_attributes.push_back(u"firstname");
name_attributes.emplace_back();
labels.push_back(u"First Name:");
names.push_back(id_attributes.back());
values.push_back(u"John");
id_attributes.push_back(u"lastname");
name_attributes.emplace_back();
labels.push_back(u"Last Name:");
names.push_back(id_attributes.back());
values.push_back(u"Smith");
id_attributes.push_back(u"email");
name_attributes.emplace_back();
labels.push_back(u"Email: xxx@yyy.com");
names.push_back(id_attributes.back());
values.push_back(u"john@example.com");
ExpectLabels(
R"(<form name=TestForm action='http://cnn.com'>
<label for=firstname> First Name: </label>
<label for=firstname></label>
<input id=firstname value=John>
<label for=lastname></label>
<label for=lastname> Last Name: </label>
<input id=lastname value=Smith>
<label for=email> Email: </label>
<label for=email> xxx@yyy.com </label>
<input id=email value='john@example.com'>
<input type=submit name='reply-send' value=Send>
</form>)",
id_attributes, name_attributes, labels, names, values);
}
TEST_F(FormAutofillTest, ClickElement) {
LoadHTML(R"(<button id=link>Button</button>
<button name=button>Button</button>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
// Successful retrieval by id.
WebElementDescriptor clicker;
clicker.retrieval_method = WebElementDescriptor::ID;
clicker.descriptor = "link";
EXPECT_TRUE(ClickElement(frame->GetDocument(), clicker));
// Successful retrieval by css selector.
clicker.retrieval_method = WebElementDescriptor::CSS_SELECTOR;
clicker.descriptor = "button[name='button']";
EXPECT_TRUE(ClickElement(frame->GetDocument(), clicker));
// Unsuccessful retrieval due to invalid CSS selector.
clicker.descriptor = "^*&";
EXPECT_FALSE(ClickElement(frame->GetDocument(), clicker));
// Unsuccessful retrieval because element does not exist.
clicker.descriptor = "#junk";
EXPECT_FALSE(ClickElement(frame->GetDocument(), clicker));
}
TEST_F(FormAutofillTest, SelectOneAsText) {
LoadHTML(R"(<form name=TestForm action='http://cnn.com'>
<input id=firstname value=John>
<input id=lastname value=Smith>
<select id=country>
<option value=AF>Afghanistan</option>
<option value=AL>Albania</option>
<option value=DZ>Algeria</option>
</select>
<input type=submit name='reply-send' value=Send>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
// Set the value of the select-one.
WebSelectElement select_element =
frame->GetDocument().GetElementById("country").To<WebSelectElement>();
select_element.SetValue(WebString::FromUTF8("AL"));
std::vector<WebFormElement> forms = frame->GetDocument().GetTopLevelForms();
ASSERT_EQ(1U, forms.size());
FormData form = *ExtractFormData(forms[0]);
EXPECT_EQ(u"TestForm", form.name());
EXPECT_EQ(GURL("http://cnn.com"), form.action());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"John");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Smith");
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"country");
expected.set_name(expected.id_attribute());
expected.set_value(u"AL");
expected.set_label({});
expected.set_form_control_type(FormControlType::kSelectOne);
expected.set_max_length(0);
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
EXPECT_THAT(fields[2].selected_option().CopyAsOptional(),
Optional(Field(&SelectOption::text, u"Albania")));
}
TEST_F(FormAutofillTest, UnownedFormElementsToFormDataWithoutForm) {
LoadHTML(R"(<head><title>delivery info</title></head>
<div>
<label for=firstname>First name:</label>
<label for=lastname>Last name:</label>
<input id=firstname value=John>
<input id=lastname value=Smith>
<label for=email>Email:</label>
<input id=email value='john@example.com'>
</div>)");
FormData form = *ExtractFormData(WebFormElement());
EXPECT_TRUE(form.name().empty());
EXPECT_FALSE(form.action().is_valid());
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
FormFieldData expected;
expected.set_form_control_type(FormControlType::kInputText);
expected.set_max_length(FormFieldData::kDefaultMaxLength);
expected.set_id_attribute(u"firstname");
expected.set_name(expected.id_attribute());
expected.set_value(u"John");
expected.set_label(u"First name:");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[0]);
expected.set_id_attribute(u"lastname");
expected.set_name(expected.id_attribute());
expected.set_value(u"Smith");
expected.set_label(u"Last name:");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[1]);
expected.set_id_attribute(u"email");
expected.set_name(expected.id_attribute());
expected.set_value(u"john@example.com");
expected.set_label(u"Email:");
EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields[2]);
}
TEST_F(FormAutofillTest, UnownedFormElementsToFormDataWithForm) {
LoadHTML(kFormHtml);
EXPECT_FALSE(ExtractFormData(WebFormElement()));
}
TEST_F(FormAutofillTest, FormlessForms) {
LoadHTML(kUnownedUntitledFormHtml);
EXPECT_TRUE(ExtractFormData(WebFormElement()));
}
TEST_F(FormAutofillTest, FormCache_ExtractNewForms) {
struct {
const char* description;
const char* html;
const size_t number_of_extracted_forms;
const bool is_form_tag;
} test_cases[] = {
// An empty form should not be extracted
{"Empty Form",
R"(<form name=TestForm action='http://abc.com'>
</form>)",
0u, true},
// A form with less than three fields with no autocomplete type(s) should
// be extracted because no minimum is being enforced for upload.
{"Small Form no autocomplete",
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname>
</form>)",
1u, true},
// A form with less than three fields with at least one autocomplete type
// should be extracted.
{"Small Form w/ autocomplete",
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname autocomplete='given-name'>
</form>)",
1u, true},
// A form with three or more fields should be extracted.
{"3 Field Form",
R"(<form name=TestForm action='http://abc.com'>
<input id=firstname>
<input id=lastname>
<input id=email>
<input type=submit value=Send>
</form>)",
1u, true},
// An input field with an autocomplete attribute outside of a form should
// be extracted.
{"Small, formless, with autocomplete",
R"(<input id=firstname autocomplete='given-name'>
<input type=submit value=Send>)",
1u, false},
// An input field without an autocomplete attribute outside of a form,
// with no checkout hints, should not be extracted.
{"Small, formless, no autocomplete",
R"(<input id=firstname>
<input type=submit value=Send>)",
1u, false},
// A form with one field which is password gets extracted.
{"Password-Only",
R"(<form name=TestForm action='http://abc.com'>
<input type=password id=pw>
</form>)",
1u, true},
// A form with two fields which are passwords should be extracted.
{"two passwords",
R"(<form name=TestForm action='http://abc.com'>
<input type=password id=pw>
<input type=password id=new_pw>
</form>)",
1u, true},
};
for (auto test_case : test_cases) {
SCOPED_TRACE(test_case.description);
LoadHTML(test_case.html);
std::vector<FormData> forms = UpdateFormCache().updated_forms;
EXPECT_EQ(test_case.number_of_extracted_forms, forms.size());
if (!forms.empty())
EXPECT_EQ(test_case.is_form_tag, !forms.back().renderer_id().is_null());
}
}
TEST_F(FormAutofillTest, AriaLabelAndDescription) {
LoadHTML(
R"(<form id=form>
<div id=label>aria label</div>
<div id=description>aria description</div>
<input id=field0 aria-label='inline aria label'>
<input id=field1 aria-labelledby='label'>
<input id=field2 aria-describedby='description'>
</form>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
WebFormControlElement control_element =
frame->GetDocument().GetElementById("field0").To<WebFormControlElement>();
ASSERT_TRUE(control_element);
FormData form = FindForm(control_element);
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
// Field 0
EXPECT_EQ(u"inline aria label", fields[0].aria_label());
EXPECT_EQ(u"", fields[0].aria_description());
// Field 1
EXPECT_EQ(u"aria label", fields[1].aria_label());
EXPECT_EQ(u"", fields[1].aria_description());
// Field 2
EXPECT_EQ(u"", fields[2].aria_label());
EXPECT_EQ(u"aria description", fields[2].aria_description());
}
TEST_F(FormAutofillTest, AriaLabelAndDescription2) {
LoadHTML(
R"(<form id=form>
<input id=field0 aria-label='inline aria label'>
<input id=field1 aria-labelledby='label'>
<input id=field2 aria-describedby='description'>
</form>
<div id=label>aria label</div>
<div id=description>aria description</div>)");
WebLocalFrame* frame = GetMainFrame();
ASSERT_NE(nullptr, frame);
WebFormElement web_form =
frame->GetDocument().GetElementById("form").To<WebFormElement>();
ASSERT_TRUE(web_form);
WebFormControlElement control_element =
frame->GetDocument().GetElementById("field0").To<WebFormControlElement>();
ASSERT_TRUE(control_element);
FormData form = FindForm(control_element);
const std::vector<FormFieldData>& fields = form.fields();
ASSERT_EQ(3U, fields.size());
// Field 0
EXPECT_EQ(u"inline aria label", fields[0].aria_label());
EXPECT_EQ(u"", fields[0].aria_description());
// Field 1
EXPECT_EQ(u"aria label", fields[1].aria_label());
EXPECT_EQ(u"", fields[1].aria_description());
// Field 2
EXPECT_EQ(u"", fields[2].aria_label());
EXPECT_EQ(u"aria description", fields[2].aria_description());
}
} // namespace
} // namespace autofill::form_util