| // 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/automation/automation_app_interface.h" | 
 |  | 
 | #import "base/json/json_reader.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/personal_data_manager.h" | 
 | #import "ios/chrome/browser/autofill/personal_data_manager_factory.h" | 
 | #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.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" | 
 |  | 
 | #if !defined(__has_feature) || !__has_feature(objc_arc) | 
 | #error "This file requires ARC support." | 
 | #endif | 
 |  | 
 | using autofill::PersonalDataManager; | 
 | using autofill::PersonalDataManagerFactory; | 
 |  | 
 | namespace { | 
 |  | 
 | // Converts a string (from the test recipe) to the autofill ServerFieldType it | 
 | // represents. | 
 | autofill::ServerFieldType ServerFieldTypeFromString(const std::string& str, | 
 |                                                     NSError** error) { | 
 |   static std::map<const std::string, autofill::ServerFieldType> | 
 |       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 (size_t i = autofill::NO_SERVER_DATA; | 
 |          i < autofill::MAX_VALID_FIELD_TYPE; ++i) { | 
 |       autofill::AutofillType autofill_type( | 
 |           static_cast<autofill::ServerFieldType>(i)); | 
 |       string_to_field_type_map[autofill_type.ToString()] = | 
 |           autofill_type.GetStorableType(); | 
 |     } | 
 |  | 
 |     for (size_t i = static_cast<size_t>(autofill::HtmlFieldType::kUnspecified); | 
 |          i <= static_cast<size_t>(autofill::HtmlFieldType::kMaxValue); ++i) { | 
 |       autofill::AutofillType autofill_type( | 
 |           static_cast<autofill::HtmlFieldType>(i), | 
 |           autofill::HtmlFieldMode::kNone); | 
 |       string_to_field_type_map[autofill_type.ToString()] = | 
 |           autofill_type.GetStorableType(); | 
 |     } | 
 |   } | 
 |  | 
 |   if (string_to_field_type_map.find(str) == string_to_field_type_map.end()) { | 
 |     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::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::ServerFieldType type = | 
 |         ServerFieldTypeFromString(field_type, &error); | 
 |     if (error) { | 
 |       return error; | 
 |     } | 
 |  | 
 |     // TODO(crbug.com/895968): 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)); | 
 |     } | 
 |   } | 
 |  | 
 |   // Save the profile and credit card generated to the personal data manager. | 
 |   ChromeBrowserState* browser_state = | 
 |       chrome_test_util::GetOriginalBrowserState(); | 
 |   PersonalDataManager* personal_data_manager = | 
 |       PersonalDataManagerFactory::GetForBrowserState(browser_state); | 
 |   personal_data_manager->ClearAllLocalData(); | 
 |   personal_data_manager->AddCreditCard(credit_card); | 
 |   personal_data_manager->SaveImportedProfile(profile); | 
 |  | 
 |   return nil; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | @implementation AutomationAppInterface | 
 |  | 
 | + (NSError*)setAutofillAutomationProfile:(NSString*)profileJSON { | 
 |   absl::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 |