| // Copyright 2023 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/check_deref.h" |
| #include "base/containers/to_vector.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/autofill/autofill_uitest_util.h" |
| #include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/autofill/content/browser/content_autofill_driver.h" |
| #include "components/autofill/content/browser/test_autofill_manager_injector.h" |
| #include "components/autofill/content/common/mojom/autofill_driver.mojom.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/foundations/browser_autofill_manager.h" |
| #include "components/autofill/core/browser/foundations/browser_autofill_manager_test_api.h" |
| #include "components/autofill/core/browser/foundations/mock_autofill_manager_observer.h" |
| #include "components/autofill/core/browser/foundations/test_autofill_manager_waiter.h" |
| #include "components/autofill/core/browser/test_utils/autofill_test_utils.h" |
| #include "components/autofill/core/browser/ui/addresses/autofill_address_util.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "components/autofill/core/common/form_data_test_api.h" |
| #include "components/autofill/core/common/unique_ids.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using testing::_; |
| using testing::Eq; |
| using testing::Not; |
| using testing::ResultOf; |
| |
| namespace autofill { |
| namespace { |
| |
| // Asserts that a filled field sent to devtools has `attribute` set with |
| // `expected_value`. |
| auto FilledFieldHasAttributeWithValue(const std::string& attribute, |
| const std::string& expected_value) { |
| return ResultOf( |
| [&](const base::Value& filled_field) { |
| const std::string* value = |
| filled_field.GetDict().FindStringByDottedPath(attribute); |
| return value ? *value : ""; |
| }, |
| Eq(expected_value)); |
| } |
| |
| // Returns the all the `autofill::FieldType`s used to build an UI representation |
| // of an address. These values are the same as the ones used in the settings |
| // page and edit dialog, furthermore, they depend on a profile's `country_code`. |
| std::set<autofill::FieldType> |
| GetExpectedFieldTypesToBuildAddressUiForCountryCode( |
| const std::string& country_code) { |
| std::set<autofill::FieldType> expected_address_ui_field_types; |
| |
| // These types are not part of `AutofillAddressUIComponent`. |
| expected_address_ui_field_types.insert( |
| autofill::FieldType::ADDRESS_HOME_COUNTRY); |
| expected_address_ui_field_types.insert( |
| autofill::FieldType::PHONE_HOME_WHOLE_NUMBER); |
| expected_address_ui_field_types.insert(autofill::FieldType::EMAIL_ADDRESS); |
| |
| std::vector<std::vector<autofill::AutofillAddressUIComponent>> components; |
| autofill::GetAddressComponents(country_code, "en-US", |
| /*include_literals=*/false, &components, |
| nullptr); |
| for (const std::vector<autofill::AutofillAddressUIComponent>& line : |
| components) { |
| for (const autofill::AutofillAddressUIComponent& component : line) { |
| expected_address_ui_field_types.insert(component.field); |
| } |
| } |
| |
| return expected_address_ui_field_types; |
| } |
| |
| auto FilledFieldHasAttributeWithValue16(const std::string& attribute, |
| const std::u16string& expected_value) { |
| return FilledFieldHasAttributeWithValue(attribute, |
| base::UTF16ToUTF8(expected_value)); |
| } |
| |
| std::string GetProfileInfoFromAddressField(const AutofillProfile profile, |
| const base::Value& address_field) { |
| return base::UTF16ToUTF8(profile.GetInfo( |
| TypeNameToFieldType( |
| *address_field.GetDict().FindStringByDottedPath("name")), |
| "en-US")); |
| } |
| |
| // Adds waiting capabilities to BrowserAutofillManager. |
| class TestAutofillManager : public autofill::BrowserAutofillManager { |
| public: |
| explicit TestAutofillManager(autofill::ContentAutofillDriver* driver) |
| : BrowserAutofillManager(driver) {} |
| |
| static TestAutofillManager& GetForRenderFrameHost( |
| content::RenderFrameHost* rfh) { |
| return static_cast<TestAutofillManager&>( |
| autofill::ContentAutofillDriver::GetForRenderFrameHost(rfh) |
| ->GetAutofillManager()); |
| } |
| |
| [[nodiscard]] testing::AssertionResult WaitForFormsSeen( |
| size_t num_awaited_calls) { |
| return forms_seen_.Wait(num_awaited_calls); |
| } |
| |
| const FormStructure* WaitForFormWithNFields(int n) { |
| return WaitForMatchingForm(this, base::BindRepeating( |
| [](int n, const FormStructure& form) { |
| return form.fields().size() == |
| static_cast<size_t>(n); |
| }, |
| n)); |
| } |
| |
| private: |
| autofill::TestAutofillManagerWaiter forms_seen_{ |
| *this, |
| {autofill::AutofillManagerEvent::kFormsSeen}}; |
| }; |
| |
| class DevToolsAutofillTest : public DevToolsProtocolTestBase { |
| public: |
| DevToolsAutofillTest() = default; |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| |
| content::RenderFrameHost* main_frame() { |
| return web_contents()->GetPrimaryMainFrame(); |
| } |
| |
| TestAutofillManager& main_autofill_manager() { |
| return TestAutofillManager::GetForRenderFrameHost(main_frame()); |
| } |
| |
| std::string EvaluateAndGetValue(const std::string& expression, |
| const std::string& unique_context_id, |
| const std::string& session_id) { |
| base::Value::Dict params; |
| params.Set("expression", expression); |
| if (!unique_context_id.empty()) { |
| params.Set("uniqueContextId", unique_context_id); |
| } |
| const base::Value::Dict* result = SendSessionCommand( |
| "Runtime.evaluate", std::move(params), session_id, /*wait=*/true); |
| return *result->FindStringByDottedPath("result.value"); |
| } |
| |
| int GetBackendNodeIdByIdAttribute(const std::string& id_attribute) { |
| return GetBackendNodeIdByIdAttribute(id_attribute, "", ""); |
| } |
| |
| int GetBackendNodeIdByIdAttribute(const std::string& id_attribute, |
| const std::string& unique_context_id) { |
| return GetBackendNodeIdByIdAttribute(id_attribute, unique_context_id, ""); |
| } |
| |
| int GetBackendNodeIdByIdAttribute(const std::string& id_attribute, |
| const std::string& unique_context_id, |
| const std::string& session_id) { |
| std::string object_id; |
| { |
| base::Value::Dict params; |
| params.Set("expression", base::StrCat({"document.getElementById('", |
| id_attribute, "')"})); |
| if (!unique_context_id.empty()) { |
| params.Set("uniqueContextId", unique_context_id); |
| } |
| const base::Value::Dict* result = SendSessionCommand( |
| "Runtime.evaluate", std::move(params), session_id, /*wait=*/true); |
| object_id = *result->FindStringByDottedPath("result.objectId"); |
| } |
| |
| base::Value::Dict params; |
| params.Set("objectId", object_id); |
| const base::Value::Dict* result = SendSessionCommand( |
| "DOM.describeNode", std::move(params), session_id, /*wait=*/true); |
| return *result->FindIntByDottedPath("node.backendNodeId"); |
| } |
| |
| std::string GetOOPIFTargetId() { |
| base::Value::Dict params; |
| params.Set("discover", true); |
| SendCommandSync("Target.setDiscoverTargets", std::move(params)); |
| std::string frame_target_id; |
| while (true) { |
| base::Value::Dict result; |
| result = WaitForNotification("Target.targetCreated", true); |
| if (*result.FindStringByDottedPath("targetInfo.type") == "iframe") { |
| frame_target_id = |
| std::string(*result.FindStringByDottedPath("targetInfo.targetId")); |
| break; |
| } |
| } |
| return frame_target_id; |
| } |
| |
| std::string AttachToTarget(const std::string& target_id) { |
| base::Value::Dict params; |
| params.Set("targetId", target_id); |
| params.Set("flatten", true); |
| SendCommandSync("Target.attachToTarget", std::move(params)); |
| return CHECK_DEREF(CHECK_DEREF(result()).FindString("sessionId")); |
| } |
| |
| base::Value::Dict GetTestCreditCard() { |
| base::Value::Dict card; |
| card.Set("number", "4444444444444444"); |
| card.Set("name", "John Smith"); |
| card.Set("expiryMonth", "01"); |
| card.Set("expiryYear", "2030"); |
| card.Set("cvc", "123"); |
| return card; |
| } |
| |
| AutofillProfile CreateTestProfile() { |
| AutofillProfile profile = test::GetFullProfile(); |
| AddTestProfile(browser()->profile(), profile); |
| return profile; |
| } |
| |
| FormGlobalId form_id() { |
| return {LocalFrameToken(*main_frame()->GetFrameToken()), |
| FormRendererId(123)}; |
| } |
| |
| base::Value::Dict GetFilledOutForm(const std::string& unique_context_id) { |
| return GetFilledOutForm(unique_context_id, ""); |
| } |
| |
| base::Value::Dict GetFilledOutForm(const std::string& unique_context_id, |
| const std::string& session_id) { |
| base::Value::Dict card; |
| card.Set("number", |
| EvaluateAndGetValue( |
| "document.getElementById('CREDIT_CARD_NUMBER').value", |
| unique_context_id, session_id)); |
| card.Set("name", |
| EvaluateAndGetValue( |
| "document.getElementById('CREDIT_CARD_NAME_FULL').value", |
| unique_context_id, session_id)); |
| card.Set("expiryMonth", |
| EvaluateAndGetValue( |
| "document.getElementById('CREDIT_CARD_EXP_MONTH').value", |
| unique_context_id, session_id)); |
| card.Set( |
| "expiryYear", |
| EvaluateAndGetValue( |
| "document.getElementById('CREDIT_CARD_EXP_4_DIGIT_YEAR').value", |
| unique_context_id, session_id)); |
| // CVC is not filled out in the form. |
| card.Set("cvc", "123"); |
| return card; |
| } |
| |
| private: |
| test::AutofillBrowserTestEnvironment autofill_test_environment_; |
| autofill::TestAutofillManagerInjector<TestAutofillManager> |
| autofill_manager_injector_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, SetAddresses) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url( |
| embedded_test_server()->GetURL("/autofill_creditcard_form.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| base::Value::Dict address_1_fields; |
| address_1_fields.Set("name", "ADDRESS_HOME_LINE1"); |
| address_1_fields.Set("value", "Erika-mann"); |
| base::Value::Dict address_1; |
| base::Value::List fields; |
| fields.Append(std::move(address_1_fields)); |
| address_1.Set("fields", std::move(fields)); |
| |
| base::Value::Dict address_2_fields; |
| address_2_fields.Set("name", "ADDRESS_HOME_LINE2"); |
| address_2_fields.Set("value", "Faria lima"); |
| base::Value::Dict address_2; |
| base::Value::List fields_2; |
| fields_2.Append(std::move(address_2_fields)); |
| address_2.Set("fields", std::move(fields_2)); |
| |
| base::Value::List test_addresses; |
| test_addresses.Append(std::move(address_1)); |
| test_addresses.Append(std::move(address_2)); |
| |
| base::Value::Dict params; |
| params.Set("addresses", std::move(test_addresses)); |
| |
| SendCommandSync("Autofill.setAddresses", std::move(params)); |
| |
| base::span<const autofill::AutofillProfile> res = |
| main_autofill_manager().client().GetTestAddresses(); |
| ASSERT_EQ(res.size(), 2u); |
| ASSERT_EQ( |
| res[0].GetAddress().GetRawInfo(autofill::FieldType::ADDRESS_HOME_LINE1), |
| u"Erika-mann"); |
| ASSERT_EQ( |
| res[1].GetAddress().GetRawInfo(autofill::FieldType::ADDRESS_HOME_LINE2), |
| u"Faria lima"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerCreditCardInIframe) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url(embedded_test_server()->GetURL( |
| "/autofill_creditcard_form_in_iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| std::string frame_id; |
| { |
| const base::Value::Dict* result = SendCommandSync("Page.getFrameTree"); |
| const base::Value::List* frames = |
| result->FindListByDottedPath("frameTree.childFrames"); |
| const base::Value::Dict* frame_dict = frames->front().GetIfDict(); |
| frame_id = *frame_dict->FindStringByDottedPath("frame.id"); |
| } |
| |
| std::string unique_context_id; |
| { |
| base::Value::Dict command_params; |
| SendCommandSync("Runtime.enable"); |
| base::Value::Dict params; |
| for (int context_count = 1; true; context_count++) { |
| params = WaitForNotification("Runtime.executionContextCreated", true); |
| if (*params.FindStringByDottedPath("context.auxData.frameId") == |
| frame_id) { |
| unique_context_id = *params.FindStringByDottedPath("context.uniqueId"); |
| break; |
| } |
| ASSERT_LT(context_count, 2); |
| } |
| } |
| |
| int backend_node_id = |
| GetBackendNodeIdByIdAttribute("CREDIT_CARD_NUMBER", unique_context_id); |
| |
| { |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("card", GetTestCreditCard()); |
| params.Set("frameId", "wrong"); |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| EXPECT_EQ(*error()->FindString("message"), "Frame not found"); |
| } |
| |
| { |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("card", GetTestCreditCard()); |
| params.Set("frameId", frame_id); |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| ASSERT_TRUE(result()); |
| EXPECT_EQ(*result(), base::Value::Dict()); |
| } |
| |
| EXPECT_EQ(GetFilledOutForm(unique_context_id), GetTestCreditCard()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerCreditCardInOOPIFIframe) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url = embedded_test_server()->GetURL( |
| "a.com", "/autofill_creditcard_form_in_oopif.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormWithNFields(4)); |
| |
| std::string frame_target_id = GetOOPIFTargetId(); |
| std::string session_id = AttachToTarget(frame_target_id); |
| |
| int backend_node_id = |
| GetBackendNodeIdByIdAttribute("CREDIT_CARD_NUMBER", "", session_id); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("frameId", frame_target_id); |
| params.Set("card", GetTestCreditCard()); |
| SendSessionCommand("Autofill.trigger", std::move(params), session_id, |
| /*wait=*/true); |
| ASSERT_TRUE(result()); |
| EXPECT_EQ(*result(), base::Value::Dict()); |
| EXPECT_EQ(GetFilledOutForm("", session_id), GetTestCreditCard()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerCreditCardAcrossOOPIFs) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url = embedded_test_server()->GetURL( |
| "a.com", "/autofill_creditcard_form_in_oopif.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormWithNFields(4)); |
| |
| std::string frame_target_id = GetOOPIFTargetId(); |
| std::string session_id = AttachToTarget(frame_target_id); |
| |
| int backend_node_id = |
| GetBackendNodeIdByIdAttribute("CREDIT_CARD_NUMBER", "", session_id); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("frameId", frame_target_id); |
| params.Set("card", GetTestCreditCard()); |
| SendSessionCommand("Autofill.trigger", std::move(params), session_id, |
| /*wait=*/true); |
| ASSERT_TRUE(result()); |
| EXPECT_EQ(*result(), base::Value::Dict()); |
| EXPECT_EQ(GetFilledOutForm("", session_id), GetTestCreditCard()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, AddressFormFilled) { |
| Attach(); |
| // Create a profile to read information from and send to devtools. |
| AutofillProfile profile = CreateTestProfile(); |
| |
| // TODO(crbug.com/40227496): Get rid of FormFieldData. |
| FormData form; |
| form.set_host_frame(form_id().frame_token); |
| form.set_renderer_id(form_id().renderer_id); |
| std::vector<FormFieldData> fields; |
| fields.push_back(test::CreateTestFormField( |
| /*label=*/"", "name_1", /*value=*/"", FormControlType::kInputText)); |
| fields.back().set_id_attribute(u"id_1"); |
| fields.back().set_host_frame(form.host_frame()); |
| fields.push_back(test::CreateTestFormField( |
| /*label=*/"", "name_2", /*value=*/"", FormControlType::kInputText)); |
| fields.back().set_id_attribute(u"id_2"); |
| fields.back().set_host_frame(form.host_frame()); |
| form.set_fields(std::move(fields)); |
| |
| // The parsed form is queried by |
| // AutofillHandler::OnFillOrPreviewForm() to obtain the type |
| // predictions. |
| auto form_structure = std::make_unique<FormStructure>(form); |
| form_structure->field(0)->set_server_predictions( |
| {test::CreateFieldPrediction(NAME_FULL)}); |
| form_structure->field(0)->SetHtmlType(HtmlFieldType::kName, |
| HtmlFieldMode::kShipping); |
| form_structure->field(1)->set_server_predictions( |
| {test::CreateFieldPrediction(NAME_FULL)}); |
| form_structure->field(1)->SetHtmlType(HtmlFieldType::kUnspecified, |
| HtmlFieldMode::kShipping); |
| |
| const std::vector<FormFieldData> filled_fields_by_autofill = { |
| {form.fields()[0], form.fields()[1]}}; |
| |
| test_api(main_autofill_manager()) |
| .AddSeenFormStructure(std::move(form_structure)); |
| |
| // Enable events and emit event about form being filled. |
| SendCommandSync("Autofill.enable"); |
| main_autofill_manager().NotifyObservers( |
| &autofill::AutofillManager::Observer::OnFillOrPreviewForm, form_id(), |
| autofill::mojom::ActionPersistence::kFill, |
| base::MakeFlatSet<FieldGlobalId>(filled_fields_by_autofill, {}, |
| &FormFieldData::global_id), |
| &profile); |
| |
| base::Value::Dict notification = WaitForNotification( |
| "Autofill.addressFormFilled", /*allow_existing=*/true); |
| |
| std::set<autofill::FieldType> field_types_added_to_address_ui; |
| for (const base::Value& address_line : |
| *notification.FindListByDottedPath("addressUi.addressFields")) { |
| for (const base::Value& address_field : |
| *address_line.GetDict().FindListByDottedPath("fields")) { |
| // Test that the profile address values sent to devtools match what we |
| // have in `profile`. |
| EXPECT_EQ(GetProfileInfoFromAddressField(profile, address_field), |
| *address_field.GetDict().FindStringByDottedPath("value")); |
| field_types_added_to_address_ui.insert(TypeNameToFieldType( |
| *address_field.GetDict().FindStringByDottedPath("name"))); |
| } |
| } |
| |
| // Assert the expected values used to build the address ui were sent to |
| // devtools. |
| ASSERT_EQ(field_types_added_to_address_ui, |
| GetExpectedFieldTypesToBuildAddressUiForCountryCode( |
| base::UTF16ToUTF8(profile.GetInfo( |
| autofill::FieldType::ADDRESS_HOME_COUNTRY, "en-US")))); |
| // Assert that the filled fields sent to devtools match exactly the ones |
| // filled by autofill. |
| const base::Value::List* filled_fields = |
| notification.FindListByDottedPath("filledFields"); |
| ASSERT_EQ(filled_fields->size(), filled_fields_by_autofill.size()); |
| for (size_t i = 0; i < filled_fields->size(); ++i) { |
| const FormStructure& fs = |
| CHECK_DEREF(main_autofill_manager().FindCachedFormById(form_id())); |
| const base::Value& ff = (*filled_fields)[i]; |
| const FormFieldData& ffd = filled_fields_by_autofill[i]; |
| const AutofillField* af = fs.GetFieldById(ffd.global_id()); |
| |
| std::vector<std::string_view> field_type_strings = |
| base::ToVector(af->Type().GetTypes(), |
| &autofill::FieldTypeToDeveloperRepresentationString); |
| std::erase(field_type_strings, ""); |
| |
| EXPECT_THAT(ff, |
| FilledFieldHasAttributeWithValue16("id", af->id_attribute())); |
| EXPECT_THAT( |
| ff, FilledFieldHasAttributeWithValue( |
| "autofillType", base::JoinString(field_type_strings, ", "))); |
| EXPECT_THAT(ff, FilledFieldHasAttributeWithValue16( |
| "value", profile.GetInfo(af->Type(), "en-us"))); |
| EXPECT_THAT(ff, FilledFieldHasAttributeWithValue16( |
| "frameId", |
| base::UTF8ToUTF16( |
| main_frame()->GetDevToolsFrameToken().ToString()))); |
| EXPECT_THAT(ff, |
| FilledFieldHasAttributeWithValue( |
| "htmlType", std::string(autofill::FormControlTypeToString( |
| af->form_control_type())))); |
| EXPECT_THAT( |
| ff, FilledFieldHasAttributeWithValue16("name", af->name_attribute())); |
| EXPECT_EQ(*ff.GetDict().FindIntByDottedPath("fieldId"), |
| (int)(ffd.renderer_id().value())); |
| } |
| |
| // The first filled field uses autocomplete attribute as filling strategy. |
| EXPECT_EQ(*filled_fields->front().GetDict().FindStringByDottedPath( |
| "fillingStrategy"), |
| "autocompleteAttribute"); |
| // The second one used autofill internals, either local heuristics or server |
| // predictions. |
| EXPECT_EQ(*filled_fields->back().GetDict().FindStringByDottedPath( |
| "fillingStrategy"), |
| "autofillInferred"); |
| } |
| |
| // This test guards the assumption that autofill events in iframes are routed to |
| // the main frame on the browser side. It's implicitly assumed in tests that |
| // make direct `AutofillManager::Observer` calls on `main_autofill_manager()` |
| // while testing iframes. |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, AutofillInOOPIFs) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url = embedded_test_server()->GetURL( |
| "a.com", "/autofill_address_multi_form_in_oopif.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormWithNFields(10)); |
| ASSERT_EQ(main_autofill_manager().form_structures().size(), 1u); |
| |
| FormData form = |
| main_autofill_manager().form_structures().begin()->second->ToFormData(); |
| |
| autofill::MockAutofillManagerObserver observer; |
| main_autofill_manager().AddObserver(&observer); |
| |
| // Expect the `AskForValuesToFill()` call below to be routed to the main |
| // frame `AutofillManager`. |
| EXPECT_CALL(observer, |
| OnBeforeAskForValuesToFill(_, form.global_id(), |
| form.fields()[0].global_id(), _)); |
| |
| const std::vector<const FormFieldData*> filled_fields_by_autofill = { |
| {&form.fields()[0], &form.fields()[1]}}; |
| web_contents()->ForEachRenderFrameHost([&](content::RenderFrameHost* rfh) { |
| // Call the driver of the field host iframe. |
| if (rfh->GetFrameToken().ToString() == |
| form.fields()[0].host_frame()->ToString()) { |
| ASSERT_NE(rfh->GetFrameToken(), main_frame()->GetFrameToken()); |
| auto* driver = static_cast<mojom::AutofillDriver*>( |
| autofill::ContentAutofillDriver::GetForRenderFrameHost(rfh)); |
| driver->AskForValuesToFill( |
| form, form.fields()[0].renderer_id(), gfx::Rect(0, 10), |
| ::autofill::mojom::AutofillSuggestionTriggerSource::kUnspecified, |
| std::nullopt); |
| } |
| }); |
| |
| main_autofill_manager().RemoveObserver(&observer); |
| } |
| |
| // Tests that only the handler associated with the root frame (page) handles |
| // AutofillManager events (for cross-iframe filling, the filling events from all |
| // frames are routed to the root AutofillManager and others don't emit them). |
| // It also tests that only relevant elements (those from forms which have at |
| // least one field autofilled, others are ignored) are sent to the frontend. |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, AddressFormFilledInOOPIFs) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url = embedded_test_server()->GetURL( |
| "a.com", "/autofill_address_multi_form_in_oopif.html"); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormWithNFields(10)); |
| ASSERT_EQ(main_autofill_manager().form_structures().size(), 1u); |
| |
| std::string frame_target_id = GetOOPIFTargetId(); |
| std::string session_id = AttachToTarget(frame_target_id); |
| |
| SendCommandSync("Autofill.enable"); |
| SendSessionCommand("Autofill.enable", base::Value::Dict(), session_id, true); |
| |
| AutofillProfile profile = CreateTestProfile(); |
| FormData form = |
| main_autofill_manager().form_structures().begin()->second->ToFormData(); |
| base::flat_set<FieldGlobalId> filled_fields_by_autofill{ |
| {form.fields()[0].global_id(), form.fields()[1].global_id()}}; |
| main_autofill_manager().NotifyObservers( |
| &autofill::AutofillManager::Observer::OnFillOrPreviewForm, |
| form.global_id(), autofill::mojom::ActionPersistence::kFill, |
| filled_fields_by_autofill, &profile); |
| |
| base::Value::Dict notification = WaitForNotification( |
| "Autofill.addressFormFilled", /*allow_existing=*/true); |
| EXPECT_EQ(notification.FindListByDottedPath("filledFields")->size(), 6u); |
| EXPECT_FALSE(HasExistingNotification("Autofill.addressFormFilled")) |
| << "The other handler should not handle `OnFillOrPreviewForm`"; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, |
| AutofillManagerEventsAfterNavigation) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("a.com", "/autofill_test_form.html"))); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| EXPECT_TRUE(main_autofill_manager().WaitForFormWithNFields(9)); |
| |
| Attach(); |
| SendCommandSync("Autofill.enable"); |
| |
| AutofillProfile profile_a = CreateTestProfile(); |
| FormData form_a = |
| main_autofill_manager().form_structures().begin()->second->ToFormData(); |
| const base::flat_set<FieldGlobalId> filled_fields_by_autofill_a = { |
| {form_a.fields()[0].global_id(), form_a.fields()[1].global_id()}}; |
| main_autofill_manager().NotifyObservers( |
| &autofill::AutofillManager::Observer::OnFillOrPreviewForm, |
| form_a.global_id(), autofill::mojom::ActionPersistence::kFill, |
| filled_fields_by_autofill_a, &profile_a); |
| |
| WaitForNotification("Autofill.addressFormFilled", /*allow_existing=*/true); |
| |
| // Navigating from "a.com" to "b.com". |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("b.com", "/autofill_test_form.html"))); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| EXPECT_TRUE(main_autofill_manager().WaitForFormWithNFields(9)); |
| |
| AutofillProfile profile_b = CreateTestProfile(); |
| FormData form_b = |
| main_autofill_manager().form_structures().begin()->second->ToFormData(); |
| const base::flat_set<FieldGlobalId> filled_fields_by_autofill_b = { |
| {form_b.fields()[0].global_id(), form_b.fields()[1].global_id()}}; |
| main_autofill_manager().NotifyObservers( |
| &autofill::AutofillManager::Observer::OnFillOrPreviewForm, |
| form_b.global_id(), autofill::mojom::ActionPersistence::kFill, |
| filled_fields_by_autofill_b, &profile_b); |
| WaitForNotification("Autofill.addressFormFilled", /*allow_existing=*/true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerAddressAutofill) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const GURL url = |
| embedded_test_server()->GetURL("/autofill_address_enabled.html"); |
| ASSERT_TRUE(content::NavigateToURL(web_contents(), url)); |
| |
| Attach(); |
| SendCommandSync("Autofill.enable"); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| int backend_node_id = GetBackendNodeIdByIdAttribute("street-address"); |
| |
| auto address_fields = base::Value::List() |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_LINE1") |
| .Set("value", "123 Main Street")) |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_CITY") |
| .Set("value", "New York")) |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_STATE") |
| .Set("value", "NY")) |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_ZIP") |
| .Set("value", "10001")) |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_COUNTRY") |
| .Set("value", "US")); |
| |
| auto address = base::Value::Dict().Set("fields", std::move(address_fields)); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("address", std::move(address)); |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| if (error()) { |
| FAIL() << "Autofill trigger failed with error: " |
| << *error()->FindString("message"); |
| } else { |
| // Verify the field was filled |
| content::EvalJsResult street_address = content::EvalJs( |
| web_contents(), "document.getElementById('street-address').value"); |
| EXPECT_EQ("123 Main Street", street_address.ExtractString()); |
| content::EvalJsResult city = content::EvalJs( |
| web_contents(), "document.getElementById('city').value"); |
| EXPECT_EQ("New York", city.ExtractString()); |
| content::EvalJsResult zip = content::EvalJs( |
| web_contents(), "document.getElementById('postal-code').value"); |
| EXPECT_EQ("10001", zip.ExtractString()); |
| content::EvalJsResult state = content::EvalJs( |
| web_contents(), "document.getElementById('state').value"); |
| EXPECT_EQ("NY", state.ExtractString()); |
| content::EvalJsResult country = content::EvalJs( |
| web_contents(), "document.getElementById('country').value"); |
| EXPECT_EQ("US", country.ExtractString()); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerWithInvalidFieldId) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url( |
| embedded_test_server()->GetURL("/autofill_creditcard_form.html")); |
| ASSERT_TRUE(content::NavigateToURL(web_contents(), url)); |
| |
| Attach(); |
| SendCommandSync("Autofill.enable"); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", 99999); // Invalid field ID |
| params.Set("card", GetTestCreditCard()); |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| EXPECT_TRUE(error()); |
| EXPECT_EQ(*error()->FindString("message"), "Field not found"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, |
| TriggerAddressWithUnsupportedFieldType) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const GURL url = |
| embedded_test_server()->GetURL("/autofill_address_enabled.html"); |
| ASSERT_TRUE(content::NavigateToURL(web_contents(), url)); |
| |
| Attach(); |
| SendCommandSync("Autofill.enable"); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| int backend_node_id = GetBackendNodeIdByIdAttribute("street-address"); |
| |
| // Use an invalid/unsupported field type |
| auto address_fields = |
| base::Value::List().Append(base::Value::Dict() |
| .Set("name", "INVALID_FIELD_TYPE") |
| .Set("value", "123 Main Street")); |
| |
| auto address = base::Value::Dict().Set("fields", std::move(address_fields)); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("address", std::move(address)); |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| EXPECT_TRUE(error()); |
| EXPECT_EQ(*error()->FindString("message"), |
| "Unsupported field type: INVALID_FIELD_TYPE"); |
| |
| address_fields = |
| base::Value::List().Append(base::Value::Dict() |
| .Set("name", "LOYALTY_MEMBERSHIP_ID") |
| .Set("value", "1234567890")); |
| |
| address = base::Value::Dict().Set("fields", std::move(address_fields)); |
| |
| params.Set("fieldId", backend_node_id); |
| params.Set("address", std::move(address)); |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| EXPECT_TRUE(error()); |
| EXPECT_EQ(*error()->FindString("message"), |
| "Unsupported field type: LOYALTY_MEMBERSHIP_ID"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerAddressAutofillInIframe) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url(embedded_test_server()->GetURL( |
| "/autofill_address_multi_form_in_oopif.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| std::string frame_id; |
| { |
| const base::Value::Dict* result = SendCommandSync("Page.getFrameTree"); |
| const base::Value::List* frames = |
| result->FindListByDottedPath("frameTree.childFrames"); |
| const base::Value::Dict* frame_dict = frames->front().GetIfDict(); |
| frame_id = *frame_dict->FindStringByDottedPath("frame.id"); |
| } |
| |
| std::string unique_context_id; |
| { |
| base::Value::Dict command_params; |
| SendCommandSync("Runtime.enable"); |
| base::Value::Dict params; |
| for (int context_count = 1; true; context_count++) { |
| params = WaitForNotification("Runtime.executionContextCreated", true); |
| if (*params.FindStringByDottedPath("context.auxData.frameId") == |
| frame_id) { |
| unique_context_id = *params.FindStringByDottedPath("context.uniqueId"); |
| break; |
| } |
| ASSERT_LT(context_count, 2); |
| } |
| } |
| |
| int backend_node_id = |
| GetBackendNodeIdByIdAttribute("street-address", unique_context_id); |
| |
| // Use valid address field types |
| auto address_fields = base::Value::List() |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_LINE1") |
| .Set("value", "123 Main Street")) |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_CITY") |
| .Set("value", "New York")); |
| |
| auto address = base::Value::Dict().Set("fields", std::move(address_fields)); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("address", std::move(address)); |
| params.Set("frameId", frame_id); |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| if (error()) { |
| FAIL() << "Autofill trigger failed with error: " |
| << *error()->FindString("message"); |
| } else { |
| // Verify the fields were filled in the iframe |
| content::EvalJsResult street_address = |
| content::EvalJs(web_contents(), |
| "document.querySelector('iframe').contentDocument." |
| "getElementById('street-address').value"); |
| EXPECT_EQ("123 Main Street", street_address.ExtractString()); |
| |
| content::EvalJsResult city = |
| content::EvalJs(web_contents(), |
| "document.querySelector('iframe').contentDocument." |
| "getElementById('city').value"); |
| EXPECT_EQ("New York", city.ExtractString()); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerWithBothCardAndAddress) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url( |
| embedded_test_server()->GetURL("/autofill_creditcard_form.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| int backend_node_id = GetBackendNodeIdByIdAttribute("CREDIT_CARD_NUMBER"); |
| |
| auto address_fields = base::Value::List() |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_LINE1") |
| .Set("value", "123 Main Street")) |
| .Append(base::Value::Dict() |
| .Set("name", "ADDRESS_HOME_COUNTRY") |
| .Set("value", "US")); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| params.Set("card", GetTestCreditCard()); |
| params.Set("address", |
| base::Value::Dict().Set("fields", std::move(address_fields))); |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| EXPECT_TRUE(error()); |
| EXPECT_EQ(*error()->FindString("message"), |
| "Card and address cannot both be provided"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(DevToolsAutofillTest, TriggerWithNeitherCardNorAddress) { |
| embedded_test_server()->ServeFilesFromSourceDirectory( |
| "chrome/test/data/autofill"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url( |
| embedded_test_server()->GetURL("/autofill_creditcard_form.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| ASSERT_TRUE(content::WaitForLoadStop(web_contents())); |
| Attach(); |
| |
| EXPECT_TRUE(main_autofill_manager().WaitForFormsSeen(1)); |
| |
| int backend_node_id = GetBackendNodeIdByIdAttribute("CREDIT_CARD_NUMBER"); |
| |
| base::Value::Dict params; |
| params.Set("fieldId", backend_node_id); |
| // Neither card nor address provided |
| |
| SendCommandSync("Autofill.trigger", std::move(params)); |
| |
| EXPECT_TRUE(error()); |
| EXPECT_EQ(*error()->FindString("message"), |
| "Either card or address must be provided"); |
| } |
| |
| } // namespace |
| } // namespace autofill |