| // 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/functional/bind.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 "chrome/browser/autofill/personal_data_manager_factory.h" |
| #include "chrome/browser/autofill/ui/ui_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/ui/autofill/add_new_address_bubble_controller.h" |
| #include "chrome/browser/ui/autofill/autofill_bubble_handler.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/grit/theme_resources.h" |
| #include "components/autofill/content/browser/content_autofill_client.h" |
| #include "components/autofill/core/browser/autofill_address_util.h" |
| #include "components/autofill/core/browser/data_model/autofill_profile.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill/core/browser/personal_data_manager.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, |
| bool is_migration_to_account, |
| content::WebContents* web_contents, |
| bool shown_by_user_gesture, |
| base::WeakPtr<AddressBubbleControllerDelegate> delegate) { |
| auto controller = std::make_unique<SaveAddressBubbleController>( |
| delegate, web_contents, profile, is_migration_to_account); |
| return chrome::FindBrowserWithTab(web_contents) |
| ->window() |
| ->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 chrome::FindBrowserWithTab(web_contents) |
| ->window() |
| ->GetAutofillBubbleHandler() |
| ->ShowUpdateAddressProfileBubble( |
| web_contents, std::move(update_controller), shown_by_user_gesture); |
| } |
| |
| AutofillBubbleBase* ShowAddNewAddressBubble( |
| content::WebContents* web_contents, |
| bool shown_by_user_gesture, |
| base::WeakPtr<AddressBubbleControllerDelegate> delegate) { |
| auto controller = |
| std::make_unique<AddNewAddressBubbleController>(web_contents, delegate); |
| return chrome::FindBrowserWithTab(web_contents) |
| ->window() |
| ->GetAutofillBubbleHandler() |
| ->ShowAddNewAddressProfileBubble(web_contents, std::move(controller), |
| shown_by_user_gesture); |
| } |
| } // namespace |
| |
| AddressBubblesController::AddressBubblesController( |
| content::WebContents* web_contents) |
| : AutofillBubbleControllerBase(web_contents), |
| content::WebContentsUserData<AddressBubblesController>(*web_contents), |
| app_locale_(g_browser_process->GetApplicationLocale()) {} |
| |
| 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::SaveAddressProfilePromptOptions options, |
| AutofillClient::AddressProfileSavePromptCallback callback) { |
| AddressBubblesController::CreateForWebContents(web_contents); |
| auto* controller = AddressBubblesController::FromWebContents(web_contents); |
| bool is_save_bubble = !original_profile; |
| auto show_bubble_view_impl = |
| is_save_bubble |
| // Save address bubble. |
| ? base::BindRepeating(ShowSaveBubble, profile, |
| options.is_migration_to_account) |
| // Update address bubble. |
| : base::BindRepeating(ShowUpdateBubble, profile, *original_profile); |
| std::u16string page_action_icon_tootip = l10n_util::GetStringUTF16( |
| is_save_bubble ? (options.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_tootip), options, |
| std::move(callback)); |
| } |
| |
| // static |
| void AddressBubblesController::SetUpAndShowAddNewAddressBubble( |
| content::WebContents* web_contents, |
| AutofillClient::AddressProfileSavePromptCallback callback) { |
| AddressBubblesController::CreateForWebContents(web_contents); |
| auto* controller = AddressBubblesController::FromWebContents(web_contents); |
| std::u16string page_action_icon_tootip = |
| l10n_util::GetStringUTF16(IDS_AUTOFILL_ADD_NEW_ADDRESS_PROMPT_TITLE); |
| |
| controller->SetUpAndShowBubble(base::BindRepeating(ShowAddNewAddressBubble), |
| std::move(page_action_icon_tootip), {}, |
| 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) { |
| 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(); |
| } |
| |
| 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; |
| Show(); |
| return; |
| } |
| if (address_profile_save_prompt_callback_) { |
| std::move(address_profile_save_prompt_callback_).Run(decision, profile); |
| } |
| } |
| |
| void AddressBubblesController::OnBubbleClosed() { |
| set_bubble_view(nullptr); |
| UpdatePageActionIcon(); |
| } |
| |
| void AddressBubblesController::OnPageActionIconClicked() { |
| // Don't show the bubble if it's already visible. |
| if (bubble_view()) { |
| return; |
| } |
| shown_by_user_gesture_ = true; |
| Show(); |
| } |
| |
| bool AddressBubblesController::IsBubbleActive() const { |
| return !address_profile_save_prompt_callback_.is_null(); |
| } |
| |
| std::u16string AddressBubblesController::GetPageActionIconTootip() const { |
| return page_action_icon_tootip_; |
| } |
| |
| 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); |
| } |
| |
| PageActionIconType AddressBubblesController::GetPageActionIconType() { |
| return PageActionIconType::kAutofillAddress; |
| } |
| |
| void AddressBubblesController::DoShowBubble() { |
| CHECK(!bubble_view()); |
| CHECK(show_bubble_view_callback_); |
| |
| set_bubble_view(show_bubble_view_callback_.Run( |
| web_contents(), shown_by_user_gesture_, GetWeakPtr())); |
| |
| CHECK(bubble_view()); |
| } |
| |
| void AddressBubblesController::SetUpAndShowBubble( |
| ShowBubbleViewCallback show_bubble_view_callback, |
| std::u16string page_action_icon_tootip, |
| AutofillClient::SaveAddressProfilePromptOptions options, |
| AutofillClient::AddressProfileSavePromptCallback |
| address_profile_save_prompt_callback) { |
| // Don't show the bubble if it's already visible, and inform the backend. |
| if (bubble_view()) { |
| std::move(address_profile_save_prompt_callback) |
| .Run(AutofillClient::AddressPromptUserDecision::kAutoDeclined, |
| std::nullopt); |
| return; |
| } |
| // 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. When a second |
| // prompt arrives, we finish the previous import process as "Ignored", before |
| // showing the 2nd prompt. |
| if (address_profile_save_prompt_callback_) { |
| std::move(address_profile_save_prompt_callback_) |
| .Run(AutofillClient::AddressPromptUserDecision::kIgnored, std::nullopt); |
| } |
| |
| show_bubble_view_callback_ = std::move(show_bubble_view_callback); |
| page_action_icon_tootip_ = std::move(page_action_icon_tootip); |
| address_profile_save_prompt_callback_ = |
| std::move(address_profile_save_prompt_callback); |
| shown_by_user_gesture_ = false; |
| is_migration_to_account_ = options.is_migration_to_account; |
| if (options.show_prompt) { |
| Show(); |
| } |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(AddressBubblesController); |
| |
| } // namespace autofill |