blob: f239cca572490ce774c61b6ace7f688a127f8d4a [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/scoped_feature_list.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/autofill/content/renderer/focus_test_utils.h"
#include "components/autofill/content/renderer/form_autofill_util.h"
#include "components/autofill/content/renderer/form_cache.h"
#include "components/autofill/content/renderer/form_cache_test_api.h"
#include "components/autofill/content/renderer/test_utils.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/form_field_data.h"
#include "content/public/test/render_view_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_document.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_remote_frame.h"
#include "third_party/blink/public/web/web_select_element.h"
using base::ASCIIToUTF16;
using blink::WebDocument;
using blink::WebElement;
using blink::WebInputElement;
using blink::WebSelectElement;
using blink::WebString;
using testing::AllOf;
using testing::ElementsAre;
using testing::Field;
using testing::UnorderedElementsAre;
namespace autofill {
auto HasId(FormRendererId expected_id) {
return Field("unique_renderer_id", &FormData::unique_renderer_id,
expected_id);
}
auto HasName(base::StringPiece expected_name) {
return Field("name", &FormData::name, base::ASCIIToUTF16(expected_name));
}
auto IsToken(FrameToken expected_token, int expected_predecessor) {
return AllOf(
Field(&FrameTokenWithPredecessor::token, expected_token),
Field(&FrameTokenWithPredecessor::predecessor, expected_predecessor));
}
const FormData* GetFormByName(const std::vector<FormData>& forms,
base::StringPiece name) {
for (const FormData& form : forms) {
if (form.name == ASCIIToUTF16(name))
return &form;
}
return nullptr;
}
class FormCacheBrowserTest : public content::RenderViewTest {
public:
FormCacheBrowserTest() {
focus_test_utils_ = std::make_unique<test::FocusTestUtils>(
base::BindRepeating(&FormCacheBrowserTest::ExecuteJavaScriptForTests,
base::Unretained(this)));
}
~FormCacheBrowserTest() override = default;
FormCacheBrowserTest(const FormCacheBrowserTest&) = delete;
FormCacheBrowserTest& operator=(const FormCacheBrowserTest&) = delete;
protected:
std::string GetFocusLog() {
return focus_test_utils_->GetFocusLog(GetMainFrame()->GetDocument());
}
std::unique_ptr<test::FocusTestUtils> focus_test_utils_;
};
TEST_F(FormCacheBrowserTest, UpdatedForms) {
LoadHTML(R"(
<form id="form1">
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
</form>
<input type="text" name="unowned_element">
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms = form_cache.UpdateFormCache(nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
const FormData* form1 = GetFormByName(forms.updated_forms, "form1");
ASSERT_TRUE(form1);
EXPECT_EQ(3u, form1->fields.size());
EXPECT_TRUE(form1->child_frames.empty());
const FormData* unowned_form = GetFormByName(forms.updated_forms, "");
ASSERT_TRUE(unowned_form);
EXPECT_EQ(1u, unowned_form->fields.size());
EXPECT_TRUE(unowned_form->child_frames.empty());
}
TEST_F(FormCacheBrowserTest, RemovedForms) {
LoadHTML(R"(
<form id="form1">
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
</form>
<form id="form2">
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
</form>
<input type="text" id="unowned_element">
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms = form_cache.UpdateFormCache(nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1"),
HasName("form2")));
EXPECT_TRUE(forms.removed_forms.empty());
ExecuteJavaScriptForTests(R"(
document.getElementById("form1").remove();
document.getElementById("form2").innerHTML = "";
)");
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_THAT(forms.removed_forms,
UnorderedElementsAre(FormRendererId(1), FormRendererId(2)));
ExecuteJavaScriptForTests(R"(
document.getElementById("unowned_element").remove();
)");
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_THAT(forms.removed_forms, ElementsAre(FormRendererId()));
ExecuteJavaScriptForTests(R"(
document.getElementById("form2").innerHTML = `
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
`;
)");
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasName("form2")));
EXPECT_TRUE(forms.removed_forms.empty());
ExecuteJavaScriptForTests(R"(
document.getElementById("form2").innerHTML = `
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
<input type="text" name="foo4">
`;
)");
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasName("form2")));
EXPECT_TRUE(forms.removed_forms.empty());
}
// Test if the form gets re-extracted after a label change.
TEST_F(FormCacheBrowserTest, ExtractFormAfterDynamicFieldChange) {
LoadHTML(R"(
<form id="f"><input></form>
<form id="g"> <label id="label">Name</label><input></form>
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasName("f"), HasName("g")));
EXPECT_TRUE(forms.removed_forms.empty());
ExecuteJavaScriptForTests(R"(
document.getElementById("label").innerHTML = "Last Name";
)");
forms = form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasName("g")));
EXPECT_TRUE(forms.removed_forms.empty());
}
class FormCacheIframeBrowserTest : public FormCacheBrowserTest {
public:
FormCacheIframeBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(features::kAutofillAcrossIframes);
}
~FormCacheIframeBrowserTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(FormCacheIframeBrowserTest, ExtractFrames) {
LoadHTML(R"(
<form id="form1">
<iframe id="frame1"></iframe>
</form>
<iframe id="frame2"></iframe>
)");
FrameToken frame1_token =
GetFrameToken(GetMainFrame()->GetDocument(), "frame1");
FrameToken frame2_token =
GetFrameToken(GetMainFrame()->GetDocument(), "frame2");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
const FormData* form1 = GetFormByName(forms.updated_forms, "form1");
ASSERT_TRUE(form1);
EXPECT_TRUE(form1->fields.empty());
EXPECT_THAT(form1->child_frames, ElementsAre(IsToken(frame1_token, -1)));
const FormData* unowned_form = GetFormByName(forms.updated_forms, "");
ASSERT_TRUE(unowned_form);
EXPECT_TRUE(unowned_form->fields.empty());
EXPECT_THAT(unowned_form->child_frames,
ElementsAre(AllOf(IsToken(frame2_token, -1))));
}
TEST_F(FormCacheBrowserTest, ExtractFormsTwice) {
LoadHTML(R"(
<form id="form1">
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
</form>
<input type="text" name="unowned_element">
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
forms = form_cache.UpdateFormCache(nullptr);
// As nothing has changed, there are no new or removed forms.
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_TRUE(forms.removed_forms.empty());
}
TEST_F(FormCacheIframeBrowserTest, ExtractFramesTwice) {
LoadHTML(R"(
<form id="form1">
<iframe></iframe>
</form>
<iframe></iframe>
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
forms = form_cache.UpdateFormCache(nullptr);
// As nothing has changed, there are no new or removed forms.
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_TRUE(forms.removed_forms.empty());
}
// TODO(crbug.com/1117028) Adjust expectations when we omit invisible iframes.
TEST_F(FormCacheIframeBrowserTest, ExtractFramesAfterVisibilityChange) {
LoadHTML(R"(
<form id="form1">
<iframe id="frame1" style="display: none;"></iframe>
<iframe id="frame2" style="display: none;"></iframe>
</form>
<iframe id="frame3" style="display: none;"></iframe>
)");
WebElement iframe1 = GetElementById(GetMainFrame()->GetDocument(), "frame1");
WebElement iframe2 = GetElementById(GetMainFrame()->GetDocument(), "frame2");
WebElement iframe3 = GetElementById(GetMainFrame()->GetDocument(), "frame3");
auto GetSize = [](const WebElement& element) {
gfx::Rect bounds = element.BoundsInWidget();
return bounds.width() * bounds.height();
};
ASSERT_LE(GetSize(iframe1), 0);
ASSERT_LE(GetSize(iframe2), 0);
ASSERT_LE(GetSize(iframe3), 0);
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
iframe1.SetAttribute("style", "display: block;");
iframe2.SetAttribute("style", "display: block;");
iframe3.SetAttribute("style", "display: block;");
ASSERT_GT(GetSize(iframe1), 0);
ASSERT_GT(GetSize(iframe2), 0);
ASSERT_GT(GetSize(iframe3), 0);
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_TRUE(forms.removed_forms.empty());
iframe2.SetAttribute("style", "display: none;");
iframe3.SetAttribute("style", "display: none;");
ASSERT_GT(GetSize(iframe1), 0);
ASSERT_LE(GetSize(iframe2), 0);
ASSERT_LE(GetSize(iframe3), 0);
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_TRUE(forms.removed_forms.empty());
}
TEST_F(FormCacheBrowserTest, ExtractFormsAfterModification) {
LoadHTML(R"(
<form id="form1">
<input type="text" name="foo1">
<input type="text" name="foo2">
<input type="text" name="foo3">
</form>
<input type="text" name="unowned_element">
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
// Append an input element to the form and to the list of unowned inputs.
ExecuteJavaScriptForTests(R"(
var new_input_1 = document.createElement("input");
new_input_1.setAttribute("type", "text");
new_input_1.setAttribute("name", "foo4");
var form1 = document.getElementById("form1");
form1.appendChild(new_input_1);
var new_input_2 = document.createElement("input");
new_input_2.setAttribute("type", "text");
new_input_2.setAttribute("name", "unowned_element_2");
document.body.appendChild(new_input_2);
)");
forms = form_cache.UpdateFormCache(nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("form1")));
EXPECT_TRUE(forms.removed_forms.empty());
const FormData* form1 = GetFormByName(forms.updated_forms, "form1");
ASSERT_TRUE(form1);
EXPECT_EQ(4u, form1->fields.size());
const FormData* unowned_form = GetFormByName(forms.updated_forms, "");
ASSERT_TRUE(unowned_form);
EXPECT_EQ(2u, unowned_form->fields.size());
}
TEST_F(FormCacheBrowserTest, FillAndClear) {
LoadHTML(R"(
<input type="text" name="text" id="text">
<input type="checkbox" checked name="checkbox" id="checkbox">
<select name="select" id="select">
<option value="first">first</option>
<option value="second" selected>second</option>
</select>
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasId(FormRendererId())));
EXPECT_TRUE(forms.removed_forms.empty());
FormData values_to_fill = forms.updated_forms[0];
values_to_fill.fields[0].value = u"test";
values_to_fill.fields[0].is_autofilled = true;
values_to_fill.fields[1].check_status =
FormFieldData::CheckStatus::kCheckableButUnchecked;
values_to_fill.fields[1].is_autofilled = true;
values_to_fill.fields[2].value = u"first";
values_to_fill.fields[2].is_autofilled = true;
WebDocument doc = GetMainFrame()->GetDocument();
auto text = GetFormControlElementById(doc, "text");
auto checkbox = GetElementById(doc, "checkbox").To<WebInputElement>();
auto select_element = GetFormControlElementById(doc, "select");
form_util::FillOrPreviewForm(values_to_fill, text,
mojom::RendererFormDataAction::kFill);
EXPECT_EQ("test", text.Value().Ascii());
EXPECT_FALSE(checkbox.IsChecked());
EXPECT_EQ("first", select_element.Value().Ascii());
// Validate that clearing works, in particular that the previous values
// were saved correctly.
form_cache.ClearSectionWithElement(text);
EXPECT_EQ("", text.Value().Ascii());
EXPECT_TRUE(checkbox.IsChecked());
EXPECT_EQ("second", select_element.Value().Ascii());
}
// Tests that correct focus, change and blur events are emitted during the
// autofilling and clearing of the form with an initially focused element.
TEST_F(FormCacheBrowserTest,
VerifyFocusAndBlurEventsAfterAutofillAndClearingWithFocusElement) {
// Load a form.
LoadHTML(
"<html><form id='myForm'>"
"<label>First Name:</label><input id='fname' name='0'><br>"
"<label>Last Name:</label> <input id='lname' name='1'><br>"
"</form></html>");
focus_test_utils_->SetUpFocusLogging();
focus_test_utils_->FocusElement("fname");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms,
UnorderedElementsAre(HasId(FormRendererId()), HasName("myForm")));
EXPECT_TRUE(forms.removed_forms.empty());
FormData values_to_fill = forms.updated_forms[0];
values_to_fill.fields[0].value = u"John";
values_to_fill.fields[0].is_autofilled = true;
values_to_fill.fields[1].value = u"Smith";
values_to_fill.fields[1].is_autofilled = true;
auto fname =
GetFormControlElementById(GetMainFrame()->GetDocument(), "fname");
// Simulate filling the form using Autofill.
form_util::FillOrPreviewForm(values_to_fill, fname,
mojom::RendererFormDataAction::kFill);
// Simulate clearing the form.
form_cache.ClearSectionWithElement(fname);
// Expected Result in order:
// - from filling
// * Change fname
// * Blur fname
// * Focus lname
// * Change lname
// * Blur lname
// * Focus fname
// - from clearing
// * Change fname
// * Blur fname
// * Focus lname
// * Change lname
// * Blur lname
// * Focus fname
EXPECT_EQ(GetFocusLog(), "c0b0f1c1b1f0c0b0f1c1b1f0");
}
TEST_F(FormCacheBrowserTest, FreeDataOnElementRemoval) {
LoadHTML(R"(
<div id="container">
<input type="text" name="text" id="text">
<input type="checkbox" checked name="checkbox" id="checkbox">
<select name="select" id="select">
<option value="first">first</option>
<option value="second" selected>second</option>
</select>
</div>
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasId(FormRendererId())));
EXPECT_TRUE(forms.removed_forms.empty());
EXPECT_EQ(1u, FormCacheTestApi(&form_cache).initial_select_values_size());
EXPECT_EQ(1u, FormCacheTestApi(&form_cache).initial_checked_state_size());
ExecuteJavaScriptForTests(R"(
const container = document.getElementById('container');
while (container.childElementCount > 0) {
container.removeChild(container.children.item(0));
}
)");
forms = form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_THAT(forms.removed_forms, ElementsAre(FormRendererId()));
EXPECT_EQ(0u, FormCacheTestApi(&form_cache).initial_select_values_size());
EXPECT_EQ(0u, FormCacheTestApi(&form_cache).initial_checked_state_size());
}
// Test that the select element's user edited field state is set
// to false after clearing the form.
TEST_F(FormCacheBrowserTest, ClearFormSelectElementEditedStateReset) {
LoadHTML(R"(
<input type="text" name="text" id="text">
<select name="date" id="date">
<option value="first">first</option>
<option value="second" selected>second</option>
<option value="third">third</option>
</select>
<select name="month" id="month">
<option value="january">january</option>
<option value="february">february</option>
<option value="march" selected>march</option>
</select>
)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasId(FormRendererId())));
EXPECT_TRUE(forms.removed_forms.empty());
FormData values_to_fill = forms.updated_forms[0];
values_to_fill.fields[0].value = u"test";
values_to_fill.fields[0].is_autofilled = true;
values_to_fill.fields[1].value = u"first";
values_to_fill.fields[1].is_autofilled = true;
values_to_fill.fields[2].value = u"january";
values_to_fill.fields[2].is_autofilled = true;
WebDocument doc = GetMainFrame()->GetDocument();
auto text = GetFormControlElementById(doc, "text");
auto select_date = GetFormControlElementById(doc, "date");
auto select_month = GetFormControlElementById(doc, "month");
form_util::FillOrPreviewForm(values_to_fill, text,
mojom::RendererFormDataAction::kFill);
EXPECT_EQ("test", text.Value().Ascii());
EXPECT_EQ("first", select_date.Value().Ascii());
EXPECT_EQ("january", select_month.Value().Ascii());
// Expect that the 'user has edited field' state is set
EXPECT_TRUE(select_date.UserHasEditedTheField());
EXPECT_TRUE(select_month.UserHasEditedTheField());
// Clear form
form_cache.ClearSectionWithElement(text);
// Expect that the state is now cleared
EXPECT_FALSE(select_date.UserHasEditedTheField());
EXPECT_FALSE(select_month.UserHasEditedTheField());
// Fill the form again, this time the select elements are being filled
// with different values just for additional check.
values_to_fill.fields[1].value = u"third";
values_to_fill.fields[1].is_autofilled = true;
values_to_fill.fields[2].value = u"february";
values_to_fill.fields[2].is_autofilled = true;
form_util::FillOrPreviewForm(values_to_fill, text,
mojom::RendererFormDataAction::kFill);
// Ensure the form is filled correctly, including the select elements.
EXPECT_EQ("test", text.Value().Ascii());
EXPECT_EQ("third", select_date.Value().Ascii());
EXPECT_EQ("february", select_month.Value().Ascii());
// Expect that the state is set again
EXPECT_TRUE(select_date.UserHasEditedTheField());
EXPECT_TRUE(select_month.UserHasEditedTheField());
}
TEST_F(FormCacheBrowserTest, IsFormElementEligibleForManualFilling) {
// Load a form.
LoadHTML(
"<html><form id='myForm'>"
"<label>First Name:</label><input id='fname' name='0'><br>"
"<label>Middle Name:</label> <input id='mname' name='1'><br>"
"<label>Last Name:</label> <input id='lname' name='2'><br>"
"</form></html>");
WebDocument doc = GetMainFrame()->GetDocument();
auto first_name_element = GetFormControlElementById(doc, "fname");
auto middle_name_element = GetFormControlElementById(doc, "mname");
auto last_name_element = GetFormControlElementById(doc, "lname");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_THAT(forms.updated_forms, ElementsAre(HasName("myForm")));
EXPECT_TRUE(forms.removed_forms.empty());
const FormData* form_data = GetFormByName(forms.updated_forms, "myForm");
EXPECT_EQ(3u, form_data->fields.size());
// Set the first_name and last_name fields as eligible for manual filling.
std::vector<FieldRendererId> fields_eligible_for_manual_filling;
fields_eligible_for_manual_filling.push_back(
form_data->fields[0].unique_renderer_id);
fields_eligible_for_manual_filling.push_back(
form_data->fields[2].unique_renderer_id);
form_cache.SetFieldsEligibleForManualFilling(
fields_eligible_for_manual_filling);
EXPECT_TRUE(FormCacheTestApi(&form_cache)
.IsFormElementEligibleForManualFilling(first_name_element));
EXPECT_FALSE(FormCacheTestApi(&form_cache)
.IsFormElementEligibleForManualFilling(middle_name_element));
EXPECT_TRUE(FormCacheTestApi(&form_cache)
.IsFormElementEligibleForManualFilling(last_name_element));
}
// Test that the FormCache does not contain empty forms.
TEST_F(FormCacheBrowserTest, DoNotStoreEmptyForms) {
LoadHTML(R"(<form></form>)");
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_TRUE(forms.updated_forms.empty());
EXPECT_TRUE(forms.removed_forms.empty());
EXPECT_EQ(1u, GetMainFrame()->GetDocument().Forms().size());
EXPECT_EQ(0u, FormCacheTestApi(&form_cache).parsed_forms_size());
}
// Test that the FormCache never contains more than |kMaxParseableFields|
// non-empty parsed forms.
TEST_F(FormCacheBrowserTest, FormCacheSizeUpperBound) {
// Create a HTML page that contains `kMaxParseableFields + 1` non-empty
// forms.
std::string html;
for (unsigned int i = 0; i < kMaxParseableFields + 1; i++) {
html += "<form><input></form>";
}
LoadHTML(html.c_str());
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_EQ(forms.updated_forms.size(), kMaxParseableFields);
EXPECT_TRUE(forms.removed_forms.empty());
EXPECT_EQ(kMaxParseableFields + 1,
GetMainFrame()->GetDocument().Forms().size());
EXPECT_EQ(kMaxParseableFields,
FormCacheTestApi(&form_cache).parsed_forms_size());
}
// Test that FormCache::UpdateFormCache() limits the number of total fields by
// skipping any additional forms.
TEST_F(FormCacheBrowserTest, FieldLimit) {
std::string html;
for (unsigned int i = 0; i < kMaxParseableFields + 1; i++)
html += "<form><input></form>";
LoadHTML(html.c_str());
ASSERT_EQ(kMaxParseableFields + 1,
GetMainFrame()->GetDocument().Forms().size());
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_EQ(kMaxParseableFields, forms.updated_forms.size());
EXPECT_TRUE(forms.removed_forms.empty());
}
// Test that FormCache::UpdateFormCache() limits the number of total frames by
// clearing their frames and skipping the then-empty forms.
TEST_F(FormCacheIframeBrowserTest, FrameLimit) {
std::string html;
for (unsigned int i = 0; i < kMaxParseableChildFrames + 1; i++)
html += "<form><iframe></iframe></form>";
LoadHTML(html.c_str());
ASSERT_EQ(kMaxParseableChildFrames + 1,
GetMainFrame()->GetDocument().Forms().size());
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_EQ(kMaxParseableChildFrames, forms.updated_forms.size());
EXPECT_TRUE(forms.removed_forms.empty());
}
// Test that FormCache::UpdateFormCache() limits the number of total fields and
// total frames:
// - the forms [0, kMaxParseableChildFrames) should be unchanged,
// - the forms [kMaxParseableChildFrames, kMaxParseableFields) should have
// empty FormData::child_frames,
// - the forms [kMaxParseableFields, end) should be skipped.
// TODO(https://crbug.com/1287782): Flaky on android.
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_FieldAndFrameLimit DISABLED_FieldAndFrameLimit
#else
#define MAYBE_FieldAndFrameLimit FieldAndFrameLimit
#endif
TEST_F(FormCacheIframeBrowserTest, MAYBE_FieldAndFrameLimit) {
ASSERT_LE(kMaxParseableChildFrames, kMaxParseableFields);
std::string html;
for (unsigned int i = 0; i < kMaxParseableFields + 1; i++)
html += "<form><input><iframe></iframe></form>";
LoadHTML(html.c_str());
ASSERT_EQ(kMaxParseableFields + 1,
GetMainFrame()->GetDocument().Forms().size());
FormCache form_cache(GetMainFrame());
FormCache::UpdateFormCacheResult forms =
form_cache.UpdateFormCache(/*field_data_manager=*/nullptr);
EXPECT_EQ(forms.updated_forms.size(), kMaxParseableFields);
EXPECT_TRUE(base::ranges::none_of(forms.updated_forms,
&std::vector<FormFieldData>::empty,
&FormData::fields));
EXPECT_TRUE(base::ranges::none_of(
base::make_span(forms.updated_forms).subspan(0, kMaxParseableChildFrames),
&std::vector<FrameTokenWithPredecessor>::empty, &FormData::child_frames));
EXPECT_TRUE(base::ranges::all_of(
base::make_span(forms.updated_forms).subspan(kMaxParseableChildFrames),
&std::vector<FrameTokenWithPredecessor>::empty, &FormData::child_frames));
EXPECT_TRUE(forms.removed_forms.empty());
}
} // namespace autofill