| // Copyright 2022 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/fast_checkout/fast_checkout_client_impl.h" |
| |
| #include <cmath> |
| |
| #include "base/containers/flat_set.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/uuid.h" |
| #include "chrome/browser/fast_checkout/fast_checkout_accessibility_service_impl.h" |
| #include "chrome/browser/fast_checkout/fast_checkout_capabilities_fetcher_factory.h" |
| #include "chrome/browser/fast_checkout/fast_checkout_delegate_impl.h" |
| #include "chrome/browser/fast_checkout/fast_checkout_personal_data_helper_impl.h" |
| #include "chrome/browser/fast_checkout/fast_checkout_trigger_validator_impl.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/autofill/content/browser/content_autofill_client.h" |
| #include "components/autofill/content/browser/content_autofill_driver.h" |
| #include "components/autofill/core/browser/data_model/autofill_profile.h" |
| #include "components/autofill/core/browser/data_model/credit_card.h" |
| #include "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h" |
| #include "components/autofill/core/browser/payments/payments_autofill_client.h" |
| #include "components/autofill/core/browser/ui/fast_checkout_enums.h" |
| #include "components/autofill/core/common/dense_set.h" |
| #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" |
| #include "components/autofill/core/common/signatures.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| using ::autofill::FastCheckoutRunOutcome; |
| using ::autofill::FastCheckoutTriggerOutcome; |
| using ::autofill::FastCheckoutUIState; |
| |
| constexpr base::TimeDelta kSleepBetweenTriggerFormExtractionCalls = |
| base::Seconds(1); |
| constexpr base::TimeDelta kTimeout = base::Minutes(30); |
| |
| constexpr auto kSupportedFormTypes = base::MakeFixedFlatSet<autofill::FormType>( |
| {autofill::FormType::kAddressForm, autofill::FormType::kCreditCardForm}); |
| |
| constexpr auto kAddressFieldTypes = |
| base::MakeFixedFlatSet<autofill::FieldTypeGroup>( |
| {autofill::FieldTypeGroup::kName, autofill::FieldTypeGroup::kEmail, |
| autofill::FieldTypeGroup::kPhone, autofill::FieldTypeGroup::kAddress}); |
| |
| bool IsVisibleTextField(const autofill::AutofillField& field) { |
| return field.IsFocusable() && field.IsTextInputElement(); |
| } |
| |
| autofill::AutofillField* GetFieldToFill( |
| const std::vector<std::unique_ptr<autofill::AutofillField>>& fields, |
| bool is_credit_card_form) { |
| for (const std::unique_ptr<autofill::AutofillField>& field : fields) { |
| if (IsVisibleTextField(*field) && field->IsEmpty() && |
| ((!is_credit_card_form && |
| kAddressFieldTypes.contains(field->Type().group())) || |
| (is_credit_card_form && |
| field->Type().GetStorableType() == autofill::CREDIT_CARD_NUMBER))) { |
| return field.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| bool IsNameOrAddress(autofill::FieldTypeGroup type_group) { |
| return type_group == autofill::FieldTypeGroup::kName || |
| type_group == autofill::FieldTypeGroup::kAddress; |
| } |
| |
| // Returns `true` if `form` is considered an address form containing only an |
| // `email` field but no `name` or `address` fields. |
| bool IsEmailForm(const autofill::FormStructure& form) { |
| // `kAddressForm` includes email fields. |
| bool is_address_form = |
| form.GetFormTypes().contains(autofill::FormType::kAddressForm); |
| bool has_name_or_address_field = base::ranges::any_of( |
| form.fields().begin(), form.fields().end(), |
| [](const std::unique_ptr<autofill::AutofillField>& field) { |
| autofill::FieldTypeGroup type_group = field->Type().group(); |
| return IsNameOrAddress(type_group) && IsVisibleTextField(*field); |
| }); |
| bool has_focusable_email_field = base::ranges::any_of( |
| form.fields().begin(), form.fields().end(), |
| [](const std::unique_ptr<autofill::AutofillField>& field) { |
| return field->Type().group() == autofill::FieldTypeGroup::kEmail && |
| IsVisibleTextField(*field); |
| }); |
| return is_address_form && has_focusable_email_field && |
| !has_name_or_address_field; |
| } |
| |
| // Returns `true` if `form_signature`'s form is in `forms` and is an email form. |
| bool ContainsEmailFormWithSignature( |
| const std::map<autofill::FormGlobalId, |
| std::unique_ptr<autofill::FormStructure>>& forms, |
| autofill::FormSignature form_signature) { |
| for (auto& [_, form] : forms) { |
| // It is possible to have multiple forms with the same form signature on the |
| // same page where only some are visible to the user. An example could be |
| // shipping and billing address forms. For that reason the `IsEmailForm` |
| // check must not be returned directly to avoid a premature return as we |
| // don't have any control over the order of `forms`. |
| if (form->form_signature() == form_signature && IsEmailForm(*form)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| FastCheckoutDelegateImpl* GetDelegate(autofill::AutofillManager& manager) { |
| auto& bam = static_cast<autofill::BrowserAutofillManager&>(manager); |
| return static_cast<FastCheckoutDelegateImpl*>(bam.fast_checkout_delegate()); |
| } |
| } // namespace |
| |
| // No virtual functions of `client` must be called in the constructor. |
| FastCheckoutClientImpl::FastCheckoutClientImpl( |
| autofill::ContentAutofillClient* client) |
| : autofill_client_(client), |
| fetcher_(FastCheckoutCapabilitiesFetcherFactory::GetForBrowserContext( |
| client->GetWebContents().GetBrowserContext())), |
| personal_data_helper_( |
| std::make_unique<FastCheckoutPersonalDataHelperImpl>( |
| &client->GetWebContents())), |
| trigger_validator_(std::make_unique<FastCheckoutTriggerValidatorImpl>( |
| autofill_client_, |
| fetcher_, |
| personal_data_helper_.get())), |
| accessibility_service_( |
| std::make_unique<FastCheckoutAccessibilityServiceImpl>()), |
| keyboard_suppressor_( |
| client, |
| base::BindRepeating([](autofill::AutofillManager& manager) { |
| return GetDelegate(manager) && |
| GetDelegate(manager)->IsShowingFastCheckoutUI(); |
| }), |
| base::BindRepeating([](autofill::AutofillManager& manager, |
| autofill::FormGlobalId form, |
| autofill::FieldGlobalId field, |
| const autofill::FormData& form_data) { |
| return GetDelegate(manager) && |
| GetDelegate(manager)->IntendsToShowFastCheckout( |
| manager, form, field, form_data); |
| }), |
| base::Seconds(1)) { |
| driver_factory_observation_.Observe( |
| autofill_client_->GetAutofillDriverFactory()); |
| } |
| |
| FastCheckoutClientImpl::~FastCheckoutClientImpl() = default; |
| |
| void FastCheckoutClientImpl::OnContentAutofillDriverFactoryDestroyed( |
| autofill::ContentAutofillDriverFactory& factory) { |
| driver_factory_observation_.Reset(); |
| } |
| |
| void FastCheckoutClientImpl::OnContentAutofillDriverCreated( |
| autofill::ContentAutofillDriverFactory& factory, |
| autofill::ContentAutofillDriver& driver) { |
| auto& manager = static_cast<autofill::BrowserAutofillManager&>( |
| driver.GetAutofillManager()); |
| manager.set_fast_checkout_delegate(std::make_unique<FastCheckoutDelegateImpl>( |
| &autofill_client_->GetWebContents(), this, &manager)); |
| } |
| |
| bool FastCheckoutClientImpl::TryToStart( |
| const GURL& url, |
| const autofill::FormData& form, |
| const autofill::FormFieldData& field, |
| base::WeakPtr<autofill::AutofillManager> autofill_manager) { |
| if (!keyboard_suppressor_.is_suppressing()) { |
| return false; |
| } |
| |
| if (!autofill_manager) { |
| return false; |
| } |
| |
| FastCheckoutTriggerOutcome trigger_outcome = trigger_validator_->ShouldRun( |
| form, field, fast_checkout_ui_state_, is_running_, *autofill_manager); |
| |
| if (trigger_outcome != FastCheckoutTriggerOutcome::kSuccess) { |
| return false; |
| } |
| |
| autofill_manager_ = autofill_manager; |
| origin_ = url::Origin::Create(url); |
| is_running_ = true; |
| personal_data_manager_observation_.Observe( |
| personal_data_helper_->GetPersonalDataManager()); |
| autofill_manager_observation_.Observe(autofill_manager_.get()); |
| run_id_ = |
| base::HashMetricName(base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| |
| SetFormsToFill(); |
| |
| fast_checkout_controller_ = CreateFastCheckoutController(); |
| ShowFastCheckoutUI(); |
| |
| fast_checkout_ui_state_ = FastCheckoutUIState::kIsShowing; |
| autofill_client_->HideAutofillSuggestions( |
| autofill::SuggestionHidingReason::kOverlappingWithFastCheckoutSurface); |
| |
| return true; |
| } |
| |
| void FastCheckoutClientImpl::ShowFastCheckoutUI() { |
| fast_checkout_controller_->Show( |
| personal_data_helper_->GetProfilesToSuggest(), |
| personal_data_helper_->GetCreditCardsToSuggest()); |
| } |
| |
| void FastCheckoutClientImpl::OnRunComplete(FastCheckoutRunOutcome run_outcome, |
| bool allow_further_runs) { |
| ukm::builders::Autofill_FastCheckoutRunOutcome run_outcome_builder( |
| autofill_client_->GetWebContents() |
| .GetPrimaryMainFrame() |
| ->GetPageUkmSourceId()); |
| run_outcome_builder.SetRunOutcome(static_cast<int64_t>(run_outcome)); |
| run_outcome_builder.SetRunId(run_id_); |
| run_outcome_builder.Record(ukm::UkmRecorder::Get()); |
| |
| if (autofill_manager_) { |
| for (auto [form_id, filling_state] : form_filling_states_) { |
| autofill::FormSignature form_signature = form_id.first; |
| autofill::DenseSet<autofill::FormType> form_types; |
| for (auto& [_, form] : autofill_manager_->form_structures()) { |
| if (form->form_signature() == form_signature) { |
| form_types = form->GetFormTypes(); |
| break; |
| } |
| } |
| ukm::builders::Autofill_FastCheckoutFormStatus form_status_builder( |
| autofill_client_->GetWebContents() |
| .GetPrimaryMainFrame() |
| ->GetPageUkmSourceId()); |
| form_status_builder.SetFilled(filling_state == FillingState::kFilled); |
| form_status_builder.SetFormSignature( |
| autofill::HashFormSignature(form_signature)); |
| form_status_builder.SetRunId(run_id_); |
| form_status_builder.SetFormTypes( |
| autofill::AutofillMetrics::FormTypesToBitVector(form_types)); |
| form_status_builder.Record(ukm::UkmRecorder::Get()); |
| } |
| } |
| |
| InternalStop(allow_further_runs); |
| } |
| |
| void FastCheckoutClientImpl::InternalStop(bool allow_further_runs) { |
| // `OnHidden` is not called if the bottom sheet never managed to show, |
| // e.g. due to a failed onboarding. This ensures that keyboard suppression |
| // stops. |
| keyboard_suppressor_.Unsuppress(); |
| |
| // Reset run related state. |
| is_running_ = false; |
| form_filling_states_.clear(); |
| form_signatures_to_fill_.clear(); |
| selected_autofill_profile_guid_ = std::nullopt; |
| selected_credit_card_id_ = std::nullopt; |
| timeout_timer_.AbandonAndStop(); |
| credit_card_form_global_id_ = std::nullopt; |
| run_id_ = 0; |
| // Reset UI related state. |
| fast_checkout_controller_.reset(); |
| // Reset personal data manager observation. |
| personal_data_manager_observation_.Reset(); |
| // Reset `autofill_manager_` and related objects. |
| form_extraction_timer_.AbandonAndStop(); |
| autofill_manager_observation_.Reset(); |
| autofill_manager_.reset(); |
| |
| if (!allow_further_runs) { |
| fast_checkout_ui_state_ = FastCheckoutUIState::kWasShown; |
| } else { |
| fast_checkout_ui_state_ = FastCheckoutUIState::kNotShownYet; |
| } |
| } |
| |
| void FastCheckoutClientImpl::Stop(bool allow_further_runs) { |
| InternalStop(allow_further_runs || !IsShowing()); |
| } |
| |
| bool FastCheckoutClientImpl::IsShowing() const { |
| return fast_checkout_ui_state_ == FastCheckoutUIState::kIsShowing; |
| } |
| |
| bool FastCheckoutClientImpl::IsRunning() const { |
| return is_running_; |
| } |
| |
| std::unique_ptr<FastCheckoutController> |
| FastCheckoutClientImpl::CreateFastCheckoutController() { |
| return std::make_unique<FastCheckoutControllerImpl>( |
| &autofill_client_->GetWebContents(), this); |
| } |
| |
| void FastCheckoutClientImpl::OnHidden() { |
| fast_checkout_ui_state_ = FastCheckoutUIState::kWasShown; |
| keyboard_suppressor_.Unsuppress(); |
| } |
| |
| void FastCheckoutClientImpl::OnOptionsSelected( |
| std::unique_ptr<autofill::AutofillProfile> selected_profile, |
| std::unique_ptr<autofill::CreditCard> selected_credit_card) { |
| OnHidden(); |
| selected_autofill_profile_guid_ = selected_profile->guid(); |
| if (autofill::CreditCard::IsLocalCard(selected_credit_card.get())) { |
| selected_credit_card_id_ = selected_credit_card->guid(); |
| selected_credit_card_is_local_ = true; |
| } else { |
| selected_credit_card_id_ = selected_credit_card->server_id(); |
| selected_credit_card_is_local_ = false; |
| } |
| timeout_timer_.Start(FROM_HERE, kTimeout, |
| base::BindOnce(&FastCheckoutClientImpl::OnRunComplete, |
| weak_ptr_factory_.GetWeakPtr(), |
| FastCheckoutRunOutcome::kTimeout, |
| /*allow_further_runs=*/true)); |
| TryToFillForms(); |
| autofill_manager_->TriggerFormExtractionInAllFrames( |
| base::BindOnce(&FastCheckoutClientImpl::OnTriggerFormExtractionFinished, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void FastCheckoutClientImpl::SetFormsToFill() { |
| if (!fetcher_) { |
| return; |
| } |
| DCHECK(form_filling_states_.empty()); |
| DCHECK(form_signatures_to_fill_.empty()); |
| form_signatures_to_fill_ = fetcher_->GetFormsToFill(origin_); |
| } |
| |
| void FastCheckoutClientImpl::OnDismiss() { |
| OnRunComplete(FastCheckoutRunOutcome::kBottomsheetDismissed, |
| /*allow_further_runs=*/false); |
| } |
| |
| void FastCheckoutClientImpl::OnPersonalDataChanged() { |
| if (!IsShowing()) { |
| return; |
| } |
| |
| if (trigger_validator_->HasValidPersonalData() != |
| FastCheckoutTriggerOutcome::kSuccess) { |
| OnRunComplete(FastCheckoutRunOutcome::kInvalidPersonalData, |
| /*allow_further_runs=*/false); |
| } else { |
| ShowFastCheckoutUI(); |
| } |
| } |
| |
| bool FastCheckoutClientImpl::AllFormsAreFilled() const { |
| return base::ranges::all_of(form_filling_states_.begin(), |
| form_filling_states_.end(), |
| [](const auto& pair) { |
| return pair.second == FillingState::kFilled; |
| }) && |
| base::ranges::all_of( |
| form_signatures_to_fill_.begin(), form_signatures_to_fill_.end(), |
| [&](autofill::FormSignature form_signature) { |
| return form_filling_states_.contains(std::make_pair( |
| form_signature, autofill::FormType::kAddressForm)) || |
| form_filling_states_.contains(std::make_pair( |
| form_signature, autofill::FormType::kCreditCardForm)); |
| }); |
| } |
| |
| bool FastCheckoutClientImpl::IsFilling() const { |
| return IsRunning() && selected_autofill_profile_guid_ && |
| selected_credit_card_id_; |
| } |
| |
| void FastCheckoutClientImpl::OnAfterLoadedServerPredictions( |
| autofill::AutofillManager& manager) { |
| TryToFillForms(); |
| } |
| |
| void FastCheckoutClientImpl::OnTriggerFormExtractionFinished(bool success) { |
| if (!form_extraction_timer_.IsRunning()) { |
| // Trigger form (re-)extraction in all frames continuously until the run |
| // stops. That will eventually trigger this |
| // (`OnAfterLoadedServerPredictions()`) method. |
| form_extraction_timer_.Start( |
| FROM_HERE, kSleepBetweenTriggerFormExtractionCalls, |
| base::BindOnce( |
| &autofill::AutofillManager::TriggerFormExtractionInAllFrames, |
| autofill_manager_, |
| base::BindOnce( |
| &FastCheckoutClientImpl::OnTriggerFormExtractionFinished, |
| weak_ptr_factory_.GetWeakPtr()))); |
| } |
| } |
| |
| void FastCheckoutClientImpl::TryToFillForms() { |
| if (!IsFilling()) { |
| return; |
| } |
| SetFormFillingStates(); |
| for (const auto& [form_global_id, form] : |
| autofill_manager_->form_structures()) { |
| if (ShouldFillForm(*form, autofill::FormType::kAddressForm)) { |
| autofill::AutofillField* field = |
| GetFieldToFill(form->fields(), /*is_credit_card_form=*/false); |
| autofill::AutofillProfile* autofill_profile = |
| GetSelectedAutofillProfile(); |
| if (field && autofill_profile) { |
| form_filling_states_[std::make_pair(form->form_signature(), |
| autofill::FormType::kAddressForm)] = |
| FillingState::kFilling; |
| auto* bam = static_cast<autofill::BrowserAutofillManager*>( |
| autofill_manager_.get()); |
| bam->SetFastCheckoutRunId(autofill::FieldTypeGroup::kAddress, run_id_); |
| bam->FillOrPreviewProfileForm( |
| autofill::mojom::ActionPersistence::kFill, form->ToFormData(), |
| *field, *autofill_profile, |
| autofill::AutofillTriggerDetails( |
| autofill::AutofillTriggerSource::kFastCheckout)); |
| } |
| } |
| |
| if (ShouldFillForm(*form, autofill::FormType::kCreditCardForm)) { |
| autofill::AutofillField* field = |
| GetFieldToFill(form->fields(), /*is_credit_card_form=*/true); |
| autofill::CreditCard* credit_card = GetSelectedCreditCard(); |
| if (field && !credit_card_form_global_id_ && credit_card) { |
| if (autofill::CreditCard::IsLocalCard(credit_card)) { |
| FillCreditCardForm(*form, *field, *credit_card, u""); |
| } else { |
| autofill::CreditCardCvcAuthenticator& cvc_authenticator = |
| autofill_client_->GetPaymentsAutofillClient() |
| ->GetCvcAuthenticator(); |
| credit_card_form_global_id_ = form_global_id; |
| cvc_authenticator.GetFullCardRequest()->GetFullCard( |
| *credit_card, |
| autofill::AutofillClient::UnmaskCardReason::kAutofill, |
| weak_ptr_factory_.GetWeakPtr(), |
| cvc_authenticator.GetAsFullCardRequestUIDelegate(), |
| autofill_client_->GetLastCommittedPrimaryMainFrameOrigin()); |
| } |
| } |
| } |
| } |
| } |
| |
| void FastCheckoutClientImpl::FillCreditCardForm( |
| const autofill::FormStructure& form, |
| const autofill::FormFieldData& field, |
| const autofill::CreditCard& credit_card, |
| const std::u16string& cvc) { |
| form_filling_states_[std::make_pair(form.form_signature(), |
| autofill::FormType::kCreditCardForm)] = |
| FillingState::kFilling; |
| auto* bam = |
| static_cast<autofill::BrowserAutofillManager*>(autofill_manager_.get()); |
| bam->SetFastCheckoutRunId(autofill::FieldTypeGroup::kCreditCard, run_id_); |
| bam->FillOrPreviewCreditCardForm( |
| autofill::mojom::ActionPersistence::kFill, form.ToFormData(), field, |
| credit_card, cvc, |
| {.trigger_source = autofill::AutofillTriggerSource::kFastCheckout}); |
| } |
| |
| autofill::AutofillProfile* |
| FastCheckoutClientImpl::GetSelectedAutofillProfile() { |
| autofill::AutofillProfile* autofill_profile = |
| personal_data_helper_->GetPersonalDataManager() |
| ->address_data_manager() |
| .GetProfileByGUID(selected_autofill_profile_guid_.value()); |
| if (!autofill_profile) { |
| OnRunComplete(FastCheckoutRunOutcome::kAutofillProfileDeleted); |
| } |
| return autofill_profile; |
| } |
| |
| autofill::CreditCard* FastCheckoutClientImpl::GetSelectedCreditCard() { |
| autofill::CreditCard* credit_card = nullptr; |
| if (selected_credit_card_is_local_) { |
| credit_card = |
| personal_data_helper_->GetPersonalDataManager()->GetCreditCardByGUID( |
| selected_credit_card_id_.value()); |
| } else { |
| credit_card = |
| personal_data_helper_->GetPersonalDataManager() |
| ->GetCreditCardByServerId(selected_credit_card_id_.value()); |
| } |
| if (!credit_card) { |
| OnRunComplete(FastCheckoutRunOutcome::kCreditCardDeleted); |
| } |
| return credit_card; |
| } |
| |
| void FastCheckoutClientImpl::SetFormFillingStates() { |
| for (const auto& [_, form] : autofill_manager_->form_structures()) { |
| // Only attempt to fill forms that were provided by the |
| // `FastCheckoutCapabilitiesFetcher`. |
| if (!form_signatures_to_fill_.contains(form->form_signature())) { |
| continue; |
| } |
| autofill::DenseSet<autofill::FormType> form_types = form->GetFormTypes(); |
| for (autofill::FormType form_type : kSupportedFormTypes) { |
| // Only attempt to fill forms if they match `form_type`. |
| if (!form_types.contains(form_type)) { |
| continue; |
| } |
| auto form_id = std::make_pair(form->form_signature(), form_type); |
| if (!form_filling_states_.contains(form_id)) { |
| form_filling_states_[form_id] = FillingState::kNotFilled; |
| } |
| } |
| } |
| } |
| |
| void FastCheckoutClientImpl::OnFullCardRequestSucceeded( |
| const autofill::payments::FullCardRequest& full_card_request, |
| const autofill::CreditCard& card, |
| const std::u16string& cvc) { |
| if (!IsFilling() || !credit_card_form_global_id_) { |
| return; |
| } |
| if (!autofill_manager_->form_structures().contains( |
| credit_card_form_global_id_.value())) { |
| credit_card_form_global_id_ = std::nullopt; |
| return; |
| } |
| const std::unique_ptr<autofill::FormStructure>& form = |
| autofill_manager_->form_structures().at( |
| credit_card_form_global_id_.value()); |
| if (autofill::AutofillField* field = |
| GetFieldToFill(form->fields(), /*is_credit_card_form=*/true)) { |
| FillCreditCardForm(*form, *field, card, cvc); |
| } |
| credit_card_form_global_id_ = std::nullopt; |
| } |
| |
| void FastCheckoutClientImpl::OnFullCardRequestFailed( |
| autofill::CreditCard::RecordType card_type, |
| autofill::payments::FullCardRequest::FailureType failure_type) { |
| if (!IsFilling() || !credit_card_form_global_id_) { |
| return; |
| } |
| if (failure_type == |
| autofill::payments::FullCardRequest::FailureType::PROMPT_CLOSED) { |
| OnRunComplete(FastCheckoutRunOutcome::kCvcPopupClosed, |
| /*allow_further_runs=*/false); |
| } else { |
| OnRunComplete(FastCheckoutRunOutcome::kCvcPopupError, |
| /*allow_further_runs=*/false); |
| } |
| } |
| |
| void FastCheckoutClientImpl::OnAfterDidFillAutofillFormData( |
| autofill::AutofillManager& manager, |
| autofill::FormGlobalId form_id) { |
| if (!IsFilling()) { |
| return; |
| } |
| UpdateFillingStates(); |
| if (AllFormsAreFilled()) { |
| OnRunComplete(FastCheckoutRunOutcome::kSuccess, |
| /*allow_further_runs=*/false); |
| } |
| } |
| |
| void FastCheckoutClientImpl::UpdateFillingStates() { |
| for (auto& [form_id, filling_state] : form_filling_states_) { |
| const auto& [form_signature, form_type] = form_id; |
| if (form_type == autofill::FormType::kAddressForm && |
| filling_state == FillingState::kFilling) { |
| // Assume that if `OnAfterDidFillAutofillFormData()` is called while |
| // `this` is in filling mode and there's an address form in `kFilling` |
| // state that it got filled. |
| filling_state = FillingState::kFilled; |
| A11yAnnounce(form_signature, /*is_credit_card_form=*/false); |
| } else if (form_type == autofill::FormType::kCreditCardForm) { |
| auto address_form_id = |
| std::make_pair(form_signature, autofill::FormType::kAddressForm); |
| if (form_filling_states_.contains(address_form_id) && |
| form_filling_states_[address_form_id] == FillingState::kFilling) { |
| // Assume that the address part was filled first if the corresponding |
| // form is both an address and a credit card form. |
| continue; |
| } else if (filling_state == FillingState::kFilling) { |
| // Assume that if `OnAfterDidFillAutofillFormData()` is called while |
| // `this` is in filling mode and there's a credit card form in |
| // `kFilling` state - while no address form of the same signature is in |
| // `kFilling` state - that it got filled. |
| filling_state = FillingState::kFilled; |
| A11yAnnounce(form_signature, /*is_credit_card_form=*/true); |
| } |
| } |
| } |
| } |
| |
| void FastCheckoutClientImpl::A11yAnnounce( |
| autofill::FormSignature form_signature, |
| bool is_credit_card_form) { |
| if (is_credit_card_form) { |
| if (autofill::CreditCard* credit_card = GetSelectedCreditCard()) { |
| accessibility_service_->Announce(l10n_util::GetStringFUTF16( |
| IDS_FAST_CHECKOUT_A11Y_CREDIT_CARD_FORM_FILLED, |
| credit_card->HasNonEmptyValidNickname() |
| ? credit_card->nickname() |
| : credit_card->NetworkAndLastFourDigits())); |
| } |
| return; |
| } |
| |
| if (ContainsEmailFormWithSignature(autofill_manager_->form_structures(), |
| form_signature)) { |
| accessibility_service_->Announce( |
| l10n_util::GetStringUTF16(IDS_FAST_CHECKOUT_A11Y_EMAIL_FILLED)); |
| } else if (autofill::AutofillProfile* autofill_profile = |
| GetSelectedAutofillProfile()) { |
| accessibility_service_->Announce(l10n_util::GetStringFUTF16( |
| IDS_FAST_CHECKOUT_A11Y_ADDRESS_FORM_FILLED, |
| base::UTF8ToUTF16(autofill_profile->profile_label()))); |
| } |
| } |
| |
| void FastCheckoutClientImpl::OnAutofillManagerDestroyed( |
| autofill::AutofillManager& manager) { |
| if (IsRunning()) { |
| if (autofill_client_->GetWebContents().IsBeingDestroyed()) { |
| OnRunComplete(FastCheckoutRunOutcome::kTabClosed); |
| } else { |
| OnRunComplete(FastCheckoutRunOutcome::kAutofillManagerDestroyed); |
| } |
| return; |
| } |
| InternalStop(/*allow_further_runs=*/true); |
| } |
| |
| void FastCheckoutClientImpl::OnAutofillManagerReset( |
| autofill::AutofillManager& manager) { |
| if (IsShowing()) { |
| OnRunComplete(FastCheckoutRunOutcome::kNavigationWhileBottomsheetWasShown); |
| } else { |
| OnRunComplete(FastCheckoutRunOutcome::kPageRefreshed); |
| } |
| } |
| |
| bool FastCheckoutClientImpl::ShouldFillForm( |
| const autofill::FormStructure& form, |
| autofill::FormType expected_form_type) const { |
| // Only attempt to fill forms that were provided by the |
| // `FastCheckoutCapabilitiesFetcher`. |
| if (!form_signatures_to_fill_.contains(form.form_signature())) { |
| return false; |
| } |
| // Only attempt to fill forms if they match `expected_form_type`. |
| if (!form.GetFormTypes().contains(expected_form_type)) { |
| return false; |
| } |
| // Attempt to fill forms once only. |
| return form_filling_states_.at( |
| std::make_pair(form.form_signature(), expected_form_type)) == |
| FillingState::kNotFilled; |
| } |
| |
| void FastCheckoutClientImpl::OnNavigation(const GURL& url, |
| bool is_cart_or_checkout_url) { |
| if (!IsRunning()) { |
| fast_checkout_ui_state_ = FastCheckoutUIState::kNotShownYet; |
| return; |
| } |
| if (url::Origin::Create(url) != origin_) { |
| OnRunComplete(FastCheckoutRunOutcome::kOriginChange); |
| } else if (!is_cart_or_checkout_url) { |
| OnRunComplete(FastCheckoutRunOutcome::kNonCheckoutPage); |
| } |
| } |
| |
| FastCheckoutTriggerOutcome FastCheckoutClientImpl::CanRun( |
| const autofill::FormData& form, |
| const autofill::FormFieldData& field, |
| const autofill::AutofillManager& autofill_manager) const { |
| return trigger_validator_->ShouldRun(form, field, fast_checkout_ui_state_, |
| is_running_, autofill_manager); |
| } |
| |
| bool FastCheckoutClientImpl::IsNotShownYet() const { |
| return fast_checkout_ui_state_ == FastCheckoutUIState::kNotShownYet; |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(FastCheckoutClientImpl); |