| // Copyright 2020 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/ui/autofill/address_bubbles_controller.h" |
| |
| #include <array> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/types/optional_util.h" |
| #include "bubble_controller_base.h" |
| #include "chrome/browser/autofill/ui/ui_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/global_features.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/promos/promos_types.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/signin/signin_promo_util.h" |
| #include "chrome/browser/ui/actions/chrome_action_id.h" |
| #include "chrome/browser/ui/autofill/autofill_bubble_handler.h" |
| #include "chrome/browser/ui/autofill/chrome_autofill_client.h" |
| #include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h" |
| #include "chrome/browser/ui/autofill/save_address_bubble_controller.h" |
| #include "chrome/browser/ui/autofill/update_address_bubble_controller.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/promos/ios_promos_utils.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "components/application_locale_storage/application_locale_storage.h" |
| #include "components/autofill/content/browser/content_autofill_client.h" |
| #include "components/autofill/core/browser/data_model/addresses/autofill_profile.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/foundations/autofill_client.h" |
| #include "components/autofill/core/browser/ui/addresses/autofill_address_util.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/signin/public/identity_manager/account_info.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/sync/base/user_selectable_type.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| AutofillBubbleBase* ShowSaveBubble( |
| const AutofillProfile& profile, |
| AutofillClient::SaveAddressBubbleType save_address_bubble_type, |
| content::WebContents* web_contents, |
| bool shown_by_user_gesture, |
| base::WeakPtr<AddressBubbleControllerDelegate> delegate) { |
| auto controller = std::make_unique<SaveAddressBubbleController>( |
| delegate, web_contents, profile, save_address_bubble_type); |
| |
| return BrowserWindow::FindBrowserWindowWithWebContents(web_contents) |
| ->GetAutofillBubbleHandler() |
| ->ShowSaveAddressProfileBubble(web_contents, std::move(controller), |
| shown_by_user_gesture); |
| } |
| |
| AutofillBubbleBase* ShowUpdateBubble( |
| const AutofillProfile& profile, |
| const AutofillProfile& original_profile, |
| content::WebContents* web_contents, |
| bool shown_by_user_gesture, |
| base::WeakPtr<AddressBubbleControllerDelegate> delegate) { |
| auto update_controller = std::make_unique<UpdateAddressBubbleController>( |
| delegate, web_contents, profile, original_profile); |
| return BrowserWindow::FindBrowserWindowWithWebContents(web_contents) |
| ->GetAutofillBubbleHandler() |
| ->ShowUpdateAddressProfileBubble( |
| web_contents, std::move(update_controller), shown_by_user_gesture); |
| } |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| AutofillBubbleBase* ShowSignInPromo(content::WebContents* web_contents, |
| const AutofillProfile& autofill_profile) { |
| // TODO(crbug.com/381390420): Expose the `AutofillBubbleHandler` in |
| // `BrowserWindowInterface` and use that instead. |
| return BrowserWindow::FindBrowserWindowWithWebContents(web_contents) |
| ->GetAutofillBubbleHandler() |
| ->ShowAddressSignInPromo(web_contents, autofill_profile); |
| } |
| #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) |
| |
| } // namespace |
| |
| AddressBubblesController::AddressBubblesController( |
| content::WebContents* web_contents) |
| : AutofillBubbleControllerBase(web_contents), |
| content::WebContentsUserData<AddressBubblesController>(*web_contents), |
| app_locale_(g_browser_process->GetFeatures() |
| ->application_locale_storage() |
| ->Get()) {} |
| |
| AddressBubblesController::~AddressBubblesController() { |
| // `address_profile_save_prompt_callback_` must have been invoked before |
| // destroying the controller to inform the backend of the output of the |
| // save/update flow. It's either invoked upon user action when accepting |
| // or rejecting the flow, or in cases when users ignore it, it's invoked |
| // when the web contents are destroyed. |
| DCHECK(address_profile_save_prompt_callback_.is_null()); |
| } |
| |
| // static |
| void AddressBubblesController::SetUpAndShowSaveOrUpdateAddressBubble( |
| content::WebContents* web_contents, |
| const AutofillProfile& profile, |
| const AutofillProfile* original_profile, |
| AutofillClient::SaveAddressBubbleType save_address_bubble_type, |
| bool user_has_any_profile_saved, |
| AutofillClient::AddressProfileSavePromptCallback callback) { |
| AddressBubblesController::CreateForWebContents(web_contents); |
| auto* controller = AddressBubblesController::FromWebContents(web_contents); |
| bool is_save_bubble = !original_profile; |
| const bool is_migration_to_account = |
| save_address_bubble_type == |
| AutofillClient::SaveAddressBubbleType::kMigrateToAccount; |
| auto show_bubble_view_impl = |
| is_save_bubble |
| // Save address bubble. |
| ? base::BindRepeating(ShowSaveBubble, profile, |
| save_address_bubble_type) |
| // Update address bubble. |
| : base::BindRepeating(ShowUpdateBubble, profile, *original_profile); |
| std::u16string page_action_icon_tooltip = l10n_util::GetStringUTF16( |
| is_save_bubble ? (is_migration_to_account |
| ? IDS_AUTOFILL_ACCOUNT_MIGRATE_ADDRESS_PROMPT_TITLE |
| : IDS_AUTOFILL_SAVE_ADDRESS_PROMPT_TITLE) |
| : IDS_AUTOFILL_UPDATE_ADDRESS_PROMPT_TITLE); |
| |
| controller->SetUpAndShowBubble( |
| std::move(show_bubble_view_impl), std::move(page_action_icon_tooltip), |
| is_migration_to_account, user_has_any_profile_saved, std::move(callback)); |
| } |
| |
| void AddressBubblesController::ShowEditor( |
| const AutofillProfile& address_profile, |
| const std::u16string& title_override, |
| const std::u16string& editor_footer_message, |
| bool is_editing_existing_address) { |
| DoNotShowNextQueuedBubbleGuard guard = DoNotShowNextQueuedBubble(); |
| EditAddressProfileDialogControllerImpl::CreateForWebContents(web_contents()); |
| EditAddressProfileDialogControllerImpl* controller = |
| EditAddressProfileDialogControllerImpl::FromWebContents(web_contents()); |
| controller->OfferEdit( |
| address_profile, title_override, editor_footer_message, |
| is_editing_existing_address, is_migration_to_account_, |
| base::BindOnce(&AddressBubblesController::OnUserDecision, |
| weak_ptr_factory_.GetWeakPtr())); |
| HideBubble(/*initiated_by_bubble_manager=*/false); |
| } |
| |
| void AddressBubblesController::OnUserDecision( |
| AutofillClient::AddressPromptUserDecision decision, |
| base::optional_ref<const AutofillProfile> profile) { |
| if (decision == AutofillClient::AddressPromptUserDecision::kEditDeclined) { |
| // Reopen this bubble if the user canceled editing. |
| shown_by_user_gesture_ = false; |
| QueueOrShowBubble(/*force_show=*/true); |
| return; |
| } |
| if (address_profile_save_prompt_callback_) { |
| std::move(address_profile_save_prompt_callback_).Run(decision, profile); |
| MaybeShowSignInPromo(profile); |
| } |
| |
| if (decision == AutofillClient::AddressPromptUserDecision::kAccepted || |
| decision == AutofillClient::AddressPromptUserDecision::kEditAccepted) { |
| MaybeShowIOSDektopAddressPromo(); |
| } else if (decision == AutofillClient::AddressPromptUserDecision::kDeclined && |
| !user_has_any_profile_saved_ && |
| base::FeatureList::IsEnabled( |
| features::kAutofillAddressUserDeclinedSaveSurvey)) { |
| if (auto* autofill_client = |
| ChromeAutofillClient::FromWebContents(web_contents())) { |
| autofill_client->TriggerDeclinedSaveAddressReasonSurvey(); |
| } |
| } |
| } |
| |
| void AddressBubblesController::OnBubbleClosed() { |
| ResetBubbleViewAndInformBubbleManager(); |
| is_showing_sign_in_promo_ = false; |
| UpdatePageActionIcon(); |
| } |
| |
| void AddressBubblesController::OnIconClicked() { |
| // Don't show the bubble if it's already visible. |
| if (bubble_view()) { |
| return; |
| } |
| shown_by_user_gesture_ = true; |
| QueueOrShowBubble(/*force_show=*/true); |
| } |
| |
| bool AddressBubblesController::IsBubbleActive() const { |
| return !address_profile_save_prompt_callback_.is_null() || |
| is_showing_sign_in_promo_; |
| } |
| |
| std::u16string AddressBubblesController::GetPageActionIconTooltip() const { |
| return page_action_icon_tooltip_; |
| } |
| |
| AutofillBubbleBase* AddressBubblesController::GetBubbleView() const { |
| return bubble_view(); |
| } |
| |
| base::WeakPtr<AddressBubbleControllerDelegate> |
| AddressBubblesController::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void AddressBubblesController::WebContentsDestroyed() { |
| AutofillBubbleControllerBase::WebContentsDestroyed(); |
| |
| OnUserDecision(AutofillClient::AddressPromptUserDecision::kIgnored, |
| std::nullopt); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| std::optional<actions::ActionId> |
| AddressBubblesController::GetActionIdForPageAction() { |
| return kActionShowAddressesBubbleOrPage; |
| } |
| |
| std::optional<std::u16string> |
| AddressBubblesController::GetPageActionTooltipText() { |
| return GetPageActionIconTooltip(); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| void AddressBubblesController::DoShowBubble() { |
| CHECK(!bubble_view()); |
| CHECK(show_bubble_view_callback_); |
| |
| SetBubbleView(*show_bubble_view_callback_.Run( |
| web_contents(), shown_by_user_gesture_, GetWeakPtr())); |
| |
| CHECK(bubble_view()); |
| } |
| |
| BubbleType AddressBubblesController::GetBubbleType() const { |
| return BubbleType::kSaveUpdateAddress; |
| } |
| |
| base::WeakPtr<BubbleControllerBase> |
| AddressBubblesController::GetBubbleControllerBaseWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void AddressBubblesController::SetUpAndShowBubble( |
| ShowBubbleViewCallback show_bubble_view_callback, |
| std::u16string page_action_icon_tooltip, |
| bool is_migration_to_account, |
| bool user_has_any_profile_saved, |
| AutofillClient::AddressProfileSavePromptCallback |
| address_profile_save_prompt_callback) { |
| // Don't show the bubble if it's already visible, and inform the backend. |
| if (bubble_view() || !MaySetUpBubble()) { |
| std::move(address_profile_save_prompt_callback) |
| .Run(AutofillClient::AddressPromptUserDecision::kAutoDeclined, |
| std::nullopt); |
| return; |
| } |
| |
| if (address_profile_save_prompt_callback_) { |
| // If the user closed the bubble of the previous import process using the |
| // "Close" button without making a decision to "Accept" or "Deny" the |
| // prompt, a fallback icon is shown, so the user can get back to the prompt. |
| // In this specific scenario the import process is considered in progress |
| // (since the backend didn't hear back via the callback yet), but hidden. Or |
| // when `bubble_manager_enabled` and the bubble is in the queue to be shown |
| // but timed out. When a second prompt arrives, we finish the previous |
| // import process as "Ignored", before showing the 2nd prompt. |
| std::move(address_profile_save_prompt_callback_) |
| .Run(AutofillClient::AddressPromptUserDecision::kIgnored, std::nullopt); |
| } |
| |
| was_bubble_shown_ = false; |
| |
| SetUpBubble(std::move(show_bubble_view_callback), |
| std::move(page_action_icon_tooltip), is_migration_to_account, |
| user_has_any_profile_saved, |
| std::move(address_profile_save_prompt_callback)); |
| |
| QueueOrShowBubble(); |
| } |
| |
| void AddressBubblesController::SetUpBubble( |
| ShowBubbleViewCallback show_bubble_view_callback, |
| std::u16string page_action_icon_tooltip, |
| bool is_migration_to_account, |
| bool user_has_any_profile_saved, |
| AutofillClient::AddressProfileSavePromptCallback |
| address_profile_save_prompt_callback) { |
| show_bubble_view_callback_ = std::move(show_bubble_view_callback); |
| page_action_icon_tooltip_ = std::move(page_action_icon_tooltip); |
| address_profile_save_prompt_callback_ = |
| std::move(address_profile_save_prompt_callback); |
| shown_by_user_gesture_ = false; |
| is_migration_to_account_ = is_migration_to_account; |
| user_has_any_profile_saved_ = user_has_any_profile_saved; |
| } |
| |
| void AddressBubblesController::MaybeShowIOSDektopAddressPromo() { |
| Browser* browser = |
| BrowserWindow::FindBrowserWindowWithWebContents(web_contents()) |
| ->AsBrowserView() |
| ->browser(); |
| |
| // Verify if user is eligible for iOS promo, and attempt showing if they are. |
| ios_promos_utils::VerifyIOSPromoEligibility(IOSPromoType::kAddress, browser); |
| } |
| |
| void AddressBubblesController::MaybeShowSignInPromo( |
| base::optional_ref<const AutofillProfile> autofill_profile) { |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| // Do nothing if there is no autofill profile or the sign in promo should not |
| // be shown. |
| if (!autofill_profile.has_value() || |
| !signin::ShouldShowAddressSignInPromo( |
| *Profile::FromBrowserContext(web_contents()->GetBrowserContext()), |
| autofill_profile.value())) { |
| return; |
| } |
| |
| DoNotShowNextQueuedBubbleGuard guard = DoNotShowNextQueuedBubble(); |
| |
| // Close the current save bubble. |
| HideBubble(/*initiated_by_bubble_manager=*/false); |
| |
| // Open the bubble with the sign in promo. |
| SetBubbleView(*ShowSignInPromo(web_contents(), autofill_profile.value())); |
| CHECK(bubble_view()); |
| is_showing_sign_in_promo_ = true; |
| UpdatePageActionIcon(); |
| #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(AddressBubblesController); |
| |
| } // namespace autofill |