blob: 10c5105320b31bcdf983d88b2ee217cb11af297b [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.
#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