| // 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. |
| |
| #import "ios/chrome/browser/autofill/model/automation/automation_app_interface.h" |
| |
| #import <map> |
| #import <string_view> |
| |
| #import "base/containers/contains.h" |
| #import "base/json/json_reader.h" |
| #import "base/strings/string_util.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/strings/utf_string_conversions.h" |
| #import "base/uuid.h" |
| #import "base/values.h" |
| #import "components/autofill/core/browser/data_manager/addresses/address_data_manager.h" |
| #import "components/autofill/core/browser/data_manager/payments/payments_data_manager.h" |
| #import "components/autofill/core/browser/data_manager/personal_data_manager.h" |
| #import "components/autofill/core/browser/field_types.h" |
| #import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h" |
| #import "ios/chrome/browser/shared/model/profile/profile_ios.h" |
| #import "ios/chrome/test/app/chrome_test_util.h" |
| #import "ios/chrome/test/app/tab_test_util.h" |
| #import "ios/testing/nserror_util.h" |
| #import "ios/web/public/js_messaging/web_frames_manager.h" |
| #import "ios/web/public/web_state.h" |
| |
| using autofill::PersonalDataManager; |
| using autofill::PersonalDataManagerFactory; |
| |
| namespace { |
| |
| // Converts a string (from the test recipe) to the autofill FieldType it |
| // represents. |
| autofill::FieldType FieldTypeFromString(std::string_view str, NSError** error) { |
| static std::map<std::string_view, autofill::FieldType> |
| string_to_field_type_map; |
| |
| // Only init the string to autofill field type map on the first call. |
| // The test recipe can contain both server and html field types, as when |
| // creating the recipe either type can be returned from predictions. |
| // Therefore, store both in this map. |
| if (string_to_field_type_map.empty()) { |
| for (autofill::FieldType ft : autofill::kAllFieldTypes) { |
| string_to_field_type_map[autofill::FieldTypeToStringView(ft)] = ft; |
| } |
| |
| for (autofill::HtmlFieldType hft : autofill::kAllHtmlFieldTypes) { |
| string_to_field_type_map[autofill::FieldTypeToStringView(hft)] = |
| autofill::HtmlFieldTypeToBestCorrespondingFieldType(hft); |
| } |
| } |
| |
| if (!base::Contains(string_to_field_type_map, str)) { |
| NSString* error_description = [NSString |
| stringWithFormat:@"Unable to recognize autofill field type %@!", |
| base::SysUTF8ToNSString(str)]; |
| *error = testing::NSErrorWithLocalizedDescription(error_description); |
| return autofill::UNKNOWN_TYPE; |
| } |
| |
| return string_to_field_type_map[str]; |
| } |
| |
| // Loads the defined autofill profile into the personal data manager, so that |
| // autofill actions will be suggested when tapping on an autofillable form. |
| // The autofill profile should be pulled from the test recipe, and consists of |
| // a list of dictionaries, each mapping one autofill type to one value, like so: |
| // "autofillProfile": [ |
| // { "type": "NAME_FIRST", "value": "Satsuki" }, |
| // { "type": "NAME_LAST", "value": "Yumizuka" }, |
| // ], |
| NSError* PrepareAutofillProfileWithValues( |
| const base::Value::List* autofill_profile) { |
| if (!autofill_profile) { |
| return testing::NSErrorWithLocalizedDescription( |
| @"Unable to find autofill profile in parsed JSON value."); |
| } |
| |
| autofill::AutofillProfile profile( |
| autofill::i18n_model_definition::kLegacyHierarchyCountryCode); |
| autofill::CreditCard credit_card( |
| base::Uuid::GenerateRandomV4().AsLowercaseString(), |
| "https://www.example.com/"); |
| |
| // For each type-value dictionary in the autofill profile list, validate it, |
| // then add it to the appropriate profile. |
| for (const auto& profile_list_item : *autofill_profile) { |
| const base::Value::Dict* entry = profile_list_item.GetIfDict(); |
| if (!entry) { |
| return testing::NSErrorWithLocalizedDescription( |
| @"Failed to extract an entry!"); |
| } |
| |
| const base::Value* type_container = entry->Find("type"); |
| if (!type_container->is_string()) { |
| return testing::NSErrorWithLocalizedDescription(@"Type is not a string!"); |
| } |
| const base::Value* value_container = entry->Find("value"); |
| if (!value_container->is_string()) { |
| return testing::NSErrorWithLocalizedDescription( |
| @"Value is not a string!"); |
| } |
| |
| const std::string field_type = type_container->GetString(); |
| NSError* error = nil; |
| autofill::FieldType type = FieldTypeFromString(field_type, &error); |
| if (error) { |
| return error; |
| } |
| |
| // TODO(crbug.com/40598404): Autofill profile and credit card info should be |
| // loaded from separate fields in the recipe, instead of being grouped |
| // together. However, need to make sure this change is also performed on |
| // desktop automation. |
| const std::string field_value = value_container->GetString(); |
| if (base::StartsWith(field_type, "HTML_TYPE_CREDIT_CARD_", |
| base::CompareCase::INSENSITIVE_ASCII) || |
| base::StartsWith(field_type, "CREDIT_CARD_", |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| credit_card.SetRawInfo(type, base::UTF8ToUTF16(field_value)); |
| } else { |
| profile.SetRawInfo(type, base::UTF8ToUTF16(field_value)); |
| } |
| } |
| |
| // Clear all existing local data and save the profile and credit card |
| // generated to the personal data manager. |
| ProfileIOS* profileIOS = chrome_test_util::GetOriginalProfile(); |
| PersonalDataManager* pdm = |
| PersonalDataManagerFactory::GetForProfile(profileIOS); |
| autofill::PaymentsDataManager& paydm = pdm->payments_data_manager(); |
| for (const autofill::CreditCard* local_card : paydm.GetLocalCreditCards()) { |
| paydm.RemoveByGUID(local_card->guid()); |
| } |
| autofill::AddressDataManager& adm = pdm->address_data_manager(); |
| for (const autofill::AutofillProfile* local_profile : |
| adm.GetProfilesByRecordType( |
| autofill::AutofillProfile::RecordType::kLocalOrSyncable)) { |
| adm.RemoveProfile(local_profile->guid()); |
| } |
| paydm.AddCreditCard(credit_card); |
| adm.AddProfile(profile); |
| |
| return nil; |
| } |
| |
| } // namespace |
| |
| @implementation AutomationAppInterface |
| |
| + (NSError*)setAutofillAutomationProfile:(NSString*)profileJSON { |
| std::optional<base::Value> readResult = |
| base::JSONReader::Read(base::SysNSStringToUTF8(profileJSON)); |
| if (!readResult.has_value()) { |
| return testing::NSErrorWithLocalizedDescription( |
| @"Unable to parse JSON string in app side."); |
| } |
| |
| base::Value recipeRoot = std::move(readResult).value(); |
| |
| const base::Value::List* autofillProfile = |
| recipeRoot.GetDict().FindList("autofillProfile"); |
| return PrepareAutofillProfileWithValues(autofillProfile); |
| } |
| |
| @end |