| // 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 "chrome/browser/devtools/protocol/autofill_handler.h" |
| |
| #include <optional> |
| #include <variant> |
| |
| #include "base/check_deref.h" |
| #include "base/containers/to_vector.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/devtools/protocol/autofill.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h" |
| #include "chrome/browser/ui/autofill/chrome_autofill_client.h" |
| #include "components/autofill/content/browser/content_autofill_client.h" |
| #include "components/autofill/content/browser/content_autofill_driver.h" |
| #include "components/autofill/content/browser/content_autofill_driver_factory.h" |
| #include "components/autofill/content/browser/scoped_autofill_managers_observation.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h" |
| #include "components/autofill/core/browser/data_model/payments/credit_card.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/filling/addresses/field_filling_address_util.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/foundations/autofill_manager.h" |
| #include "components/autofill/core/browser/foundations/browser_autofill_manager.h" |
| #include "components/autofill/core/browser/manual_testing_import.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_field_data.h" |
| #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" |
| #include "components/autofill/core/common/unique_ids.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| namespace { |
| |
| using protocol::Response; |
| |
| std::optional<autofill::FormData> FindFormDataWithField( |
| autofill::ContentAutofillDriver* driver, |
| autofill::FieldGlobalId id) { |
| if (!driver) { |
| return std::nullopt; |
| } |
| for (const auto& [key, form] : |
| driver->GetAutofillManager().form_structures()) { |
| for (const auto& field : form->fields()) { |
| if (field->global_id() == id) { |
| return form->ToFormData(); |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> GetRenderFrameDevtoolsToken( |
| const std::string& target_id, |
| const std::string& frame_token) { |
| auto host = content::DevToolsAgentHost::GetForId(target_id); |
| CHECK(host); |
| |
| std::string result; |
| host->GetWebContents()->GetOutermostWebContents()->ForEachRenderFrameHost( |
| [&result, &frame_token](content::RenderFrameHost* rfh) { |
| if (rfh->GetFrameToken().ToString() == frame_token) { |
| result = rfh->GetDevToolsFrameToken().ToString(); |
| } |
| }); |
| return result; |
| } |
| |
| } // namespace |
| |
| AutofillHandler::AutofillHandler(protocol::UberDispatcher* dispatcher, |
| const std::string& target_id) |
| : target_id_(target_id) { |
| protocol::Autofill::Dispatcher::wire(dispatcher, this); |
| frontend_ = |
| std::make_unique<protocol::Autofill::Frontend>(dispatcher->channel()); |
| } |
| |
| AutofillHandler::~AutofillHandler() { |
| Disable(); |
| } |
| |
| protocol::Response AutofillHandler::Trigger( |
| int field_id, |
| std::optional<String> frame_id, |
| std::unique_ptr<protocol::Autofill::CreditCard> card) { |
| auto host = content::DevToolsAgentHost::GetForId(target_id_); |
| if (!host) { |
| return Response::ServerError("Target not found"); |
| } |
| |
| content::RenderFrameHost* outermost_primary_rfh = |
| host->GetWebContents()->GetOutermostWebContents()->GetPrimaryMainFrame(); |
| content::RenderFrameHost* frame_rfh = nullptr; |
| |
| if (frame_id.has_value()) { |
| outermost_primary_rfh->ForEachRenderFrameHost( |
| [&frame_id, &frame_rfh](content::RenderFrameHost* rfh) { |
| if (rfh->GetDevToolsFrameToken().ToString() == frame_id.value()) { |
| frame_rfh = rfh; |
| } |
| }); |
| if (!frame_rfh) { |
| return Response::ServerError("Frame not found"); |
| } |
| } else { |
| frame_rfh = outermost_primary_rfh; |
| } |
| |
| autofill::LocalFrameToken frame_token(frame_rfh->GetFrameToken().value()); |
| autofill::FieldGlobalId global_field_id = { |
| frame_token, autofill::FieldRendererId(field_id)}; |
| |
| autofill::ContentAutofillDriver* autofill_driver = nullptr; |
| std::optional<autofill::FormData> form; |
| while (frame_rfh) { |
| autofill_driver = |
| autofill::ContentAutofillDriver::GetForRenderFrameHost(frame_rfh); |
| |
| // TODO(alexrudenko): This approach might lead to differences in behaviour |
| // between the real Autofill flow triggered manually and Autofill triggered |
| // over CDP. We should change how we find the form data and use the same |
| // logic as used by AutofillDriverRouter. |
| if (std::optional<autofill::FormData> rfh_form_data = |
| FindFormDataWithField(autofill_driver, global_field_id)) { |
| form = std::move(rfh_form_data); |
| } |
| |
| frame_rfh = frame_rfh->GetParent(); |
| } |
| |
| if (!form.has_value()) { |
| return Response::InvalidRequest("Field not found"); |
| } |
| |
| if (!autofill_driver) { |
| return Response::ServerError("RenderFrameHost is being destroyed"); |
| } |
| |
| autofill::CreditCard tmp_autofill_card; |
| tmp_autofill_card.SetRawInfo(autofill::CREDIT_CARD_NUMBER, |
| base::UTF8ToUTF16(card->GetNumber())); |
| tmp_autofill_card.SetRawInfo(autofill::CREDIT_CARD_NAME_FULL, |
| base::UTF8ToUTF16(card->GetName())); |
| tmp_autofill_card.SetRawInfo(autofill::CREDIT_CARD_EXP_MONTH, |
| base::UTF8ToUTF16(card->GetExpiryMonth())); |
| tmp_autofill_card.SetRawInfo(autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR, |
| base::UTF8ToUTF16(card->GetExpiryYear())); |
| tmp_autofill_card.SetRawInfo(autofill::CREDIT_CARD_VERIFICATION_CODE, |
| base::UTF8ToUTF16(card->GetCvc())); |
| |
| static_cast<autofill::BrowserAutofillManager&>( |
| autofill_driver->GetAutofillManager()) |
| .FillOrPreviewForm(autofill::mojom::ActionPersistence::kFill, *form, |
| global_field_id, &tmp_autofill_card, |
| autofill::AutofillTriggerSource::kDevtools); |
| |
| return Response::Success(); |
| } |
| |
| void AutofillHandler::SetAddresses( |
| std::unique_ptr<protocol::Array<protocol::Autofill::Address>> addresses, |
| std::unique_ptr<SetAddressesCallback> callback) { |
| if (!content::DevToolsAgentHost::GetForId(target_id_)) { |
| std::move(callback)->sendFailure(Response::ServerError("Target not found")); |
| return; |
| } |
| |
| std::vector<autofill::AutofillProfile> test_address_for_countries; |
| base::Value::List profiles; |
| |
| for (const auto& address : *addresses) { |
| base::Value::Dict address_fields; |
| for (const auto& field : *address->GetFields()) { |
| address_fields.Set(field->GetName(), field->GetValue()); |
| } |
| profiles.Append(std::move(address_fields)); |
| } |
| |
| std::optional<std::vector<autofill::AutofillProfile>> autofill_profiles = |
| autofill::AutofillProfilesFromJSON(&profiles); |
| if (autofill_profiles) { |
| const std::string locale = "en-US"; |
| for (const autofill::AutofillProfile& profile : *autofill_profiles) { |
| const std::u16string test_address_country = |
| profile.GetInfo(autofill::FieldType::ADDRESS_HOME_COUNTRY, locale); |
| // The current test address for Germany is based on the old model. If the |
| // new model is enabled we should not offer it in the list of |
| // available addresses. |
| // TODO(b/40270486): Offer a test address version for when the new model |
| // is enabled. |
| if (test_address_country == u"Germany") { |
| continue; |
| } |
| |
| test_address_for_countries.push_back(profile); |
| } |
| } |
| |
| autofill::ContentAutofillDriver* autofill_driver = GetAutofillDriver(); |
| if (!autofill_driver) { |
| std::move(callback)->sendFailure( |
| Response::ServerError("RenderFrameHost is being destroyed")); |
| return; |
| } |
| |
| static_cast<autofill::BrowserAutofillManager&>( |
| autofill_driver->GetAutofillManager()) |
| .client() |
| .set_test_addresses(test_address_for_countries); |
| std::move(callback)->sendSuccess(); |
| } |
| |
| void AutofillHandler::OnFillOrPreviewForm( |
| autofill::AutofillManager& manager, |
| autofill::FormGlobalId form_id, |
| autofill::mojom::ActionPersistence action_persistence, |
| const base::flat_set<autofill::FieldGlobalId>& filled_field_ids, |
| const autofill::FillingPayload& filling_payload) { |
| // We only care about address forms that were filled. |
| if (action_persistence != autofill::mojom::ActionPersistence::kFill || |
| !std::holds_alternative<const autofill::AutofillProfile*>( |
| filling_payload)) { |
| return; |
| } |
| |
| autofill::FormStructure& form = |
| CHECK_DEREF(manager.FindCachedFormById(form_id)); |
| const autofill::AutofillProfile* profile_used_to_fill_form = |
| std::get<const autofill::AutofillProfile*>(filling_payload); |
| |
| auto filled_form_ids = [&] { |
| base::flat_set<autofill::FormGlobalId> ids; |
| for (const std::unique_ptr<autofill::AutofillField>& field : form) { |
| if (filled_field_ids.contains(field->global_id())) { |
| ids.insert(field->renderer_form_id()); |
| } |
| } |
| return ids; |
| }(); |
| |
| // Devtools is already in English, so we can default the locale to en-US. |
| const std::string locale = "en-US"; |
| |
| auto filled_fields_to_be_sent_to_devtools = |
| std::make_unique<protocol::Array<protocol::Autofill::FilledField>>(); |
| filled_fields_to_be_sent_to_devtools->reserve(filled_field_ids.size()); |
| for (const std::unique_ptr<autofill::AutofillField>& field : form) { |
| // `form_structure` may contains fields from multiple forms, filter out |
| // fields from forms that have no autofilled fields as irrelevant. |
| if (!filled_form_ids.contains(field->renderer_form_id())) { |
| continue; |
| } |
| autofill::FieldTypeSet field_types = field->Type().GetTypes(); |
| // Whether the field was classified from the autocomplete attribute or |
| // predictions. If no autocomplete attribute exists OR the actual ServerType |
| // differs from what it would have been with only autocomplete, autofill |
| // inferred the type. |
| bool autofill_inferred = |
| field->html_type() == autofill::HtmlFieldType::kUnspecified || |
| field->html_type() == autofill::HtmlFieldType::kUnrecognized || |
| !field_types.contains( |
| autofill::HtmlFieldTypeToBestCorrespondingFieldType( |
| field->html_type())); |
| |
| std::vector<std::string_view> field_type_strings = base::ToVector( |
| field_types, &autofill::FieldTypeToDeveloperRepresentationString); |
| std::erase(field_type_strings, ""); |
| // TODO(crbug.com/436489442): Reading the filled value from the profile is |
| // brittle because it could diverge from the values that are actually sent |
| // to the renderer. A more robust solution should be found. |
| std::u16string filled_value = u""; |
| if (filled_field_ids.contains(field->global_id())) { |
| std::string failure_to_fill; |
| filled_value = |
| autofill::GetFillingValueAndTypeForProfile( |
| *profile_used_to_fill_form, locale, field->Type(), *field, |
| manager.client().GetAddressNormalizer(), &failure_to_fill) |
| .first; |
| } |
| filled_fields_to_be_sent_to_devtools->push_back( |
| protocol::Autofill::FilledField::Create() |
| .SetId(base::UTF16ToUTF8(field->id_attribute())) |
| .SetName(base::UTF16ToUTF8(field->name_attribute())) |
| .SetValue(base::UTF16ToUTF8(filled_value)) |
| .SetHtmlType(std::string( |
| autofill::FormControlTypeToString(field->form_control_type()))) |
| .SetAutofillType(base::JoinString(field_type_strings, ", ")) |
| .SetFillingStrategy( |
| autofill_inferred |
| ? protocol::Autofill::FillingStrategyEnum::AutofillInferred |
| : protocol::Autofill::FillingStrategyEnum:: |
| AutocompleteAttribute) |
| .SetFrameId( |
| GetRenderFrameDevtoolsToken( |
| target_id_, field->global_id().frame_token->ToString()) |
| .value_or("")) |
| .SetFieldId(field->renderer_id().value()) |
| .Build()); |
| } |
| |
| // Send profile information to devtools so that it can build the UI. |
| // We use the same format we see in the settings page. |
| std::vector<std::vector<autofill::AutofillAddressUIComponent>> components; |
| autofill::GetAddressComponents( |
| base::UTF16ToUTF8(profile_used_to_fill_form->GetInfo( |
| autofill::FieldType::ADDRESS_HOME_COUNTRY, locale)), |
| locale, |
| /*include_literals=*/false, &components, nullptr); |
| |
| // `profile_address_fields` is used to represent a profile as seen in the |
| // settings page. It consists of a 2D array where each inner array is used |
| // build a "profile line". The following `profile_address_fields` for |
| // instance: |
| // [[{name: "GIVE_NAME", value: "Jon"}, {name: "FAMILY_NAME", value: "Doe"}], |
| // [{name: "CITY", value: "Munich"}, {name: "ZIP", value: "81456"}]] should |
| // allow the receiver to render: |
| // Jon Doe |
| // Munich 81456 |
| auto profile_address_fields = |
| std::make_unique<protocol::Array<protocol::Autofill::AddressFields>>(); |
| for (const std::vector<autofill::AutofillAddressUIComponent>& line : |
| components) { |
| auto profile_values = |
| std::make_unique<protocol::Array<protocol::Autofill::AddressField>>(); |
| profile_values->reserve(line.size()); |
| for (const autofill::AutofillAddressUIComponent& component : line) { |
| profile_values->push_back( |
| protocol::Autofill::AddressField::Create() |
| .SetName(FieldTypeToString(component.field)) |
| .SetValue(base::UTF16ToUTF8( |
| profile_used_to_fill_form->GetInfo(component.field, locale))) |
| .Build()); |
| } |
| profile_address_fields->push_back( |
| protocol::Autofill::AddressFields::Create() |
| .SetFields(std::move(profile_values)) |
| .Build()); |
| } |
| // Insert the country at the end. This is required because it is not part of |
| // `AutofillAddressUIComponent`. |
| auto country_line = |
| std::make_unique<protocol::Array<protocol::Autofill::AddressField>>(); |
| country_line->push_back( |
| protocol::Autofill::AddressField::Create() |
| .SetName(FieldTypeToString(autofill::FieldType::ADDRESS_HOME_COUNTRY)) |
| .SetValue(base::UTF16ToUTF8(profile_used_to_fill_form->GetInfo( |
| autofill::FieldType::ADDRESS_HOME_COUNTRY, locale))) |
| .Build()); |
| profile_address_fields->push_back(protocol::Autofill::AddressFields::Create() |
| .SetFields(std::move(country_line)) |
| .Build()); |
| // Similarly to the `ADDRESS_HOME_COUNTRY`, also include |
| // `PHONE_HOME_WHOLE_NUMBER` and `EMAIL_ADDRESS`. However in a single line. |
| auto phone_and_email_line = |
| std::make_unique<protocol::Array<protocol::Autofill::AddressField>>(); |
| phone_and_email_line->push_back( |
| protocol::Autofill::AddressField::Create() |
| .SetName( |
| FieldTypeToString(autofill::FieldType::PHONE_HOME_WHOLE_NUMBER)) |
| .SetValue(base::UTF16ToUTF8(profile_used_to_fill_form->GetInfo( |
| autofill::FieldType::PHONE_HOME_WHOLE_NUMBER, locale))) |
| .Build()); |
| phone_and_email_line->push_back( |
| protocol::Autofill::AddressField::Create() |
| .SetName(FieldTypeToString(autofill::FieldType::EMAIL_ADDRESS)) |
| .SetValue(base::UTF16ToUTF8(profile_used_to_fill_form->GetInfo( |
| autofill::FieldType::EMAIL_ADDRESS, locale))) |
| .Build()); |
| |
| profile_address_fields->push_back( |
| protocol::Autofill::AddressFields::Create() |
| .SetFields(std::move(phone_and_email_line)) |
| .Build()); |
| |
| frontend_->AddressFormFilled( |
| std::move(filled_fields_to_be_sent_to_devtools), |
| protocol::Autofill::AddressUI::Create() |
| .SetAddressFields(std::move(profile_address_fields)) |
| .Build()); |
| } |
| |
| void AutofillHandler::OnAutofillManagerStateChanged( |
| autofill::AutofillManager& manager, |
| autofill::AutofillManager::LifecycleState old_state, |
| autofill::AutofillManager::LifecycleState new_state) { |
| using enum autofill::AutofillManager::LifecycleState; |
| switch (new_state) { |
| case kInactive: |
| case kActive: |
| case kPendingReset: |
| break; |
| case kPendingDeletion: |
| autofill_manager_observation_.Reset(); |
| break; |
| } |
| } |
| |
| void AutofillHandler::OnContentAutofillDriverFactoryDestroyed( |
| autofill::ContentAutofillDriverFactory& factory) { |
| autofill_manager_observation_.Reset(); |
| } |
| |
| void AutofillHandler::OnContentAutofillDriverCreated( |
| autofill::ContentAutofillDriverFactory&, |
| autofill::ContentAutofillDriver& new_driver) { |
| // If the outermost frame driver (returned by `GetAutofillDriver()`) was |
| // recreated (e.g. happens after certain navigations) we need to resubscribe |
| // to the autofill manager, which is also recreated, to keep getting observer |
| // callbacks like `OnFillOrPreviewForm`. |
| if (enabled_ && &new_driver == GetAutofillDriver()) { |
| autofill_manager_observation_.Reset(); |
| autofill_manager_observation_.Observe(&new_driver.GetAutofillManager()); |
| } |
| } |
| |
| autofill::ContentAutofillDriver* AutofillHandler::GetAutofillDriver() { |
| auto host = content::DevToolsAgentHost::GetForId(target_id_); |
| CHECK(host); |
| |
| if (!host->GetWebContents()) { |
| return nullptr; |
| } |
| |
| content::RenderFrameHost* outermost_primary_rfh = |
| host->GetWebContents()->GetOutermostWebContents()->GetPrimaryMainFrame(); |
| |
| return autofill::ContentAutofillDriver::GetForRenderFrameHost( |
| outermost_primary_rfh); |
| } |
| |
| autofill::AutofillClient* AutofillHandler::GetAutofillClient() { |
| auto host = content::DevToolsAgentHost::GetForId(target_id_); |
| CHECK(host); |
| |
| if (!host->GetWebContents()) { |
| return nullptr; |
| } |
| |
| return autofill::ContentAutofillClient::FromWebContents( |
| host->GetWebContents()); |
| } |
| |
| Response AutofillHandler::Enable() { |
| if (enabled_) { |
| return Response::Success(); |
| } |
| |
| enabled_ = true; |
| |
| auto host = content::DevToolsAgentHost::GetForId(target_id_); |
| CHECK(host); |
| |
| autofill::ContentAutofillDriver* driver = GetAutofillDriver(); |
| if (driver && host->GetType() == content::DevToolsAgentHost::kTypePage) { |
| factory_observation_.Observe( |
| autofill::ContentAutofillDriverFactory::FromWebContents( |
| host->GetWebContents())); |
| autofill_manager_observation_.Observe(&driver->GetAutofillManager()); |
| } |
| |
| return Response::Success(); |
| } |
| |
| Response AutofillHandler::Disable() { |
| enabled_ = false; |
| autofill_manager_observation_.Reset(); |
| autofill::AutofillClient* autofill_client = GetAutofillClient(); |
| if (autofill_client) { |
| autofill_client->set_test_addresses({}); |
| } |
| return Response::Success(); |
| } |