| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/autofill/manual_filling_controller_impl.h" |
| |
| #include <numeric> |
| #include <utility> |
| |
| #include "base/callback.h" |
| #include "base/containers/fixed_flat_set.h" |
| #include "base/feature_list.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/trace_event/memory_allocator_dump.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "base/trace_event/memory_usage_estimator.h" |
| #include "base/trace_event/process_memory_dump.h" |
| #include "chrome/browser/autofill/address_accessory_controller.h" |
| #include "chrome/browser/autofill/credit_card_accessory_controller.h" |
| #include "chrome/browser/password_manager/android/password_accessory_controller.h" |
| #include "chrome/browser/password_manager/android/password_accessory_metrics_util.h" |
| #include "chrome/browser/password_manager/chrome_password_manager_client.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/autofill/core/browser/ui/accessory_sheet_data.h" |
| #include "components/autofill/core/browser/ui/accessory_sheet_enums.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/autofill_payments_features.h" |
| #include "components/autofill/core/common/autofill_util.h" |
| #include "components/keyed_service/core/service_access_type.h" |
| #include "components/password_manager/core/browser/credential_cache.h" |
| #include "components/password_manager/core/common/password_manager_features.h" |
| #include "content/public/browser/web_contents.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| using autofill::AccessoryAction; |
| using autofill::AccessorySheetData; |
| using autofill::AccessoryTabType; |
| using autofill::AddressAccessoryController; |
| using autofill::CreditCardAccessoryController; |
| using autofill::mojom::FocusedFieldType; |
| |
| using FillingSource = ManualFillingController::FillingSource; |
| |
| namespace { |
| |
| constexpr auto kAllowedFillingSources = base::MakeFixedFlatSet<FillingSource>( |
| {FillingSource::PASSWORD_FALLBACKS, FillingSource::CREDIT_CARD_FALLBACKS, |
| FillingSource::ADDRESS_FALLBACKS}); |
| |
| FillingSource GetSourceForTabType(const AccessorySheetData& accessory_sheet) { |
| switch (accessory_sheet.get_sheet_type()) { |
| case AccessoryTabType::PASSWORDS: |
| return FillingSource::PASSWORD_FALLBACKS; |
| case AccessoryTabType::CREDIT_CARDS: |
| return FillingSource::CREDIT_CARD_FALLBACKS; |
| case AccessoryTabType::ADDRESSES: |
| return FillingSource::ADDRESS_FALLBACKS; |
| case AccessoryTabType::OBSOLETE_TOUCH_TO_FILL: |
| case AccessoryTabType::ALL: |
| case AccessoryTabType::COUNT: |
| NOTREACHED() << "Cannot determine filling source"; |
| return FillingSource::PASSWORD_FALLBACKS; |
| } |
| } |
| |
| } // namespace |
| |
| ManualFillingControllerImpl::~ManualFillingControllerImpl() { |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| this); |
| } |
| |
| // static |
| base::WeakPtr<ManualFillingController> ManualFillingController::GetOrCreate( |
| content::WebContents* contents) { |
| ManualFillingControllerImpl* mf_controller = |
| ManualFillingControllerImpl::FromWebContents(contents); |
| if (!mf_controller) { |
| ManualFillingControllerImpl::CreateForWebContents(contents); |
| mf_controller = ManualFillingControllerImpl::FromWebContents(contents); |
| mf_controller->Initialize(); |
| } |
| return mf_controller->AsWeakPtr(); |
| } |
| |
| // static |
| base::WeakPtr<ManualFillingController> ManualFillingController::Get( |
| content::WebContents* contents) { |
| ManualFillingControllerImpl* mf_controller = |
| ManualFillingControllerImpl::FromWebContents(contents); |
| return mf_controller ? mf_controller->AsWeakPtr() : nullptr; |
| } |
| |
| // static |
| void ManualFillingControllerImpl::CreateForWebContentsForTesting( |
| content::WebContents* web_contents, |
| base::WeakPtr<PasswordAccessoryController> pwd_controller, |
| base::WeakPtr<AddressAccessoryController> address_controller, |
| base::WeakPtr<CreditCardAccessoryController> cc_controller, |
| std::unique_ptr<ManualFillingViewInterface> view) { |
| DCHECK(web_contents) << "Need valid WebContents to attach controller to!"; |
| DCHECK(!FromWebContents(web_contents)) << "Controller already attached!"; |
| DCHECK(pwd_controller); |
| DCHECK(address_controller); |
| DCHECK(cc_controller); |
| DCHECK(view); |
| |
| web_contents->SetUserData(UserDataKey(), |
| // Using `new` to access a non-public constructor. |
| base::WrapUnique(new ManualFillingControllerImpl( |
| web_contents, std::move(pwd_controller), |
| std::move(address_controller), |
| std::move(cc_controller), std::move(view)))); |
| |
| FromWebContents(web_contents)->Initialize(); |
| } |
| |
| void ManualFillingControllerImpl::OnAutomaticGenerationStatusChanged( |
| bool available) { |
| DCHECK(view_); |
| view_->OnAutomaticGenerationStatusChanged(available); |
| } |
| |
| void ManualFillingControllerImpl::RefreshSuggestions( |
| const AccessorySheetData& accessory_sheet_data) { |
| view_->OnItemsAvailable(accessory_sheet_data); |
| available_sheets_.insert_or_assign(GetSourceForTabType(accessory_sheet_data), |
| accessory_sheet_data); |
| UpdateSourceAvailability( |
| GetSourceForTabType(accessory_sheet_data), |
| !accessory_sheet_data.user_info_list().empty() || |
| !accessory_sheet_data.promo_code_info_list().empty()); |
| } |
| |
| void ManualFillingControllerImpl::NotifyFocusedInputChanged( |
| autofill::FieldRendererId focused_field_id, |
| autofill::mojom::FocusedFieldType focused_field_type) { |
| TRACE_EVENT0("passwords", |
| "ManualFillingControllerImpl::NotifyFocusedInputChanged"); |
| autofill::LocalFrameToken frame_token; |
| if (content::RenderFrameHost* rfh = GetWebContents().GetFocusedFrame()) { |
| frame_token = autofill::LocalFrameToken(rfh->GetFrameToken().value()); |
| } |
| last_focused_field_id_ = {frame_token, focused_field_id}; |
| last_focused_field_type_ = focused_field_type; |
| |
| // Ensure warnings and filling state is updated according to focused field. |
| if (cc_controller_) |
| cc_controller_->RefreshSuggestions(); |
| |
| // Whenever the focus changes, reset the accessory. |
| if (ShouldShowAccessory()) |
| view_->SwapSheetWithKeyboard(); |
| else |
| view_->CloseAccessorySheet(); |
| |
| UpdateVisibility(); |
| } |
| |
| void ManualFillingControllerImpl::ShowAccessorySheetTab( |
| const autofill::AccessoryTabType& tab_type) { |
| if (tab_type == autofill::AccessoryTabType::CREDIT_CARDS) { |
| cc_controller_->RefreshSuggestions(); |
| } else { |
| NOTIMPLEMENTED() |
| << "ShowAccessorySheetTab does not support the given TabType yet " |
| << tab_type; |
| } |
| view_->ShowAccessorySheetTab(tab_type); |
| } |
| |
| void ManualFillingControllerImpl::UpdateSourceAvailability( |
| FillingSource source, |
| bool has_suggestions) { |
| if (source == FillingSource::AUTOFILL && |
| !base::FeatureList::IsEnabled( |
| autofill::features::kAutofillKeyboardAccessory)) { |
| // Ignore autofill signals if the feature is disabled. |
| return; |
| } |
| |
| if (has_suggestions == available_sources_.contains(source)) |
| return; |
| |
| if (has_suggestions) { |
| available_sources_.insert(source); |
| UpdateVisibility(); |
| return; |
| } |
| |
| available_sources_.erase(source); |
| if (!ShouldShowAccessory()) |
| UpdateVisibility(); |
| } |
| |
| void ManualFillingControllerImpl::Hide() { |
| view_->Hide(); |
| } |
| |
| void ManualFillingControllerImpl::OnFillingTriggered( |
| AccessoryTabType type, |
| const autofill::AccessorySheetField& selection) { |
| AccessoryController* controller = GetControllerForTabType(type); |
| if (!controller) |
| return; // Controller not available anymore. |
| controller->OnFillingTriggered(last_focused_field_id_, selection); |
| view_->SwapSheetWithKeyboard(); // Soft-close the keyboard. |
| } |
| |
| void ManualFillingControllerImpl::OnOptionSelected( |
| AccessoryAction selected_action) const { |
| UMA_HISTOGRAM_ENUMERATION("KeyboardAccessory.AccessoryActionSelected", |
| selected_action, AccessoryAction::COUNT); |
| AccessoryController* controller = GetControllerForAction(selected_action); |
| if (!controller) |
| return; // Controller not available anymore. |
| controller->OnOptionSelected(selected_action); |
| } |
| |
| void ManualFillingControllerImpl::OnToggleChanged( |
| AccessoryAction toggled_action, |
| bool enabled) const { |
| AccessoryController* controller = GetControllerForAction(toggled_action); |
| if (!controller) |
| return; // Controller not available anymore. |
| controller->OnToggleChanged(toggled_action, enabled); |
| } |
| |
| void ManualFillingControllerImpl::RequestAccessorySheet( |
| autofill::AccessoryTabType tab_type, |
| base::OnceCallback<void(const autofill::AccessorySheetData&)> callback) { |
| // TODO(crbug.com/1169167): Consider to execute this async to reduce jank. |
| absl::optional<AccessorySheetData> sheet = |
| GetControllerForTabType(tab_type)->GetSheetData(); |
| // After they were loaded, all currently existing sheet types always return a |
| // value and will always result in a called callback. |
| // The only case where they are not available is before their first load (so |
| // if a user entered a tab but didn't focus any fields yet). In that case, the |
| // update is unnecessary since the first focus will push the correct sheet. |
| // TODO(crbug.com/1169167): Consider sending a null or default sheet to cover |
| // future cases where we can't rely on a sheet always being available. |
| if (sheet.has_value()) { |
| std::move(callback).Run(sheet.value()); |
| } |
| } |
| |
| gfx::NativeView ManualFillingControllerImpl::container_view() const { |
| // While a const_cast is not ideal. The Autofill API uses const in various |
| // spots and the content public API doesn't have const accessors. So the const |
| // cast is the lesser of two evils. |
| return const_cast<content::WebContents&>(GetWebContents()).GetNativeView(); |
| } |
| |
| // Returns a weak pointer for this object. |
| base::WeakPtr<ManualFillingController> |
| ManualFillingControllerImpl::AsWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void ManualFillingControllerImpl::Initialize() { |
| DCHECK(FromWebContents(&GetWebContents())) << "Don't call from constructor!"; |
| RegisterObserverForAllowedSources(); |
| if (address_controller_) |
| address_controller_->RefreshSuggestions(); |
| } |
| |
| ManualFillingControllerImpl::ManualFillingControllerImpl( |
| content::WebContents* web_contents) |
| : content::WebContentsUserData<ManualFillingControllerImpl>(*web_contents) { |
| if (PasswordAccessoryController::AllowedForWebContents(web_contents)) { |
| pwd_controller_ = ChromePasswordManagerClient::FromWebContents(web_contents) |
| ->GetOrCreatePasswordAccessory() |
| ->AsWeakPtr(); |
| DCHECK(pwd_controller_); |
| } |
| if (AddressAccessoryController::AllowedForWebContents(web_contents)) { |
| address_controller_ = |
| AddressAccessoryController::GetOrCreate(web_contents)->AsWeakPtr(); |
| DCHECK(address_controller_); |
| } |
| if (CreditCardAccessoryController::AllowedForWebContents(web_contents)) { |
| cc_controller_ = |
| CreditCardAccessoryController::GetOrCreate(web_contents)->AsWeakPtr(); |
| DCHECK(cc_controller_); |
| } |
| |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| this, "ManualFillingCache", base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| ManualFillingControllerImpl::ManualFillingControllerImpl( |
| content::WebContents* web_contents, |
| base::WeakPtr<PasswordAccessoryController> pwd_controller, |
| base::WeakPtr<AddressAccessoryController> address_controller, |
| base::WeakPtr<CreditCardAccessoryController> cc_controller, |
| std::unique_ptr<ManualFillingViewInterface> view) |
| : content::WebContentsUserData<ManualFillingControllerImpl>(*web_contents), |
| pwd_controller_(std::move(pwd_controller)), |
| address_controller_(std::move(address_controller)), |
| cc_controller_(std::move(cc_controller)), |
| view_(std::move(view)) { |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| this, "ManualFillingCache", base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| bool ManualFillingControllerImpl::OnMemoryDump( |
| const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* process_memory_dump) { |
| auto* dump = process_memory_dump->CreateAllocatorDump( |
| base::StringPrintf("passwords/manual_filling_controller/0x%" PRIXPTR, |
| reinterpret_cast<uintptr_t>(this))); |
| dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| base::trace_event::EstimateMemoryUsage(available_sheets_)); |
| return true; |
| } |
| |
| bool ManualFillingControllerImpl::ShouldShowAccessory() const { |
| // If we only provide password fallbacks (== accessory V1), show them for |
| // passwords and username fields only. |
| if (!base::FeatureList::IsEnabled( |
| autofill::features::kAutofillKeyboardAccessory) && |
| !base::FeatureList::IsEnabled( |
| autofill::features::kAutofillManualFallbackAndroid) && |
| !base::FeatureList::IsEnabled( |
| autofill::features::kAutofillEnableManualFallbackForVirtualCards)) { |
| return last_focused_field_type_ == |
| FocusedFieldType::kFillablePasswordField || |
| last_focused_field_type_ == FocusedFieldType::kFillableUsernameField; |
| } |
| switch (last_focused_field_type_) { |
| // Always show on password fields to provide management and generation. |
| case FocusedFieldType::kFillablePasswordField: |
| return true; |
| |
| // If there are suggestions, show on usual form fields. |
| case FocusedFieldType::kFillableUsernameField: |
| case FocusedFieldType::kFillableNonSearchField: |
| // TODO(crbug/1242839): Hide the accessory if no fallback is available. |
| return true; |
| |
| // Fallbacks aren't really useful on search fields but autocomplete entries |
| // justify showing the accessory. |
| case FocusedFieldType::kFillableSearchField: |
| return available_sources_.contains(FillingSource::AUTOFILL); |
| |
| // Even if there are suggestions, don't show on textareas. |
| case FocusedFieldType::kFillableTextArea: |
| return false; // TODO(https://crbug.com/965478): true on long-press. |
| |
| // Never show if the focused field is not explicitly fillable. |
| case FocusedFieldType::kUnfillableElement: |
| case FocusedFieldType::kUnknown: |
| return false; |
| } |
| } |
| |
| void ManualFillingControllerImpl::UpdateVisibility() { |
| TRACE_EVENT0("passwords", "ManualFillingControllerImpl::UpdateVisibility"); |
| if (ShouldShowAccessory()) { |
| for (const FillingSource& source : available_sources_) { |
| if (available_sheets_.contains(source)) { |
| // Use a local cached copy if it exists. This will be deprecated with |
| // crbug.com/1169167. |
| view_->OnItemsAvailable(available_sheets_.find(source)->second); |
| continue; |
| } |
| if (source == FillingSource::AUTOFILL) |
| continue; // Autofill suggestions have no sheet. |
| absl::optional<AccessorySheetData> sheet = |
| GetControllerForFillingSource(source)->GetSheetData(); |
| if (sheet.has_value()) |
| view_->OnItemsAvailable(std::move(sheet.value())); |
| } |
| view_->ShowWhenKeyboardIsVisible(); |
| } else { |
| view_->Hide(); |
| } |
| } |
| |
| void ManualFillingControllerImpl::RegisterObserverForAllowedSources() { |
| if (!base::FeatureList::IsEnabled( |
| autofill::features::kAutofillKeyboardAccessory)) { |
| return; // Observer mechanism only available for the modern accessory. |
| } |
| for (FillingSource source : kAllowedFillingSources) { |
| AccessoryController* sheet_controller = |
| GetControllerForFillingSource(source); |
| if (!sheet_controller) |
| continue; // Ignore disallowed sheets. |
| sheet_controller->RegisterFillingSourceObserver(base::BindRepeating( |
| &ManualFillingControllerImpl::OnSourceAvailabilityChanged, |
| weak_factory_.GetWeakPtr(), source)); |
| } |
| } |
| |
| void ManualFillingControllerImpl::OnSourceAvailabilityChanged( |
| FillingSource source, |
| AccessoryController* source_controller, |
| AccessoryController::IsFillingSourceAvailable is_source_available) { |
| absl::optional<AccessorySheetData> sheet = source_controller->GetSheetData(); |
| bool show_filling_source = sheet.has_value() && is_source_available; |
| // TODO(crbug.com/1169167): Remove once all sheets pull this information |
| // instead of waiting to get it pushed. |
| view_->OnItemsAvailable(std::move(sheet.value())); |
| UpdateSourceAvailability(source, show_filling_source); |
| } |
| |
| AccessoryController* ManualFillingControllerImpl::GetControllerForTabType( |
| AccessoryTabType type) const { |
| switch (type) { |
| case AccessoryTabType::ADDRESSES: |
| return address_controller_.get(); |
| case AccessoryTabType::PASSWORDS: |
| return pwd_controller_.get(); |
| case AccessoryTabType::CREDIT_CARDS: |
| return cc_controller_.get(); |
| case AccessoryTabType::OBSOLETE_TOUCH_TO_FILL: |
| case AccessoryTabType::ALL: |
| case AccessoryTabType::COUNT: |
| NOTREACHED() << "Controller not defined for tab: " |
| << static_cast<int>(type); |
| return nullptr; |
| } |
| } |
| |
| AccessoryController* ManualFillingControllerImpl::GetControllerForAction( |
| AccessoryAction action) const { |
| switch (action) { |
| case AccessoryAction::GENERATE_PASSWORD_MANUAL: |
| case AccessoryAction::MANAGE_PASSWORDS: |
| case AccessoryAction::USE_OTHER_PASSWORD: |
| case AccessoryAction::GENERATE_PASSWORD_AUTOMATIC: |
| case AccessoryAction::TOGGLE_SAVE_PASSWORDS: |
| return pwd_controller_.get(); |
| case AccessoryAction::MANAGE_ADDRESSES: |
| return address_controller_.get(); |
| case AccessoryAction::MANAGE_CREDIT_CARDS: |
| return cc_controller_.get(); |
| case AccessoryAction::AUTOFILL_SUGGESTION: |
| case AccessoryAction::COUNT: |
| NOTREACHED() << "Controller not defined for action: " |
| << static_cast<int>(action); |
| return nullptr; |
| } |
| } |
| |
| AccessoryController* ManualFillingControllerImpl::GetControllerForFillingSource( |
| const FillingSource& filling_source) const { |
| switch (filling_source) { |
| case FillingSource::PASSWORD_FALLBACKS: |
| return pwd_controller_.get(); |
| case FillingSource::CREDIT_CARD_FALLBACKS: |
| return cc_controller_.get(); |
| case FillingSource::ADDRESS_FALLBACKS: |
| return address_controller_.get(); |
| case FillingSource::AUTOFILL: |
| NOTREACHED() << "Controller not defined for filling source: " |
| << static_cast<int>(filling_source); |
| return nullptr; |
| } |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(ManualFillingControllerImpl); |