blob: 91f033e04118838f53cf26bc7efc779a2d4410e0 [file] [log] [blame]
// Copyright 2019 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/autofill_keyboard_accessory_adapter.h"
#include <numeric>
#include <string>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "components/autofill/core/browser/filling_product.h"
#include "components/autofill/core/browser/metrics/granular_filling_metrics.h"
#include "components/autofill/core/browser/ui/popup_item_ids.h"
#include "components/autofill/core/browser/ui/suggestion.h"
#include "components/autofill/core/common/aliases.h"
namespace autofill {
constexpr char16_t kLabelSeparator = ' ';
constexpr size_t kMaxBulletCount = 8;
namespace {
std::u16string CreateLabel(const Suggestion& suggestion) {
std::u16string password =
suggestion.additional_label.substr(0, kMaxBulletCount);
// The label contains the signon_realm or is empty. The additional_label can
// never be empty since it must contain a password.
if (suggestion.labels.empty() || suggestion.labels[0][0].value.empty())
return password;
// TODO(crbug.com/1313616): Re-consider whether using CHECK is an appropriate
// way to explicitly regulate what information should be populated for the
// interface.
CHECK_EQ(suggestion.labels.size(), 1U);
CHECK_EQ(suggestion.labels[0].size(), 1U);
return suggestion.labels[0][0].value + kLabelSeparator + password;
}
} // namespace
AutofillKeyboardAccessoryAdapter::AutofillKeyboardAccessoryAdapter(
base::WeakPtr<AutofillPopupController> controller)
: controller_(controller) {}
AutofillKeyboardAccessoryAdapter::~AutofillKeyboardAccessoryAdapter() = default;
// AutofillPopupView implementation.
bool AutofillKeyboardAccessoryAdapter::Show(
AutoselectFirstSuggestion autoselect_first_suggestion) {
CHECK(view_) << "Show called before a View was set!";
OnSuggestionsChanged();
return true;
}
void AutofillKeyboardAccessoryAdapter::Hide() {
CHECK(view_) << "Hide called before a View was set!";
view_->Hide();
}
bool AutofillKeyboardAccessoryAdapter::OverlapsWithPictureInPictureWindow()
const {
// TODO(crbug.com/1477682): Hide the KA suggestion if it overlaps with
// picture-in-picture window.
return false;
}
bool AutofillKeyboardAccessoryAdapter::HandleKeyPressEvent(
const content::NativeWebKeyboardEvent& event) {
return false;
}
void AutofillKeyboardAccessoryAdapter::OnSuggestionsChanged() {
TRACE_EVENT0("passwords",
"AutofillKeyboardAccessoryAdapter::OnSuggestionsChanged");
CHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
CHECK(view_) << "OnSuggestionsChanged called before a View was set!";
labels_.clear();
front_element_ = std::nullopt;
for (int i = 0; i < GetLineCount(); ++i) {
const Suggestion& suggestion = controller_->GetSuggestionAt(i);
if (suggestion.popup_item_id != PopupItemId::kClearForm) {
labels_.push_back(CreateLabel(suggestion));
continue;
}
CHECK(!front_element_.has_value()) << "Additional front item at: " << i;
front_element_ = std::optional<int>(i);
// If there is a special popup item, just reuse the previously used label.
std::vector<std::vector<Suggestion::Text>> suggestion_labels =
controller_->GetSuggestionLabelsAt(i);
if (suggestion_labels.empty()) {
labels_.emplace_back();
} else {
labels_.emplace_back(std::move(suggestion_labels[0][0].value));
}
}
view_->Show();
}
void AutofillKeyboardAccessoryAdapter::AxAnnounce(const std::u16string& text) {
CHECK(view_) << "AxAnnounce called before a View was set!";
view_->AxAnnounce(text);
}
std::optional<int32_t> AutofillKeyboardAccessoryAdapter::GetAxUniqueId() {
NOTIMPLEMENTED() << "See https://crbug.com/985927";
return std::nullopt;
}
base::WeakPtr<AutofillPopupView>
AutofillKeyboardAccessoryAdapter::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
base::WeakPtr<AutofillPopupView>
AutofillKeyboardAccessoryAdapter::CreateSubPopupView(
base::WeakPtr<AutofillPopupController> controller) {
NOTIMPLEMENTED() << "No sub-popups on Keyboard Accessory";
return nullptr;
}
// AutofillPopupController implementation.
void AutofillKeyboardAccessoryAdapter::AcceptSuggestion(int index) {
if (controller_) {
controller_->AcceptSuggestion(OffsetIndexFor(index));
}
}
void AutofillKeyboardAccessoryAdapter::PerformButtonActionForSuggestion(
int index) {
// Actions currently only exist on Desktop.
NOTREACHED();
}
int AutofillKeyboardAccessoryAdapter::GetLineCount() const {
return controller_ ? controller_->GetLineCount() : 0;
}
const autofill::Suggestion& AutofillKeyboardAccessoryAdapter::GetSuggestionAt(
int row) const {
CHECK(controller_) << "Call GetSuggestionAt only from its owner!";
return controller_->GetSuggestionAt(OffsetIndexFor(row));
}
std::u16string AutofillKeyboardAccessoryAdapter::GetSuggestionMainTextAt(
int row) const {
CHECK(controller_) << "Call GetSuggestionMainTextAt only from its owner!";
return controller_->GetSuggestionMainTextAt(OffsetIndexFor(row));
}
std::u16string AutofillKeyboardAccessoryAdapter::GetSuggestionMinorTextAt(
int row) const {
CHECK(controller_) << "Call GetSuggestionMinorTextAt only from its owner!";
return controller_->GetSuggestionMinorTextAt(OffsetIndexFor(row));
}
std::vector<std::vector<Suggestion::Text>>
AutofillKeyboardAccessoryAdapter::GetSuggestionLabelsAt(int row) const {
CHECK(controller_) << "Call GetSuggestionLabelAt only from its owner!";
CHECK(static_cast<size_t>(row) < labels_.size());
return {{Suggestion::Text(labels_[OffsetIndexFor(row)])}};
}
FillingProduct AutofillKeyboardAccessoryAdapter::GetMainFillingProduct() const {
CHECK(controller_) << "Call GetPopupType only from its owner!";
return controller_->GetMainFillingProduct();
}
bool AutofillKeyboardAccessoryAdapter::
ShouldIgnoreMouseObservedOutsideItemBoundsCheck() const {
CHECK(controller_) << "Call ShouldIgnoreMouseObservedOutsideItemBoundsCheck "
"only from its owner!";
return controller_->ShouldIgnoreMouseObservedOutsideItemBoundsCheck();
}
base::WeakPtr<AutofillPopupController>
AutofillKeyboardAccessoryAdapter::OpenSubPopup(
const gfx::RectF& anchor_bounds,
std::vector<Suggestion> suggestions,
AutoselectFirstSuggestion autoselect_first_suggestion) {
NOTIMPLEMENTED() << "No sub-popups on Keyboard Accessory";
return nullptr;
}
void AutofillKeyboardAccessoryAdapter::HideSubPopup() {
NOTIMPLEMENTED() << "No sub-popups on Keyboard Accessory";
}
bool AutofillKeyboardAccessoryAdapter::GetRemovalConfirmationText(
int index,
std::u16string* title,
std::u16string* body) {
return controller_ && controller_->GetRemovalConfirmationText(
OffsetIndexFor(index), title, body);
}
bool AutofillKeyboardAccessoryAdapter::RemoveSuggestion(
int index,
AutofillMetrics::SingleEntryRemovalMethod removal_method) {
CHECK_EQ(removal_method,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory);
CHECK(view_) << "RemoveSuggestion called before a View was set!";
std::u16string title, body;
if (!GetRemovalConfirmationText(index, &title, &body))
return false;
view_->ConfirmDeletion(
title, body,
base::BindOnce(&AutofillKeyboardAccessoryAdapter::OnDeletionDialogClosed,
weak_ptr_factory_.GetWeakPtr(), index));
return true;
}
void AutofillKeyboardAccessoryAdapter::SelectSuggestion(int index) {
if (!controller_)
return;
controller_->SelectSuggestion(OffsetIndexFor(index));
}
void AutofillKeyboardAccessoryAdapter::UnselectSuggestion() {
if (!controller_) {
return;
}
controller_->UnselectSuggestion();
}
// AutofillPopupViewDelegate implementation
void AutofillKeyboardAccessoryAdapter::Hide(PopupHidingReason reason) {
if (controller_)
controller_->Hide(reason);
}
void AutofillKeyboardAccessoryAdapter::ViewDestroyed() {
if (controller_)
controller_->ViewDestroyed();
view_.reset();
// The controller has now deleted itself.
controller_ = nullptr;
delete this; // Remove dangling weak reference.
}
gfx::NativeView AutofillKeyboardAccessoryAdapter::container_view() const {
CHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
return controller_->container_view();
}
content::WebContents* AutofillKeyboardAccessoryAdapter::GetWebContents() const {
return controller_->GetWebContents();
}
const gfx::RectF& AutofillKeyboardAccessoryAdapter::element_bounds() const {
CHECK(controller_) << "Call OnSuggestionsChanged only from its owner!";
return controller_->element_bounds();
}
base::i18n::TextDirection
AutofillKeyboardAccessoryAdapter::GetElementTextDirection() const {
CHECK(controller_);
return controller_->GetElementTextDirection();
}
std::vector<Suggestion> AutofillKeyboardAccessoryAdapter::GetSuggestions()
const {
if (!controller_)
return std::vector<Suggestion>();
std::vector<Suggestion> suggestions = controller_->GetSuggestions();
if (front_element_.has_value()) {
std::rotate(suggestions.begin(),
suggestions.begin() + front_element_.value(),
suggestions.begin() + front_element_.value() + 1);
}
return suggestions;
}
std::optional<AutofillClient::PopupScreenLocation>
AutofillKeyboardAccessoryAdapter::GetPopupScreenLocation() const {
NOTIMPLEMENTED() << "No popup screen location for keyboard accessories.";
return std::nullopt;
}
void AutofillKeyboardAccessoryAdapter::OnDeletionDialogClosed(int index,
bool confirmed) {
if (confirmed) {
if (controller_) {
controller_->RemoveSuggestion(
OffsetIndexFor(index),
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory);
}
return;
}
if (GetFillingProductFromPopupItemId(GetSuggestionAt(index).popup_item_id) ==
FillingProduct::kAddress) {
autofill_metrics::LogDeleteAddressProfileFromExtendedMenu(
/*user_accepted_delete=*/false);
}
}
int AutofillKeyboardAccessoryAdapter::OffsetIndexFor(int element_index) const {
if (!front_element_.has_value())
return element_index;
if (0 == element_index)
return front_element_.value();
return element_index - (element_index <= front_element_.value() ? 1 : 0);
}
} // namespace autofill