// Copyright 2013 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/ui_bundled/chrome_autofill_client_ios.h"
#import <optional>
#import <utility>
#import <vector>
#import "base/check.h"
#import "base/check_deref.h"
#import "base/functional/bind.h"
#import "base/functional/callback.h"
#import "base/memory/ptr_util.h"
#import "base/notreached.h"
#import "base/ranges/algorithm.h"
#import "base/strings/string_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/autofill/core/browser/autofill_plus_address_delegate.h"
#import "components/autofill/core/browser/autofill_save_update_address_profile_delegate_ios.h"
#import "components/autofill/core/browser/form_data_importer.h"
#import "components/autofill/core/browser/logging/log_manager.h"
#import "components/autofill/core/browser/payments/payments_network_interface.h"
#import "components/autofill/core/browser/ui/suggestion_type.h"
#import "components/autofill/core/common/autofill_features.h"
#import "components/autofill/core/common/autofill_prefs.h"
#import "components/autofill/ios/browser/autofill_driver_ios_factory.h"
#import "components/autofill/ios/browser/autofill_util.h"
#import "components/infobars/core/infobar.h"
#import "components/infobars/core/infobar_manager.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/form_parsing/form_data_parser.h"
#import "components/password_manager/core/browser/password_form.h"
#import "components/password_manager/core/common/password_manager_pref_names.h"
#import "components/plus_addresses/plus_address_service.h"
#import "components/security_state/ios/security_state_utils.h"
#import "components/sync/service/sync_service.h"
#import "components/translate/core/browser/translate_manager.h"
#import "components/ukm/ios/ukm_url_recorder.h"
#import "components/variations/service/variations_service.h"
#import "ios/chrome/browser/autofill/model/address_normalizer_factory.h"
#import "ios/chrome/browser/autofill/model/autocomplete_history_manager_factory.h"
#import "ios/chrome/browser/autofill/model/autofill_log_router_factory.h"
#import "ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h"
#import "ios/chrome/browser/autofill/model/personal_data_manager_factory.h"
#import "ios/chrome/browser/autofill/model/strike_database_factory.h"
#import "ios/chrome/browser/autofill/ui_bundled/scoped_autofill_payment_reauth_module_override.h"
#import "ios/chrome/browser/device_reauth/ios_device_authenticator.h"
#import "ios/chrome/browser/device_reauth/ios_device_authenticator_factory.h"
#import "ios/chrome/browser/history/model/history_service_factory.h"
#import "ios/chrome/browser/infobars/model/infobar_ios.h"
#import "ios/chrome/browser/infobars/model/infobar_utils.h"
#import "ios/chrome/browser/passwords/model/password_tab_helper.h"
#import "ios/chrome/browser/plus_addresses/model/plus_address_service_factory.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/translate/model/chrome_ios_translate_client.h"
#import "ios/chrome/browser/webdata_services/model/web_data_service_factory.h"
#import "ios/chrome/common/channel_info.h"
#import "ios/web/public/web_state.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
#import "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
namespace autofill {
ChromeAutofillClientIOS::ChromeAutofillClientIOS(
ChromeBrowserState* browser_state,
web::WebState* web_state,
infobars::InfoBarManager* infobar_manager,
id<AutofillClientIOSBridge> bridge)
: pref_service_(browser_state->GetPrefs()),
sync_service_(SyncServiceFactory::GetForBrowserState(browser_state)),
personal_data_manager_(PersonalDataManagerFactory::GetForBrowserState(
browser_state->GetOriginalChromeBrowserState())),
autocomplete_history_manager_(
AutocompleteHistoryManagerFactory::GetForBrowserState(browser_state)),
browser_state_(browser_state),
web_state_(web_state),
bridge_(bridge),
identity_manager_(IdentityManagerFactory::GetForBrowserState(
browser_state->GetOriginalChromeBrowserState())),
infobar_manager_(infobar_manager),
// TODO(crbug.com/40612524): Replace the closure with a callback to the
// renderer that indicates if log messages should be sent from the
// renderer.
log_manager_(LogManager::Create(
AutofillLogRouterFactory::GetForBrowserState(browser_state),
base::RepeatingClosure())),
ablation_study_(GetApplicationContext()->GetLocalState()) {}
ChromeAutofillClientIOS::~ChromeAutofillClientIOS() {
HideAutofillSuggestions(SuggestionHidingReason::kTabGone);
}
void ChromeAutofillClientIOS::SetBaseViewController(
UIViewController* base_view_controller) {
base_view_controller_ = base_view_controller;
}
version_info::Channel ChromeAutofillClientIOS::GetChannel() const {
return ::GetChannel();
}
bool ChromeAutofillClientIOS::IsOffTheRecord() const {
return web_state_->GetBrowserState()->IsOffTheRecord();
}
scoped_refptr<network::SharedURLLoaderFactory>
ChromeAutofillClientIOS::GetURLLoaderFactory() {
return base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
web_state_->GetBrowserState()->GetURLLoaderFactory());
}
AutofillDriverFactory& ChromeAutofillClientIOS::GetAutofillDriverFactory() {
return CHECK_DEREF(AutofillDriverIOSFactory::FromWebState(web_state_));
}
AutofillCrowdsourcingManager*
ChromeAutofillClientIOS::GetCrowdsourcingManager() {
if (!crowdsourcing_manager_) {
// Lazy initialization to avoid virtual function calls in the constructor.
crowdsourcing_manager_ = std::make_unique<AutofillCrowdsourcingManager>(
this, GetChannel(), GetLogManager());
}
return crowdsourcing_manager_.get();
}
PersonalDataManager* ChromeAutofillClientIOS::GetPersonalDataManager() {
return personal_data_manager_;
}
AutocompleteHistoryManager*
ChromeAutofillClientIOS::GetAutocompleteHistoryManager() {
return autocomplete_history_manager_;
}
PrefService* ChromeAutofillClientIOS::GetPrefs() {
return const_cast<PrefService*>(std::as_const(*this).GetPrefs());
}
const PrefService* ChromeAutofillClientIOS::GetPrefs() const {
return pref_service_;
}
syncer::SyncService* ChromeAutofillClientIOS::GetSyncService() {
return sync_service_;
}
signin::IdentityManager* ChromeAutofillClientIOS::GetIdentityManager() {
return identity_manager_;
}
FormDataImporter* ChromeAutofillClientIOS::GetFormDataImporter() {
if (!form_data_importer_) {
form_data_importer_ = std::make_unique<FormDataImporter>(
this,
ios::HistoryServiceFactory::GetForBrowserState(
browser_state_, ServiceAccessType::EXPLICIT_ACCESS),
GetApplicationContext()->GetApplicationLocale());
}
return form_data_importer_.get();
}
payments::IOSChromePaymentsAutofillClient*
ChromeAutofillClientIOS::GetPaymentsAutofillClient() {
return &payments_autofill_client_;
}
StrikeDatabase* ChromeAutofillClientIOS::GetStrikeDatabase() {
return StrikeDatabaseFactory::GetForBrowserState(
browser_state_->GetOriginalChromeBrowserState());
}
ukm::UkmRecorder* ChromeAutofillClientIOS::GetUkmRecorder() {
return GetApplicationContext()->GetUkmRecorder();
}
ukm::SourceId ChromeAutofillClientIOS::GetUkmSourceId() {
return ukm::GetSourceIdForWebStateDocument(web_state_);
}
AddressNormalizer* ChromeAutofillClientIOS::GetAddressNormalizer() {
return AddressNormalizerFactory::GetInstance();
}
const GURL& ChromeAutofillClientIOS::GetLastCommittedPrimaryMainFrameURL()
const {
return web_state_->GetLastCommittedURL();
}
url::Origin ChromeAutofillClientIOS::GetLastCommittedPrimaryMainFrameOrigin()
const {
return url::Origin::Create(GetLastCommittedPrimaryMainFrameURL());
}
security_state::SecurityLevel
ChromeAutofillClientIOS::GetSecurityLevelForUmaHistograms() {
return security_state::GetSecurityLevelForWebState(web_state_);
}
const translate::LanguageState* ChromeAutofillClientIOS::GetLanguageState() {
// TODO(crbug.com/41430413): iOS vs other platforms extracts language from
// the top level frame vs whatever frame directly holds the form.
auto* translate_client = ChromeIOSTranslateClient::FromWebState(web_state_);
if (translate_client) {
auto* translate_manager = translate_client->GetTranslateManager();
if (translate_manager)
return translate_manager->GetLanguageState();
}
return nullptr;
}
translate::TranslateDriver* ChromeAutofillClientIOS::GetTranslateDriver() {
auto* translate_client = ChromeIOSTranslateClient::FromWebState(web_state_);
if (translate_client) {
return translate_client->GetTranslateDriver();
}
return nullptr;
}
GeoIpCountryCode ChromeAutofillClientIOS::GetVariationConfigCountryCode()
const {
variations::VariationsService* variation_service =
GetApplicationContext()->GetVariationsService();
// Retrieves the country code from variation service and converts it to upper
// case.
return GeoIpCountryCode(
variation_service
? base::ToUpperASCII(variation_service->GetLatestCountry())
: std::string());
}
void ChromeAutofillClientIOS::ShowAutofillSettings(
SuggestionType suggestion_type) {
NOTREACHED_IN_MIGRATION();
}
void ChromeAutofillClientIOS::ConfirmSaveAddressProfile(
const AutofillProfile& profile,
const AutofillProfile* original_profile,
bool is_migration_to_account,
AddressProfileSavePromptCallback callback) {
for (infobars::InfoBar* infobar : infobar_manager_->infobars()) {
AutofillSaveUpdateAddressProfileDelegateIOS* existing_delegate =
AutofillSaveUpdateAddressProfileDelegateIOS::FromInfobarDelegate(
infobar->delegate());
if (existing_delegate) {
if (existing_delegate->is_infobar_visible()) {
// AutoDecline the new prompt if the existing prompt is visible.
std::move(callback).Run(
AutofillClient::AddressPromptUserDecision::kAutoDeclined, profile);
return;
} else {
// If the existing prompt is not visible, it means that the user has
// closed the prompt of the previous import process using the "Cancel"
// button or it has been accepted by the user. A fallback icon is shown
// for the user in the omnibox to get back to the prompt. If it is
// already accepted by the user, the save button is disabled and the
// saved data is shown to user. In both the cases, the original prompt
// is replaced by the new one provided that the modal view of the
// original infobar is not visible to the user.
infobar_manager_->RemoveInfoBar(infobar);
break;
}
}
}
auto delegate = std::make_unique<AutofillSaveUpdateAddressProfileDelegateIOS>(
profile, original_profile, GetUserEmail(),
GetApplicationContext()->GetApplicationLocale(), is_migration_to_account,
std::move(callback));
infobar_manager_->AddInfoBar(std::make_unique<InfoBarIOS>(
InfobarType::kInfobarTypeSaveAutofillAddressProfile,
std::move(delegate)));
}
void ChromeAutofillClientIOS::ShowEditAddressProfileDialog(
const AutofillProfile& profile,
AddressProfileSavePromptCallback on_user_decision_callback) {
NOTREACHED();
}
void ChromeAutofillClientIOS::ShowDeleteAddressProfileDialog(
const AutofillProfile& profile,
AddressProfileDeleteDialogCallback delete_dialog_callback) {
NOTREACHED();
}
void ChromeAutofillClientIOS::ShowAutofillSuggestions(
const AutofillClient::PopupOpenArgs& open_args,
base::WeakPtr<AutofillSuggestionDelegate> delegate) {
[bridge_ showAutofillPopup:open_args.suggestions suggestionDelegate:delegate];
}
AutofillPlusAddressDelegate* ChromeAutofillClientIOS::GetPlusAddressDelegate() {
return PlusAddressServiceFactory::GetForBrowserState(browser_state_);
}
void ChromeAutofillClientIOS::OfferPlusAddressCreation(
const url::Origin& main_frame_origin,
PlusAddressCallback callback) {
AutofillBottomSheetTabHelper* bottomSheetTabHelper =
AutofillBottomSheetTabHelper::FromWebState(web_state_);
bottomSheetTabHelper->ShowPlusAddressesBottomSheet(std::move(callback));
}
void ChromeAutofillClientIOS::UpdateAutofillDataListValues(
base::span<const autofill::SelectOption> datalist) {
// No op. ios/web_view does not support display datalist.
}
void ChromeAutofillClientIOS::PinAutofillSuggestions() {
NOTIMPLEMENTED();
}
void ChromeAutofillClientIOS::HideAutofillSuggestions(
SuggestionHidingReason reason) {
[bridge_ hideAutofillPopup];
}
bool ChromeAutofillClientIOS::IsAutocompleteEnabled() const {
return prefs::IsAutocompleteEnabled(GetPrefs());
}
bool ChromeAutofillClientIOS::IsPasswordManagerEnabled() {
return GetPrefs()->GetBoolean(
password_manager::prefs::kCredentialsEnableService);
}
void ChromeAutofillClientIOS::DidFillOrPreviewForm(
mojom::ActionPersistence action_persistence,
AutofillTriggerSource trigger_source,
bool is_refill) {}
bool ChromeAutofillClientIOS::IsContextSecure() const {
return IsContextSecureForWebState(web_state_);
}
FormInteractionsFlowId
ChromeAutofillClientIOS::GetCurrentFormInteractionsFlowId() {
// Currently not in use here. See `ChromeAutofillClient` for a proper
// implementation.
return {};
}
LogManager* ChromeAutofillClientIOS::GetLogManager() const {
return log_manager_.get();
}
const AutofillAblationStudy& ChromeAutofillClientIOS::GetAblationStudy() const {
return ablation_study_;
}
bool ChromeAutofillClientIOS::IsLastQueriedField(FieldGlobalId field_id) {
return [bridge_ isLastQueriedField:field_id];
}
bool ChromeAutofillClientIOS::ShouldFormatForLargeKeyboardAccessory() const {
return IsKeyboardAccessoryUpgradeEnabled();
}
std::unique_ptr<device_reauth::DeviceAuthenticator>
ChromeAutofillClientIOS::GetDeviceAuthenticator() {
device_reauth::DeviceAuthParams params(
base::Seconds(60), device_reauth::DeviceAuthSource::kAutofill);
id<ReauthenticationProtocol> reauthModule =
ScopedAutofillPaymentReauthModuleOverride::Get();
if (!reauthModule) {
reauthModule = [[ReauthenticationModule alloc] init];
}
return CreateIOSDeviceAuthenticator(reauthModule, browser_state_, params);
}
std::optional<std::u16string> ChromeAutofillClientIOS::GetUserEmail() {
AuthenticationService* authenticationService =
AuthenticationServiceFactory::GetForBrowserState(browser_state_);
DCHECK(authenticationService);
id<SystemIdentity> identity =
authenticationService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
if (identity) {
return base::SysNSStringToUTF16(identity.userEmail);
}
return std::nullopt;
}
AutofillClient::PasswordFormClassification
ChromeAutofillClientIOS::ClassifyAsPasswordForm(AutofillManager& manager,
FormGlobalId form_id,
FieldGlobalId field_id) const {
FormStructure* form_structure = manager.FindCachedFormById(form_id);
if (!form_structure) {
return {};
}
FormDataAndServerPredictions form_and_predictions =
GetFormDataAndServerPredictions(*form_structure);
// Gets the renderer form corresponding to `field_id` when Autofill across
// iframes is enabled.
const auto GetRendererForm = [&]() -> std::optional<FormData> {
const AutofillDriverRouter& router =
AutofillDriverIOSFactory::FromWebState(web_state_)->router();
std::vector<FormData> renderer_forms =
router.GetRendererForms(form_and_predictions.form_data);
// Find the form to which `field_id` belongs.
auto renderer_forms_it =
base::ranges::find_if(renderer_forms, [field_id](const FormData& form) {
return base::ranges::find(form.fields(), field_id,
&FormFieldData::global_id) !=
form.fields().end();
});
if (renderer_forms_it == renderer_forms.end()) {
return std::nullopt;
}
return *renderer_forms_it;
};
const std::optional<FormData> renderer_form =
base::FeatureList::IsEnabled(
autofill::features::kAutofillAcrossIframesIos)
? GetRendererForm()
: std::move(form_and_predictions.form_data);
if (!renderer_form) {
return {};
}
password_manager::FormDataParser parser;
// The driver id is irrelevant here because it would only be used by password
// manager logic that handles the `PasswordForm` returned by the parser.
parser.set_predictions(password_manager::ConvertToFormPredictions(
/*driver_id=*/0, *renderer_form, form_and_predictions.predictions));
// The parser can use stored usernames to identify a filled username field by
// the value it contains. Here it remains empty.
std::unique_ptr<password_manager::PasswordForm> pw_form = parser.Parse(
*renderer_form, password_manager::FormDataParser::Mode::kFilling,
/*stored_usernames=*/{});
if (!pw_form) {
return {};
}
PasswordFormClassification result{.type = pw_form->GetPasswordFormType()};
if (!pw_form->username_element_renderer_id.is_null()) {
result.username_field = FieldGlobalId(
field_id.frame_token, pw_form->username_element_renderer_id);
}
return result;
}
AutofillSaveCardInfoBarDelegateIOS*
ChromeAutofillClientIOS::GetAutofillSaveCardInfoBarDelegateIOS() {
const auto save_card_infobar = base::ranges::find(
infobar_manager_->infobars(),
infobars::InfoBarDelegate::AUTOFILL_CC_INFOBAR_DELEGATE_MOBILE,
&infobars::InfoBar::GetIdentifier);
return save_card_infobar != infobar_manager_->infobars().cend()
? AutofillSaveCardInfoBarDelegateIOS::FromInfobarDelegate(
(*save_card_infobar)->delegate())
: nullptr;
}
void ChromeAutofillClientIOS::RemoveAutofillSaveCardInfoBar() {
const auto save_card_infobar = base::ranges::find(
infobar_manager_->infobars(),
infobars::InfoBarDelegate::AUTOFILL_CC_INFOBAR_DELEGATE_MOBILE,
&infobars::InfoBar::GetIdentifier);
if (save_card_infobar != infobar_manager_->infobars().cend()) {
infobar_manager_->RemoveInfoBar(*save_card_infobar);
}
}
} // namespace autofill