blob: 977ea9c5997243f641760d175e30af9d535e9dfc [file] [log] [blame]
// 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 <utility>
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.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/common/autofill_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 "content/public/browser/web_contents.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 {
FillingSource GetSourceForTab(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::TOUCH_TO_FILL:
return FillingSource::TOUCH_TO_FILL;
case AccessoryTabType::ALL:
case AccessoryTabType::COUNT:
break; // Intentional failure.
}
NOTREACHED() << "Cannot determine filling source";
return FillingSource::PASSWORD_FALLBACKS;
}
} // namespace
ManualFillingControllerImpl::~ManualFillingControllerImpl() = default;
// 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);
UpdateSourceAvailability(GetSourceForTab(accessory_sheet_data),
!accessory_sheet_data.user_info_list().empty());
}
void ManualFillingControllerImpl::NotifyFocusedInputChanged(
autofill::mojom::FocusedFieldType focused_field_type) {
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::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::UserInfo::Field& selection) {
AccessoryController* controller = GetControllerForTab(type);
if (!controller)
return; // Controller not available anymore.
controller->OnFillingTriggered(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);
}
gfx::NativeView ManualFillingControllerImpl::container_view() const {
return web_contents_->GetNativeView();
}
// Returns a weak pointer for this object.
base::WeakPtr<ManualFillingController>
ManualFillingControllerImpl::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void ManualFillingControllerImpl::Initialize() {
DCHECK(FromWebContents(web_contents_)) << "Don't call from constructor!";
if (address_controller_)
address_controller_->RefreshSuggestions();
if (cc_controller_)
cc_controller_->RefreshSuggestions();
}
ManualFillingControllerImpl::ManualFillingControllerImpl(
content::WebContents* web_contents)
: web_contents_(web_contents) {
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_);
}
}
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)
: web_contents_(web_contents),
pwd_controller_for_testing_(std::move(pwd_controller)),
address_controller_(std::move(address_controller)),
cc_controller_(std::move(cc_controller)),
view_(std::move(view)) {}
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)) {
return focused_field_type_ == FocusedFieldType::kFillablePasswordField ||
(focused_field_type_ == FocusedFieldType::kFillableUsernameField &&
available_sources_.contains(FillingSource::PASSWORD_FALLBACKS));
}
switch (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:
return !available_sources_.empty();
// Even if there are suggestions, don't show on search fields and textareas.
case FocusedFieldType::kFillableSearchField:
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;
}
NOTREACHED() << "Unhandled field type " << focused_field_type_;
return false;
}
void ManualFillingControllerImpl::UpdateVisibility() {
if (ShouldShowAccessory()) {
view_->ShowWhenKeyboardIsVisible();
} else {
view_->Hide();
}
}
AccessoryController* ManualFillingControllerImpl::GetControllerForTab(
AccessoryTabType type) {
switch (type) {
case AccessoryTabType::ADDRESSES:
return address_controller_.get();
case AccessoryTabType::PASSWORDS:
return GetPasswordController();
case AccessoryTabType::CREDIT_CARDS:
return cc_controller_.get();
case AccessoryTabType::TOUCH_TO_FILL:
case AccessoryTabType::ALL:
case AccessoryTabType::COUNT:
break; // Intentional failure.
}
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 GetPasswordController();
case AccessoryAction::MANAGE_ADDRESSES:
return address_controller_.get();
case AccessoryAction::MANAGE_CREDIT_CARDS:
return cc_controller_.get();
case AccessoryAction::AUTOFILL_SUGGESTION:
case AccessoryAction::COUNT:
break; // Intentional failure;
}
NOTREACHED() << "Controller not defined for action: "
<< static_cast<int>(action);
return nullptr;
}
PasswordAccessoryController*
ManualFillingControllerImpl::GetPasswordController() const {
if (pwd_controller_for_testing_)
return pwd_controller_for_testing_.get();
return PasswordAccessoryController::AllowedForWebContents(web_contents_)
? ChromePasswordManagerClient::FromWebContents(web_contents_)
->GetOrCreatePasswordAccessory()
: nullptr;
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(ManualFillingControllerImpl)