blob: 050b3599f02aea189a0361254f202e3b82a71fc8 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/accessibility/accessibility_state_utils.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_test_base.h"
#include "chrome/browser/ui/autofill/autofill_popup_view.h"
#include "chrome/browser/ui/autofill/popup_controller_common.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/content_autofill_driver_factory.h"
#include "components/autofill/content/browser/content_autofill_driver_factory_test_api.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/content/browser/test_content_autofill_client.h"
#include "components/autofill/core/browser/autofill_driver_router.h"
#include "components/autofill/core/browser/autofill_external_delegate.h"
#include "components/autofill/core/browser/autofill_manager.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/browser/ui/autofill_popup_delegate.h"
#include "components/autofill/core/browser/ui/popup_hiding_reasons.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"
#include "components/autofill/core/common/unique_ids.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/video_picture_in_picture_window_controller.h"
#include "content/public/browser/weak_document_ptr.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/input/native_web_keyboard_event.h"
#include "content/public/test/navigation_simulator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/text_utils.h"
namespace autofill {
namespace {
using base::ASCIIToUTF16;
using base::WeakPtr;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Optional;
using ::testing::Return;
content::RenderFrameHost* CreateAndNavigateChildFrame(
content::RenderFrameHost* parent,
const GURL& url,
std::string_view name) {
content::RenderFrameHost* rfh =
content::RenderFrameHostTester::For(parent)->AppendChild(
std::string(name));
// ContentAutofillDriverFactory::DidFinishNavigation() creates a driver for
// subframes only if
// `NavigationHandle::HasSubframeNavigationEntryCommitted()` is true. This
// is not the case for the first navigation. (In non-unit-tests, the first
// navigation creates a driver in
// ContentAutofillDriverFactory::BindAutofillDriver().) Therefore,
// we simulate *two* navigations here, and explicitly set the transition
// type for the second navigation.
std::unique_ptr<content::NavigationSimulator> simulator;
// First navigation: `HasSubframeNavigationEntryCommitted() == false`.
// Must be a different URL from the second navigation.
GURL about_blank("about:blank");
CHECK_NE(about_blank, url);
simulator =
content::NavigationSimulator::CreateRendererInitiated(about_blank, rfh);
simulator->Commit();
rfh = simulator->GetFinalRenderFrameHost();
// Second navigation: `HasSubframeNavigationEntryCommitted() == true`.
// Must set the transition type to ui::PAGE_TRANSITION_MANUAL_SUBFRAME.
simulator = content::NavigationSimulator::CreateRendererInitiated(url, rfh);
simulator->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
content::RenderFrameHost* NavigateAndCommitFrame(content::RenderFrameHost* rfh,
const GURL& url) {
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(url, rfh);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
} // namespace
using AutofillPopupControllerTest = AutofillPopupControllerTestBase<>;
TEST_F(AutofillPopupControllerTest, RemoveSuggestion) {
ShowSuggestions(manager(),
{PopupItemId::kAddressEntry, PopupItemId::kAddressEntry,
PopupItemId::kAutofillOptions});
// Generate a popup, so it can be hidden later. It doesn't matter what the
// external_delegate thinks is being shown in the process, since we are just
// testing the popup here.
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAddressEntry)))
.WillRepeatedly(Return(true));
// Remove the first entry. The popup should be redrawn since its size has
// changed.
EXPECT_CALL(client().popup_view(), OnSuggestionsChanged());
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
Mock::VerifyAndClearExpectations(&client().popup_view());
// Remove the next entry. The popup should then be hidden since there are
// no Autofill entries left.
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kNoSuggestions));
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
}
// Regression test for (crbug.com/1513574): Showing an Autofill Compose
// suggestion twice does not crash.
TEST_F(AutofillPopupControllerTest, ShowTwice) {
ShowSuggestions(manager(),
{Suggestion(u"Help me write", PopupItemId::kCompose)});
ShowSuggestions(manager(),
{Suggestion(u"Help me write", PopupItemId::kCompose)});
}
TEST_F(AutofillPopupControllerTest, RemoveAutocompleteSuggestion_AnnounceText) {
ShowSuggestions(manager(),
{Suggestion(u"main text", PopupItemId::kAutocompleteEntry)});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAutocompleteEntry)))
.WillOnce(Return(true));
EXPECT_CALL(client().popup_view(),
AxAnnounce(Eq(u"Entry main text has been deleted")));
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
}
TEST_F(AutofillPopupControllerTest,
RemoveAutocompleteSuggestion_IgnoresClickOutsideCheck) {
ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry,
PopupItemId::kAutocompleteEntry});
// Generate a popup, so it can be hidden later. It doesn't matter what the
// external_delegate thinks is being shown in the process, since we are just
// testing the popup here.
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAutocompleteEntry)))
.WillOnce(Return(true));
// Remove the first entry. The popup should be redrawn since its size has
// changed.
EXPECT_CALL(client().popup_view(), OnSuggestionsChanged());
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
Mock::VerifyAndClearExpectations(&client().popup_view());
EXPECT_TRUE(client()
.popup_controller(manager())
.ShouldIgnoreMouseObservedOutsideItemBoundsCheck());
}
TEST_F(AutofillPopupControllerTest,
RemoveAutocompleteSuggestion_NoMetricsEmittedOnFail) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAutocompleteEntry)))
.WillOnce(Return(false));
EXPECT_FALSE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
histogram_tester.ExpectUniqueSample(
"Autofill.Autocomplete.SingleEntryRemovalMethod",
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
0);
histogram_tester.ExpectUniqueSample(
"Autocomplete.Events2",
AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
}
TEST_F(AutofillPopupControllerTest,
RemoveAutocompleteSuggestion_MetricsEmittedOnSuccess) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAutocompleteEntry)))
.WillOnce(Return(true));
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
histogram_tester.ExpectUniqueSample(
"Autofill.Autocomplete.SingleEntryRemovalMethod",
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
1);
histogram_tester.ExpectUniqueSample(
"Autocomplete.Events2",
AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 1);
// Also no autofill metrics are emitted.
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
histogram_tester.ExpectUniqueSample(
"Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 0);
}
TEST_F(AutofillPopupControllerTest,
RemoveAddressSuggestion_ShiftDelete_NoMetricsEmittedOnFail) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAddressEntry)))
.WillOnce(Return(false));
EXPECT_FALSE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
histogram_tester.ExpectUniqueSample(
"Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 0);
}
TEST_F(AutofillPopupControllerTest,
RemoveAddressSuggestion_ShiftDelete_MetricsEmittedOnSuccess) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAddressEntry)))
.WillOnce(Return(true));
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 1);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 1);
// Also no autocomplete or keyboard accessory metrics are emitted.
histogram_tester.ExpectUniqueSample(
"Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
histogram_tester.ExpectUniqueSample(
"Autofill.Autocomplete.SingleEntryRemovalMethod",
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
0);
histogram_tester.ExpectUniqueSample(
"Autocomplete.Events2",
AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
}
TEST_F(AutofillPopupControllerTest,
RemoveAddressSuggestion_KeyboardAccessory_NoMetricsEmittedOnFail) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAddressEntry)))
.WillOnce(Return(false));
EXPECT_FALSE(client().popup_controller(manager()).RemoveSuggestion(
0, AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory));
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
histogram_tester.ExpectUniqueSample(
"Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 0);
}
TEST_F(AutofillPopupControllerTest,
RemoveAddressSuggestion_KeyboardAccessory_MetricsEmittedOnSuccess) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kAddressEntry)))
.WillOnce(Return(true));
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0, AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory));
histogram_tester.ExpectUniqueSample(
"Autofill.ProfileDeleted.KeyboardAccessory", 1, 1);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 1);
// Also no autocomplete or shift+delete metrics are emitted.
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
histogram_tester.ExpectUniqueSample(
"Autofill.Autocomplete.SingleEntryRemovalMethod",
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
0);
histogram_tester.ExpectUniqueSample(
"Autocomplete.Events2",
AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
}
TEST_F(AutofillPopupControllerTest,
RemoveCreditCardSuggestion_NoMetricsEmitted) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kCreditCardEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(manager().external_delegate(),
RemoveSuggestion(Field(&Suggestion::popup_item_id,
PopupItemId::kCreditCardEntry)))
.WillOnce(Return(true));
EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
0,
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
histogram_tester.ExpectUniqueSample(
"Autofill.Autocomplete.SingleEntryRemovalMethod",
AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
0);
histogram_tester.ExpectUniqueSample(
"Autocomplete.Events2",
AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
histogram_tester.ExpectUniqueSample(
"Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 0);
}
TEST_F(AutofillPopupControllerTest, UpdateDataListValues) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
std::vector<SelectOption> options = {
{.value = u"data list value 1", .content = u"data list label 1"}};
client().popup_controller(manager()).UpdateDataListValues(options);
ASSERT_EQ(3, client().popup_controller(manager()).GetLineCount());
Suggestion result0 = client().popup_controller(manager()).GetSuggestionAt(0);
EXPECT_EQ(options[0].value, result0.main_text.value);
EXPECT_EQ(options[0].value,
client().popup_controller(manager()).GetSuggestionMainTextAt(0));
ASSERT_EQ(1u, result0.labels.size());
ASSERT_EQ(1u, result0.labels[0].size());
EXPECT_EQ(options[0].content, result0.labels[0][0].value);
EXPECT_EQ(std::u16string(), result0.additional_label);
EXPECT_EQ(options[0].content, client()
.popup_controller(manager())
.GetSuggestionLabelsAt(0)[0][0]
.value);
EXPECT_EQ(PopupItemId::kDatalistEntry, result0.popup_item_id);
Suggestion result1 = client().popup_controller(manager()).GetSuggestionAt(1);
EXPECT_EQ(std::u16string(), result1.main_text.value);
EXPECT_TRUE(result1.labels.empty());
EXPECT_EQ(std::u16string(), result1.additional_label);
EXPECT_EQ(PopupItemId::kSeparator, result1.popup_item_id);
Suggestion result2 = client().popup_controller(manager()).GetSuggestionAt(2);
EXPECT_EQ(std::u16string(), result2.main_text.value);
EXPECT_TRUE(result2.labels.empty());
EXPECT_EQ(std::u16string(), result2.additional_label);
EXPECT_EQ(PopupItemId::kAddressEntry, result2.popup_item_id);
// Add two data list entries (which should replace the current one).
options.push_back(
{.value = u"data list value 1", .content = u"data list label 1"});
client().popup_controller(manager()).UpdateDataListValues(options);
ASSERT_EQ(4, client().popup_controller(manager()).GetLineCount());
// Original one first, followed by new one, then separator.
EXPECT_EQ(
options[0].value,
client().popup_controller(manager()).GetSuggestionAt(0).main_text.value);
EXPECT_EQ(options[0].value,
client().popup_controller(manager()).GetSuggestionMainTextAt(0));
ASSERT_EQ(
1u,
client().popup_controller(manager()).GetSuggestionAt(0).labels.size());
ASSERT_EQ(
1u,
client().popup_controller(manager()).GetSuggestionAt(0).labels[0].size());
EXPECT_EQ(options[0].content, client()
.popup_controller(manager())
.GetSuggestionAt(0)
.labels[0][0]
.value);
EXPECT_EQ(
std::u16string(),
client().popup_controller(manager()).GetSuggestionAt(0).additional_label);
EXPECT_EQ(
options[1].value,
client().popup_controller(manager()).GetSuggestionAt(1).main_text.value);
EXPECT_EQ(options[1].value,
client().popup_controller(manager()).GetSuggestionMainTextAt(1));
ASSERT_EQ(
1u,
client().popup_controller(manager()).GetSuggestionAt(1).labels.size());
ASSERT_EQ(
1u,
client().popup_controller(manager()).GetSuggestionAt(1).labels[0].size());
EXPECT_EQ(options[1].content, client()
.popup_controller(manager())
.GetSuggestionAt(1)
.labels[0][0]
.value);
EXPECT_EQ(
std::u16string(),
client().popup_controller(manager()).GetSuggestionAt(1).additional_label);
EXPECT_EQ(
PopupItemId::kSeparator,
client().popup_controller(manager()).GetSuggestionAt(2).popup_item_id);
// Clear all data list values.
options.clear();
client().popup_controller(manager()).UpdateDataListValues(options);
ASSERT_EQ(1, client().popup_controller(manager()).GetLineCount());
EXPECT_EQ(
PopupItemId::kAddressEntry,
client().popup_controller(manager()).GetSuggestionAt(0).popup_item_id);
}
TEST_F(AutofillPopupControllerTest, PopupsWithOnlyDataLists) {
// Create the popup with a single datalist element.
ShowSuggestions(manager(), {PopupItemId::kDatalistEntry});
// Replace the datalist element with a new one.
std::vector<SelectOption> options = {
{.value = u"data list value 1", .content = u"data list label 1"}};
client().popup_controller(manager()).UpdateDataListValues(options);
ASSERT_EQ(1, client().popup_controller(manager()).GetLineCount());
EXPECT_EQ(
options[0].value,
client().popup_controller(manager()).GetSuggestionAt(0).main_text.value);
ASSERT_EQ(
1u,
client().popup_controller(manager()).GetSuggestionAt(0).labels.size());
ASSERT_EQ(
1u,
client().popup_controller(manager()).GetSuggestionAt(0).labels[0].size());
EXPECT_EQ(options[0].content, client()
.popup_controller(manager())
.GetSuggestionAt(0)
.labels[0][0]
.value);
EXPECT_EQ(
std::u16string(),
client().popup_controller(manager()).GetSuggestionAt(0).additional_label);
EXPECT_EQ(
PopupItemId::kDatalistEntry,
client().popup_controller(manager()).GetSuggestionAt(0).popup_item_id);
// Clear datalist values and check that the popup becomes hidden.
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kNoSuggestions));
options.clear();
client().popup_controller(manager()).UpdateDataListValues(options);
}
TEST_F(AutofillPopupControllerTest, GetOrCreate) {
auto create_controller = [&](gfx::RectF bounds) {
return AutofillPopupController::GetOrCreate(
client().popup_controller(manager()).GetWeakPtr(),
manager().external_delegate().GetWeakPtrForTest(), nullptr,
PopupControllerCommon(std::move(bounds), base::i18n::UNKNOWN_DIRECTION,
nullptr),
/*form_control_ax_id=*/0);
};
WeakPtr<AutofillPopupController> controller = create_controller(gfx::RectF());
EXPECT_TRUE(controller);
controller->Hide(PopupHidingReason::kViewDestroyed);
EXPECT_FALSE(controller);
controller = create_controller(gfx::RectF());
EXPECT_TRUE(controller);
WeakPtr<AutofillPopupController> controller2 =
create_controller(gfx::RectF());
EXPECT_EQ(controller.get(), controller2.get());
controller->Hide(PopupHidingReason::kViewDestroyed);
EXPECT_FALSE(controller);
EXPECT_FALSE(controller2);
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kViewDestroyed));
gfx::RectF bounds(0.f, 0.f, 1.f, 2.f);
base::WeakPtr<AutofillPopupController> controller3 =
create_controller(bounds);
EXPECT_EQ(&client().popup_controller(manager()), controller3.get());
EXPECT_EQ(bounds, static_cast<AutofillPopupController*>(controller3.get())
->element_bounds());
controller3->Hide(PopupHidingReason::kViewDestroyed);
client().popup_controller(manager()).DoHide();
const base::WeakPtr<AutofillPopupController> controller4 =
create_controller(bounds);
EXPECT_EQ(&client().popup_controller(manager()), controller4.get());
EXPECT_EQ(bounds,
static_cast<const AutofillPopupController*>(controller4.get())
->element_bounds());
client().popup_controller(manager()).DoHide();
}
TEST_F(AutofillPopupControllerTest, ProperlyResetController) {
ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry,
PopupItemId::kAutocompleteEntry});
// Now show a new popup with the same controller, but with fewer items.
WeakPtr<AutofillPopupController> controller =
AutofillPopupController::GetOrCreate(
client().popup_controller(manager()).GetWeakPtr(),
manager().external_delegate().GetWeakPtrForTest(), nullptr,
PopupControllerCommon(gfx::RectF(), base::i18n::UNKNOWN_DIRECTION,
nullptr),
/*form_control_ax_id=*/0);
EXPECT_EQ(0, controller->GetLineCount());
}
TEST_F(AutofillPopupControllerTest, UnselectingClearsPreview) {
EXPECT_CALL(manager().external_delegate(), ClearPreviewedForm());
client().popup_controller(manager()).UnselectSuggestion();
}
TEST_F(AutofillPopupControllerTest, HidingClearsPreview) {
EXPECT_CALL(manager().external_delegate(), ClearPreviewedForm());
EXPECT_CALL(manager().external_delegate(), OnPopupHidden());
client().popup_controller(manager()).DoHide();
}
TEST_F(AutofillPopupControllerTest, DontHideWhenWaitingForData) {
EXPECT_CALL(client().popup_view(), Hide).Times(0);
client().popup_controller(manager()).PinView();
// Hide() will not work for stale data or when focusing native UI.
client().popup_controller(manager()).DoHide(PopupHidingReason::kStaleData);
client().popup_controller(manager()).DoHide(PopupHidingReason::kEndEditing);
// Check the expectations now since TearDown will perform a successful hide.
Mock::VerifyAndClearExpectations(&manager().external_delegate());
Mock::VerifyAndClearExpectations(&client().popup_view());
}
TEST_F(AutofillPopupControllerTest, ShouldReportHidingPopupReason) {
base::HistogramTester histogram_tester;
client().popup_controller(manager()).DoHide(PopupHidingReason::kTabGone);
histogram_tester.ExpectTotalCount("Autofill.PopupHidingReason", 1);
histogram_tester.ExpectBucketCount("Autofill.PopupHidingReason",
PopupHidingReason::kTabGone, 1);
}
// This is a regression test for crbug.com/521133 to ensure that we don't crash
// when suggestions updates race with user selections.
TEST_F(AutofillPopupControllerTest, SelectInvalidSuggestion) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(0);
// The following should not crash:
client().popup_controller(manager()).AcceptSuggestion(
/*index=*/1); // Out of bounds!
}
TEST_F(AutofillPopupControllerTest, AcceptSuggestionRespectsTimeout) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
// Calls before the threshold are ignored.
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(0);
client().popup_controller(manager()).AcceptSuggestion(0);
task_environment()->FastForwardBy(base::Milliseconds(100));
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion);
task_environment()->FastForwardBy(base::Milliseconds(400));
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
histogram_tester.ExpectTotalCount(
"Autofill.Popup.AcceptanceDelayThresholdNotMet", 2);
}
TEST_F(AutofillPopupControllerTest,
AcceptSuggestionTimeoutIsUpdatedOnPopupMove) {
base::HistogramTester histogram_tester;
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
// Calls before the threshold are ignored.
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(0);
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
task_environment()->FastForwardBy(base::Milliseconds(100));
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
histogram_tester.ExpectTotalCount(
"Autofill.Popup.AcceptanceDelayThresholdNotMet", 2);
task_environment()->FastForwardBy(base::Milliseconds(400));
// Show the suggestions again (simulating, e.g., a click somewhere slightly
// different).
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(0);
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
histogram_tester.ExpectTotalCount(
"Autofill.Popup.AcceptanceDelayThresholdNotMet", 3);
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion);
// After waiting, suggestions are accepted again.
task_environment()->FastForwardBy(base::Milliseconds(500));
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
histogram_tester.ExpectTotalCount(
"Autofill.Popup.AcceptanceDelayThresholdNotMet", 3);
}
// Tests that when a picture-in-picture window is initialized, there is a call
// to the popup view to check if the autofill popup bounds overlap with the
// picture-in-picture window.
TEST_F(AutofillPopupControllerTest, CheckBoundsOverlapWithPictureInPicture) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
PictureInPictureWindowManager* picture_in_picture_window_manager =
PictureInPictureWindowManager::GetInstance();
EXPECT_CALL(client().popup_view(), OverlapsWithPictureInPictureWindow);
picture_in_picture_window_manager->NotifyObserversOnEnterPictureInPicture();
}
TEST_F(AutofillPopupControllerTest,
GetRemovalConfirmationText_UnrelatedPopupItemId) {
std::u16string title;
std::u16string body;
ShowSuggestions(
manager(),
{Suggestion(u"Entry", PopupItemId::kAddressFieldByFieldFilling)});
EXPECT_FALSE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
}
TEST_F(AutofillPopupControllerTest,
GetRemovalConfirmationText_InvalidUniqueId) {
std::u16string title;
std::u16string body;
ShowSuggestions(manager(), {test::CreateAutofillSuggestion(
PopupItemId::kAddressFieldByFieldFilling,
u"Entry", Suggestion::Guid("1111"))});
EXPECT_FALSE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
}
TEST_F(AutofillPopupControllerTest, GetRemovalConfirmationText_Autocomplete) {
std::u16string title;
std::u16string body;
ShowSuggestions(manager(), {Suggestion(u"Autocomplete entry",
PopupItemId::kAutocompleteEntry)});
EXPECT_TRUE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
EXPECT_EQ(title, u"Autocomplete entry");
EXPECT_EQ(body,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY));
}
TEST_F(AutofillPopupControllerTest,
GetRemovalConfirmationText_LocalCreditCard) {
CreditCard local_card = test::GetCreditCard();
personal_data().AddCreditCard(local_card);
std::u16string title;
std::u16string body;
ShowSuggestions(manager(),
{test::CreateAutofillSuggestion(
PopupItemId::kCreditCardEntry, u"Local credit card",
Suggestion::Guid(local_card.guid()))});
EXPECT_TRUE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
EXPECT_EQ(title, local_card.CardNameAndLastFourDigits());
EXPECT_EQ(body,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY));
}
TEST_F(AutofillPopupControllerTest,
GetRemovalConfirmationText_ServerCreditCard) {
CreditCard server_card = test::GetMaskedServerCard();
personal_data().AddServerCreditCard(server_card);
std::u16string title;
std::u16string body;
ShowSuggestions(manager(),
{test::CreateAutofillSuggestion(
PopupItemId::kCreditCardEntry, u"Server credit card",
Suggestion::Guid(server_card.guid()))});
EXPECT_FALSE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
}
TEST_F(AutofillPopupControllerTest,
GetRemovalConfirmationText_CompleteAutofillProfile) {
AutofillProfile complete_profile = test::GetFullProfile();
personal_data().AddProfile(complete_profile);
std::u16string title;
std::u16string body;
ShowSuggestions(manager(),
{test::CreateAutofillSuggestion(
PopupItemId::kAddressEntry, u"Complete autofill profile",
Suggestion::Guid(complete_profile.guid()))});
EXPECT_TRUE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
EXPECT_EQ(title, complete_profile.GetRawInfo(ADDRESS_HOME_CITY));
EXPECT_EQ(body,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY));
}
TEST_F(AutofillPopupControllerTest,
GetRemovalConfirmationText_AutofillProfile_EmptyCity) {
AutofillProfile profile = test::GetFullProfile();
profile.ClearFields({ADDRESS_HOME_CITY});
personal_data().AddProfile(profile);
std::u16string title;
std::u16string body;
ShowSuggestions(manager(), {test::CreateAutofillSuggestion(
PopupItemId::kAddressEntry,
u"Autofill profile without city",
Suggestion::Guid(profile.guid()))});
EXPECT_TRUE(client().popup_controller(manager()).GetRemovalConfirmationText(
0, &title, &body));
EXPECT_EQ(title, u"Autofill profile without city");
EXPECT_EQ(body,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY));
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(AutofillPopupControllerTest, AcceptPwdSuggestionInvokesWarningAndroid) {
base::test::ScopedFeatureList scoped_feature_list(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions(manager(), {PopupItemId::kPasswordEntry});
// Calls are accepted immediately.
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(client().show_pwd_migration_warning_callback(),
Run(_, _,
password_manager::metrics_util::
PasswordMigrationWarningTriggers::kKeyboardAcessoryBar));
task_environment()->FastForwardBy(base::Milliseconds(500));
client().popup_controller(manager()).AcceptSuggestion(0);
}
TEST_F(AutofillPopupControllerTest,
AcceptUsernameSuggestionInvokesWarningAndroid) {
base::test::ScopedFeatureList scoped_feature_list(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions(manager(), {PopupItemId::kPasswordEntry});
// Calls are accepted immediately.
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(client().show_pwd_migration_warning_callback(), Run);
task_environment()->FastForwardBy(base::Milliseconds(500));
client().popup_controller(manager()).AcceptSuggestion(0);
}
TEST_F(AutofillPopupControllerTest,
AcceptPwdSuggestionNoWarningIfDisabledAndroid) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions(manager(), {PopupItemId::kPasswordEntry});
// Calls are accepted immediately.
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(client().show_pwd_migration_warning_callback(), Run).Times(0);
task_environment()->FastForwardBy(base::Milliseconds(500));
client().popup_controller(manager()).AcceptSuggestion(0);
}
TEST_F(AutofillPopupControllerTest, AcceptAddressNoPwdWarningAndroid) {
base::test::ScopedFeatureList scoped_feature_list(
password_manager::features::
kUnifiedPasswordManagerLocalPasswordsMigrationWarning);
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
// Calls are accepted immediately.
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion).Times(1);
EXPECT_CALL(client().show_pwd_migration_warning_callback(), Run).Times(0);
task_environment()->FastForwardBy(base::Milliseconds(500));
client().popup_controller(manager()).AcceptSuggestion(0);
}
// When a suggestion is accepted, the popup is hidden inside
// `delegate->DidAcceptSuggestion()`. On Android, some code is still being
// executed after hiding. This test makes sure no use-after-free, null pointer
// dereferencing or other memory violations occur.
TEST_F(AutofillPopupControllerTest, AcceptSuggestionIsMemorySafe) {
ShowSuggestions(manager(), {PopupItemId::kPasswordEntry});
task_environment()->FastForwardBy(base::Milliseconds(500));
EXPECT_CALL(manager().external_delegate(), DidAcceptSuggestion)
.WillOnce([this]() {
client().popup_controller(manager()).Hide(
PopupHidingReason::kAcceptSuggestion);
});
client().popup_controller(manager()).AcceptSuggestion(/*index=*/0);
}
#endif // BUILDFLAG(IS_ANDROID)
// Tests that the popup controller queries the view for its screen location.
TEST_F(AutofillPopupControllerTest, GetPopupScreenLocationCallsView) {
ShowSuggestions(manager(), {PopupItemId::kCompose});
using PopupScreenLocation = AutofillClient::PopupScreenLocation;
constexpr gfx::Rect kSampleRect = gfx::Rect(123, 234);
EXPECT_CALL(client().popup_view(), GetPopupScreenLocation)
.WillOnce(Return(PopupScreenLocation{.bounds = kSampleRect}));
EXPECT_THAT(client().popup_controller(manager()).GetPopupScreenLocation(),
Optional(Field(&PopupScreenLocation::bounds, kSampleRect)));
}
// Tests that a change to a text field hides a popup with a Compose suggestion.
TEST_F(AutofillPopupControllerTest, HidesOnFieldChangeForComposeEntries) {
ShowSuggestions(manager(), {PopupItemId::kCompose});
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kFieldValueChanged));
manager().NotifyObservers(
&AutofillManager::Observer::OnBeforeTextFieldDidChange, FormGlobalId(),
FieldGlobalId());
}
// Tests that a change to a text field does not hide a popup with an
// Autocomplete suggestion.
TEST_F(AutofillPopupControllerTest,
DoeNotHideOnFieldChangeForNonComposeEntries) {
ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry});
EXPECT_CALL(client().popup_controller(manager()), Hide).Times(0);
manager().NotifyObservers(
&AutofillManager::Observer::OnBeforeTextFieldDidChange, FormGlobalId(),
FieldGlobalId());
Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
}
class AutofillPopupControllerTestHidingLogic
: public AutofillPopupControllerTest {
public:
void SetUp() override {
AutofillPopupControllerTest::SetUp();
sub_frame_ = CreateAndNavigateChildFrame(
main_frame(), GURL("https://bar.com"), "sub_frame")
->GetWeakDocumentPtr();
}
TestManager& sub_manager() { return manager(sub_frame()); }
content::RenderFrameHost* sub_frame() {
return sub_frame_.AsRenderFrameHostIfValid();
}
private:
content::WeakDocumentPtr sub_frame_;
};
// Tests that if the popup is shown in the *main frame*, destruction of the
// *sub frame* does not hide the popup.
TEST_F(AutofillPopupControllerTestHidingLogic,
KeepOpenInMainFrameOnSubFrameDestruction) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(client().popup_controller(manager()), Hide).Times(0);
content::RenderFrameHostTester::For(sub_frame())->Detach();
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
}
// Tests that if the popup is shown in the *main frame*, a navigation in the
// *sub frame* does not hide the popup.
TEST_F(AutofillPopupControllerTestHidingLogic,
KeepOpenInMainFrameOnSubFrameNavigation) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(client().popup_controller(manager()), Hide).Times(0);
NavigateAndCommitFrame(sub_frame(), GURL("https://bar.com/"));
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
}
// Tests that if the popup is shown, destruction of the WebContents hides the
// popup.
TEST_F(AutofillPopupControllerTestHidingLogic, HideOnWebContentsDestroyed) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kRendererEvent));
DeleteContents();
}
// Tests that if the popup is shown in the *main frame*, destruction of the
// *main frame* hides the popup.
TEST_F(AutofillPopupControllerTestHidingLogic, HideInMainFrameOnDestruction) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kRendererEvent));
}
// Tests that if the popup is shown in the *sub frame*, destruction of the
// *sub frame* hides the popup.
TEST_F(AutofillPopupControllerTestHidingLogic, HideInSubFrameOnDestruction) {
ShowSuggestions(sub_manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&sub_manager().external_delegate());
EXPECT_CALL(client().popup_controller(sub_manager()),
Hide(PopupHidingReason::kRendererEvent));
content::RenderFrameHostTester::For(sub_frame())->Detach();
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&client().popup_controller(sub_manager()));
}
// Tests that if the popup is shown in the *main frame*, a navigation in the
// *main frame* hides the popup.
TEST_F(AutofillPopupControllerTestHidingLogic,
HideInMainFrameOnMainFrameNavigation) {
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kNavigation));
NavigateAndCommitFrame(main_frame(), GURL("https://bar.com/"));
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
}
// Tests that if the popup is shown in the *sub frame*, a navigation in the
// *sub frame* hides the popup.
TEST_F(AutofillPopupControllerTestHidingLogic,
HideInSubFrameOnSubFrameNavigation) {
ShowSuggestions(sub_manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&sub_manager().external_delegate());
if (sub_frame()->ShouldChangeRenderFrameHostOnSameSiteNavigation()) {
// If the RenderFrameHost changes, a RenderFrameDeleted will fire first.
EXPECT_CALL(client().popup_controller(sub_manager()),
Hide(PopupHidingReason::kRendererEvent));
} else {
EXPECT_CALL(client().popup_controller(sub_manager()),
Hide(PopupHidingReason::kNavigation));
}
NavigateAndCommitFrame(sub_frame(), GURL("https://bar.com/"));
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&client().popup_controller(sub_manager()));
}
// Tests that if the popup is shown in the *sub frame*, a navigation in the
// *main frame* hides the popup.
//
// TODO(crbug.com/41492848): This test only makes little sense: with BFcache,
// the navigation doesn't destroy the `sub_frame()` and thus we wouldn't hide
// the popup. What hides the popup in reality is
// AutofillExternalDelegate::DidEndTextFieldEditing().
TEST_F(AutofillPopupControllerTestHidingLogic,
HideInSubFrameOnMainFrameNavigation) {
ShowSuggestions(sub_manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&sub_manager().external_delegate());
EXPECT_CALL(client().popup_controller(sub_manager()),
Hide(PopupHidingReason::kRendererEvent));
NavigateAndCommitFrame(main_frame(), GURL("https://bar.com/"));
}
// Tests that Compose saved state notification popup gets hidden after 2
// seconds, but not after 1 second.
TEST_F(AutofillPopupControllerTestHidingLogic,
TimedHideComposeSavedStateNotification) {
ShowSuggestions(manager(), {PopupItemId::kComposeSavedStateNotification});
test::GenerateTestAutofillPopup(&manager().external_delegate());
::testing::MockFunction<void()> check;
{
::testing::InSequence s;
EXPECT_CALL(check, Call);
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kFadeTimerExpired));
}
task_environment()->FastForwardBy(base::Seconds(1));
check.Call();
task_environment()->FastForwardBy(base::Seconds(1));
Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
}
#if !BUILDFLAG(IS_ANDROID)
// Tests that if the popup is shown in the *main frame*, changing the zoom hides
// the popup.
TEST_F(AutofillPopupControllerTestHidingLogic, HideInMainFrameOnZoomChange) {
zoom::ZoomController::CreateForWebContents(web_contents());
ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
test::GenerateTestAutofillPopup(&manager().external_delegate());
// Triggered by OnZoomChanged().
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kContentAreaMoved));
// Override the default ON_CALL behavior to do nothing to avoid destroying the
// hide helper. We want to test ZoomObserver events explicitly.
EXPECT_CALL(client().popup_controller(manager()),
Hide(PopupHidingReason::kWidgetChanged))
.WillOnce(Return());
auto* zoom_controller = zoom::ZoomController::FromWebContents(web_contents());
zoom_controller->SetZoomLevel(zoom_controller->GetZoomLevel() + 1.0);
// Verify and clear before TearDown() closes the popup.
Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
}
#endif // BUILDFLAG(IS_ANDROID)
} // namespace autofill